killbill-aplcache
Changes
account/pom.xml 3(+1 -2)
account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java 20(+16 -4)
analytics/pom.xml 2(+1 -1)
analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java 6(+5 -1)
analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java 6(+3 -3)
api/pom.xml 10(+9 -1)
api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java 22(+9 -13)
beatrix/pom.xml 48(+45 -3)
beatrix/src/test/resources/catalogSample.xml 641(+641 -0)
beatrix/src/test/resources/log4j.xml 4(+4 -0)
catalog/pom.xml 2(+1 -1)
entitlement/pom.xml 13(+12 -1)
entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultBillingEvent.java 94(+67 -27)
entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java 113(+72 -41)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java 20(+18 -2)
entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java 27(+18 -9)
entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java 62(+44 -18)
entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg 14(+7 -7)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java 82(+65 -17)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java 20(+20 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java 63(+32 -31)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java 149(+149 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java 154(+154 -0)
entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java 53(+29 -24)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java 5(+3 -2)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java 13(+7 -6)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java 9(+5 -4)
entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java 11(+6 -5)
entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java 113(+60 -53)
entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java 17(+8 -9)
invoice/pom.xml 20(+15 -5)
invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java 50(+39 -11)
invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java 8(+8 -0)
invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java 125(+125 -0)
invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDatePoster.java 82(+39 -43)
invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg 63(+63 -0)
invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg 66(+66 -0)
invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java 308(+308 -0)
invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java 476(+315 -161)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java 2(+1 -1)
invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java 10(+1 -9)
invoice/src/test/resources/log4j.xml 36(+36 -0)
payment/pom.xml 100(+97 -3)
payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java 36(+36 -0)
payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java 43(+43 -0)
payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java 49(+49 -0)
payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.java 36(+36 -0)
payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.java 42(+42 -0)
payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java 41(+41 -0)
payment/src/test/resources/log4j.xml 30(+30 -0)
pom.xml 100(+88 -12)
util/pom.xml 23(+21 -2)
util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java 8(+3 -5)
Details
account/pom.xml 3(+1 -2)
diff --git a/account/pom.xml b/account/pom.xml
index 44b4543..0ac4f64 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
@@ -94,7 +94,6 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
- <version>2.0</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
index b953707..2b916bf 100644
--- a/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
+++ b/account/src/main/java/com/ning/billing/account/api/DefaultAccount.java
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright 2010-2011 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
@@ -16,150 +16,313 @@
package com.ning.billing.account.api;
-import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
+
import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.customfield.CustomizableEntityBase;
-import com.ning.billing.util.tag.DefaultTag;
import com.ning.billing.util.tag.DefaultTagStore;
+import com.ning.billing.util.tag.DescriptiveTag;
import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDescription;
-
+import com.ning.billing.util.tag.TagDefinition;
+
public class DefaultAccount extends CustomizableEntityBase implements Account {
- public final static String OBJECT_TYPE = "Account";
-
- private final String externalKey;
- private final String email;
- private final String name;
- private final int firstNameLength;
- private final String phone;
- private final Currency currency;
- private final int billCycleDay;
- private final String paymentProviderName;
- private final BigDecimal balance;
- private final DefaultTagStore tags;
-
- public DefaultAccount(AccountData data) {
- this(UUID.randomUUID(), data.getExternalKey(), data.getEmail(), data.getName(),
- data.getFirstNameLength(), data.getPhone(), data.getCurrency(), data.getBillCycleDay(),
- data.getPaymentProviderName(), BigDecimal.ZERO);
- }
-
- public DefaultAccount(UUID id, AccountData data) {
- this(id, data.getExternalKey(), data.getEmail(), data.getName(),
- data.getFirstNameLength(), data.getPhone(), data.getCurrency(), data.getBillCycleDay(),
- data.getPaymentProviderName(), BigDecimal.ZERO);
- }
-
- public DefaultAccount(UUID id, String externalKey, String email, String name, int firstNameLength,
- String phone, Currency currency, int billCycleDay, String paymentProviderName,
- BigDecimal balance) {
- super(id);
- this.externalKey = externalKey;
- this.email = email;
- this.name = name;
- this.firstNameLength = firstNameLength;
- this.phone = phone;
- this.currency = currency;
- this.billCycleDay = billCycleDay;
- this.paymentProviderName = paymentProviderName;
- this.balance = balance;
-
- this.tags = new DefaultTagStore(id, getObjectName());
- }
-
- @Override
- public String getObjectName() {
- return OBJECT_TYPE;
- }
-
- @Override
- public String getExternalKey() {
- return externalKey;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public String getEmail() {
- return email;
- }
-
- @Override
- public int getFirstNameLength() {
- return firstNameLength;
- }
-
- @Override
- public String getPhone() {
- return phone;
- }
-
- @Override
- public Currency getCurrency() {
- return currency;
- }
-
- @Override
- public int getBillCycleDay() {
- return billCycleDay;
- }
-
- @Override
- public String getPaymentProviderName() {
- return paymentProviderName;
- }
-
- @Override
- public List<Tag> getTagList() {
- return tags.getEntityList();
- }
-
- @Override
- public boolean hasTag(String tagName) {
- return tags.containsTag(tagName);
- }
-
- @Override
- public void addTag(TagDescription description, String addedBy, DateTime dateAdded) {
- Tag tag = new DefaultTag(description, addedBy, dateAdded);
- tags.add(tag) ;
- }
-
- @Override
- public void addTags(List<Tag> tags) {
- if (tags != null) {
- this.tags.add(tags);
- }
- }
-
- @Override
- public void clearTags() {
- this.tags.clear();
- }
-
- @Override
- public void removeTag(TagDescription description) {
- tags.remove(description.getName());
- }
-
- @Override
- public boolean generateInvoice() {
- return tags.generateInvoice();
- }
-
- @Override
- public boolean processPayment() {
- return tags.processPayment();
- }
-
- @Override
- public BigDecimal getBalance() {
- return balance;
- }
+ //public final static String OBJECT_TYPE = "Account";
+
+ private final String externalKey;
+ private final String email;
+ private final String name;
+ private final int firstNameLength;
+ private final Currency currency;
+ private final int billCycleDay;
+ private final String paymentProviderName;
+ private final DefaultTagStore tags;
+ private final DateTimeZone timeZone;
+ private final String locale;
+ private final String address1;
+ private final String address2;
+ private final String companyName;
+ private final String city;
+ private final String stateOrProvince;
+ private final String country;
+ private final String postalCode;
+ private final String phone;
+ private final DateTime createdDate;
+ private final DateTime updatedDate;
+
+ /**
+ * This call is used to create a new account
+ * @param data
+ * @param createdDate
+ */
+ public DefaultAccount(final AccountData data, DateTime createdDate) {
+ this(UUID.randomUUID(), data.getExternalKey(), data.getEmail(), data.getName(), data.getFirstNameLength(),
+ data.getCurrency(), data.getBillCycleDay(), data.getPaymentProviderName(),
+ data.getTimeZone(), data.getLocale(),
+ data.getAddress1(), data.getAddress2(), data.getCompanyName(),
+ data.getCity(), data.getStateOrProvince(), data.getCountry(),
+ data.getPostalCode(), data.getPhone(), createdDate, createdDate);
+ }
+
+ //intended for creation
+ public DefaultAccount(final AccountData data) {
+ this(UUID.randomUUID(), data, null, null);
+ }
+
+ // Intended for migration
+ public DefaultAccount(final AccountData data, DateTime createdDate, DateTime updatedDate) {
+ this(UUID.randomUUID(), data, createdDate, updatedDate);
+ }
+
+ //intended for update
+ public DefaultAccount(final UUID id, final AccountData data, DateTime createdDate, DateTime updatedDate) {
+ this(id, data.getExternalKey(), data.getEmail(), data.getName(), data.getFirstNameLength(),
+ data.getCurrency(), data.getBillCycleDay(), data.getPaymentProviderName(),
+ data.getTimeZone(), data.getLocale(),
+ data.getAddress1(), data.getAddress2(), data.getCompanyName(),
+ data.getCity(), data.getStateOrProvince(), data.getCountry(),
+ data.getPostalCode(), data.getPhone(), createdDate, updatedDate);
+ }
+
+
+ /**
+ * This call is used to update an existing account
+ *
+ * @param id
+ * @param data
+ */
+ public DefaultAccount(final UUID id, final AccountData data) {
+ this(id, data.getExternalKey(), data.getEmail(), data.getName(), data.getFirstNameLength(),
+ data.getCurrency(), data.getBillCycleDay(), data.getPaymentProviderName(),
+ data.getTimeZone(), data.getLocale(),
+ data.getAddress1(), data.getAddress2(), data.getCompanyName(),
+ data.getCity(), data.getStateOrProvince(), data.getCountry(),
+ data.getPostalCode(), data.getPhone(), null, null);
+ }
+
+ /**
+ * This call is used for testing
+ * @param id
+ * @param externalKey
+ * @param email
+ * @param name
+ * @param firstNameLength
+ * @param currency
+ * @param billCycleDay
+ * @param paymentProviderName
+ * @param timeZone
+ * @param locale
+ * @param address1
+ * @param address2
+ * @param companyName
+ * @param city
+ * @param stateOrProvince
+ * @param country
+ * @param postalCode
+ * @param phone
+ * @param createdDate
+ * @param updatedDate
+ */
+ public DefaultAccount(final UUID id, final String externalKey, final String email, final String name, final int firstNameLength,
+ final Currency currency, final int billCycleDay, final String paymentProviderName,
+ 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, DateTime createdDate, DateTime updatedDate) {
+
+ super(id);
+ this.externalKey = externalKey;
+ this.email = email;
+ this.name = name;
+ this.firstNameLength = firstNameLength;
+ this.currency = currency;
+ this.billCycleDay = billCycleDay;
+ this.paymentProviderName = paymentProviderName;
+ this.timeZone = timeZone;
+ this.locale = locale;
+ this.address1 = address1;
+ this.address2 = address2;
+ this.companyName = companyName;
+ this.city = city;
+ this.stateOrProvince = stateOrProvince;
+ this.postalCode = postalCode;
+ this.country = country;
+ this.phone = phone;
+ this.createdDate = createdDate == null ? new DateTime(DateTimeZone.UTC) : createdDate; // This is a fallback, we are only expecting these to be set to null
+ this.updatedDate = updatedDate == null ? new DateTime(DateTimeZone.UTC) : updatedDate; // in the case that the account is being updated. In which case the values are ignored anyway
+ this.tags = new DefaultTagStore(id, getObjectName());
+ }
+
+ @Override
+ public String getObjectName() {
+ return "Account";
+ }
+
+ @Override
+ public String getExternalKey() {
+ return externalKey;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getEmail() {
+ return email;
+ }
+
+ public DefaultTagStore getTags() {
+ return tags;
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ @Override
+ public int getFirstNameLength() {
+ return firstNameLength;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ @Override
+ public int getBillCycleDay() {
+ return billCycleDay;
+ }
+
+ @Override
+ public String getPaymentProviderName() {
+ return paymentProviderName;
+ }
+
+ @Override
+ public DateTimeZone getTimeZone() {
+ return timeZone;
+ }
+
+ @Override
+ public String getLocale() {
+ return locale;
+ }
+
+ @Override
+ public String getAddress1() {
+ return address1;
+ }
+
+ @Override
+ public String getAddress2() {
+ return address2;
+ }
+
+ @Override
+ public String getCompanyName() {
+ return companyName;
+ }
+
+ @Override
+ public String getCity() {
+ return city;
+ }
+
+ @Override
+ public String getStateOrProvince() {
+ return stateOrProvince;
+ }
+
+ @Override
+ public String getPostalCode() {
+ return postalCode;
+ }
+
+ @Override
+ public String getCountry() {
+ return country;
+ }
+
+ @Override
+ public String getPhone() {
+ return phone;
+ }
+
+ @Override
+ public List<Tag> getTagList() {
+ return tags.getEntityList();
+ }
+
+ @Override
+ public boolean hasTag(String tagName) {
+ return tags.containsTag(tagName);
+ }
+
+ @Override
+ public void addTag(TagDefinition definition, String addedBy, DateTime dateAdded) {
+ Tag tag = new DescriptiveTag(definition, addedBy, dateAdded);
+ tags.add(tag) ;
+ }
+
+ @Override
+ public void addTags(List<Tag> tags) {
+ if (tags != null) {
+ this.tags.add(tags);
+ }
+ }
+
+ @Override
+ public void clearTags() {
+ this.tags.clear();
+ }
+
+ @Override
+ public void removeTag(TagDefinition definition) {
+ tags.remove(definition.getName());
+ }
+
+ @Override
+ public boolean generateInvoice() {
+ return tags.generateInvoice();
+ }
+
+ @Override
+ public boolean processPayment() {
+ return tags.processPayment();
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultAccount [externalKey=" + externalKey + ", email=" + email +
+ ", name=" + name + ", " +
+ "firstNameLength=" + firstNameLength +
+ ", phone=" + phone + ", " +
+ "currency=" + currency +
+ ", billCycleDay=" + billCycleDay +
+ ", paymentProviderName=" + paymentProviderName +
+ ", timezone=" + timeZone +
+ ", locale=" + locale +
+ ", address1" + address1 +
+ ", address2" + address2 +
+ ", companyName" + companyName +
+ ", city" + city +
+ ", stateOrProvince" + stateOrProvince +
+ ", postalCode" + postalCode +
+ ", country" +
+ ", tags=" + tags +
+ ", createdDate=" + createdDate +
+ ", updatedDate=" + updatedDate + "]";
+ }
}
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java b/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
index e05e967..95eb5d8 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/AccountBuilder.java
@@ -16,79 +16,145 @@
package com.ning.billing.account.api.user;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
import com.ning.billing.account.api.DefaultAccount;
import com.ning.billing.catalog.api.Currency;
-import java.math.BigDecimal;
-import java.util.UUID;
-
public class AccountBuilder {
- private UUID id;
+ private final UUID id;
private String externalKey;
private String email;
private String name;
private int firstNameLength;
- private String phone;
private Currency currency;
private int billingCycleDay;
private String paymentProviderName;
- private BigDecimal balance;
+ private DateTimeZone timeZone;
+ private String locale;
+ private String address1;
+ private String address2;
+ private String companyName;
+ private String city;
+ private String stateOrProvince;
+ private String country;
+ private String postalCode;
+ private String phone;
+ private DateTime createdDate;
+ private DateTime updatedDate;
public AccountBuilder() {
this(UUID.randomUUID());
}
- public AccountBuilder(UUID id) {
+ public AccountBuilder(final UUID id) {
this.id = id;
}
- public AccountBuilder externalKey(String externalKey) {
+ public AccountBuilder externalKey(final String externalKey) {
this.externalKey = externalKey;
return this;
}
- public AccountBuilder email(String email) {
+ public AccountBuilder email(final String email) {
this.email = email;
return this;
}
- public AccountBuilder name(String name) {
+ public AccountBuilder name(final String name) {
this.name = name;
return this;
}
- public AccountBuilder firstNameLength(int firstNameLength) {
+ public AccountBuilder firstNameLength(final int firstNameLength) {
this.firstNameLength = firstNameLength;
return this;
}
- public AccountBuilder phone(String phone) {
- this.phone = phone;
- return this;
- }
-
- public AccountBuilder billingCycleDay(int billingCycleDay) {
+ public AccountBuilder billingCycleDay(final int billingCycleDay) {
this.billingCycleDay = billingCycleDay;
return this;
}
- public AccountBuilder currency(Currency currency) {
+ public AccountBuilder currency(final Currency currency) {
this.currency = currency;
return this;
}
- public AccountBuilder paymentProviderName(String paymentProviderName) {
+ public AccountBuilder paymentProviderName(final String paymentProviderName) {
this.paymentProviderName = paymentProviderName;
return this;
}
- public AccountBuilder balance(BigDecimal balance) {
- this.balance = balance;
+ public AccountBuilder timeZone(final DateTimeZone timeZone) {
+ this.timeZone = timeZone;
+ return this;
+ }
+
+ public AccountBuilder locale(final String locale) {
+ this.locale = locale;
+ return this;
+ }
+
+ public AccountBuilder address1(final String address1) {
+ this.address1 = address1;
+ return this;
+ }
+
+ public AccountBuilder address2(final String address2) {
+ this.address2 = address2;
+ return this;
+ }
+
+ public AccountBuilder companyName(final String companyName) {
+ this.companyName = companyName;
+ return this;
+ }
+
+ public AccountBuilder city(final String city) {
+ this.city = city;
+ return this;
+ }
+
+ public AccountBuilder stateOrProvince(final String stateOrProvince) {
+ this.stateOrProvince = stateOrProvince;
+ return this;
+ }
+
+ public AccountBuilder postalCode(final String postalCode) {
+ this.postalCode = postalCode;
+ return this;
+ }
+
+ public AccountBuilder country(final String country) {
+ this.country = country;
+ return this;
+ }
+
+ public AccountBuilder phone(final String phone) {
+ this.phone = phone;
+ return this;
+ }
+
+ public AccountBuilder createdDate(DateTime createdDate) {
+ this.createdDate = createdDate;
+ return this;
+ }
+
+ public AccountBuilder updatedDate(DateTime updatedDate) {
+ this.updatedDate = updatedDate;
return this;
}
public DefaultAccount build() {
return new DefaultAccount(id, externalKey, email, name, firstNameLength,
- phone, currency, billingCycleDay, paymentProviderName, balance);
+ currency, billingCycleDay, paymentProviderName,
+ timeZone, locale,
+ address1, address2, companyName, city, stateOrProvince, country,
+ postalCode, phone,
+ createdDate, updatedDate);
}
}
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
index 178100f..11a5a39 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
@@ -62,9 +62,6 @@ public class DefaultAccountChangeNotification implements AccountChangeNotificati
addIfValueChanged(tmpChangedFields, "firstName",
oldData.getName(), newData.getName());
- addIfValueChanged(tmpChangedFields, "phone",
- oldData.getPhone(), newData.getPhone());
-
addIfValueChanged(tmpChangedFields, "currency",
(oldData.getCurrency() != null) ? oldData.getCurrency().toString() : null,
(newData.getCurrency() != null) ? newData.getCurrency().toString() : null);
@@ -75,6 +72,21 @@ public class DefaultAccountChangeNotification implements AccountChangeNotificati
addIfValueChanged(tmpChangedFields,"paymentProviderName",
oldData.getPaymentProviderName(), newData.getPaymentProviderName());
+
+ addIfValueChanged(tmpChangedFields, "locale", oldData.getLocale(), newData.getLocale());
+
+ addIfValueChanged(tmpChangedFields, "timeZone",
+ (oldData.getTimeZone() == null) ? null : oldData.getTimeZone().toString(),
+ (newData.getTimeZone() == null) ? null : newData.getTimeZone().toString());
+
+ addIfValueChanged(tmpChangedFields, "address1", oldData.getAddress1(), newData.getAddress1());
+ addIfValueChanged(tmpChangedFields, "address2", oldData.getAddress2(), newData.getAddress2());
+ addIfValueChanged(tmpChangedFields, "city", oldData.getCity(), newData.getCity());
+ addIfValueChanged(tmpChangedFields, "stateOrProvince", oldData.getStateOrProvince(), newData.getStateOrProvince());
+ addIfValueChanged(tmpChangedFields, "country", oldData.getCountry(), newData.getCountry());
+ addIfValueChanged(tmpChangedFields, "postalCode", oldData.getPostalCode(), newData.getPostalCode());
+ addIfValueChanged(tmpChangedFields, "phone", oldData.getPhone(), newData.getPhone());
+
return tmpChangedFields;
}
@@ -85,7 +97,7 @@ public class DefaultAccountChangeNotification implements AccountChangeNotificati
// If only one is null
} else if (newData == null || oldData == null) {
inputList.add(new DefaultChangedField(key, oldData, newData));
- // If non are null we can safely compare values
+ // If neither are null we can safely compare values
} else if (!newData.equals(oldData)) {
inputList.add(new DefaultChangedField(key, oldData, newData));
}
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index d6bf995..fd17c86 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -18,39 +18,37 @@ package com.ning.billing.account.api.user;
import java.util.List;
import java.util.UUID;
+
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountData;
import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.account.api.MigrationAccountData;
import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.tag.Tag;
public class DefaultAccountUserApi implements com.ning.billing.account.api.AccountUserApi {
private final AccountDao dao;
+ private Clock clock;
@Inject
- public DefaultAccountUserApi(final AccountDao dao) {
+ public DefaultAccountUserApi(final AccountDao dao, final Clock clock) {
this.dao = dao;
+ this.clock = clock;
}
@Override
public Account createAccount(final AccountData data, final List<CustomField> fields, List<Tag> tags) throws AccountApiException {
- String key = data.getExternalKey();
- Account existingAccount = dao.getAccountByKey(key);
-
- if (existingAccount == null) {
- Account account = new DefaultAccount(data);
- account.addFields(fields);
- account.addTags(tags);
-
- dao.create(account);
- return account;
- } else {
- throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
- }
+ Account account = new DefaultAccount(data, clock.getUTCNow());
+ account.addFields(fields);
+ account.addTags(tags);
+
+ dao.create(account);
+ return account;
}
@Override
@@ -69,12 +67,40 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
}
@Override
- public UUID getIdFromKey(final String externalKey) {
+ public UUID getIdFromKey(final String externalKey) throws AccountApiException {
return dao.getIdFromKey(externalKey);
}
@Override
- public void updateAccount(final Account account) {
+ public void updateAccount(final Account account) throws AccountApiException {
dao.update(account);
}
+
+ @Override
+ public void updateAccount(final String externalKey, final AccountData accountData) throws AccountApiException {
+ UUID accountId = getIdFromKey(externalKey);
+ if(accountId == null) {
+ throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey);
+ }
+ Account account = new DefaultAccount(accountId, accountData);
+ dao.update(account);
+ }
+
+ @Override
+ public void deleteAccountByKey(String externalKey) throws AccountApiException {
+ dao.deleteByKey(externalKey);
+ }
+
+ @Override
+ public Account migrateAccount(MigrationAccountData data,
+ List<CustomField> fields, List<Tag> tags)
+ throws AccountApiException {
+
+ Account account = new DefaultAccount(data);
+ account.addFields(fields);
+ account.addTags(tags);
+
+ dao.create(account);
+ return account;
+ }
}
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
index 04b50e3..74a8d51 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountDao.java
@@ -18,10 +18,19 @@ package com.ning.billing.account.dao;
import java.util.UUID;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.util.entity.EntityDao;
public interface AccountDao extends EntityDao<Account> {
public Account getAccountByKey(String key);
- public UUID getIdFromKey(String externalKey);
-}
+ /***
+ *
+ * @param externalKey
+ * @return
+ * @throws AccountApiException when externalKey is null
+ */
+ public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+ public void deleteByKey(String externalKey) throws AccountApiException;
+}
\ No newline at end of file
diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
index 21122e2..42ec5a8 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountSqlDao.java
@@ -16,12 +16,19 @@
package com.ning.billing.account.dao;
-import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.account.api.user.AccountBuilder;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.UuidMapper;
-import com.ning.billing.util.entity.EntityDao;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.sqlobject.Bind;
@@ -36,15 +43,11 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.math.BigDecimal;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.UUID;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.user.AccountBuilder;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.UuidMapper;
+import com.ning.billing.util.entity.EntityDao;
@ExternalizedSqlViaStringTemplate3
@RegisterMapper({UuidMapper.class, AccountSqlDao.AccountMapper.class})
@@ -63,25 +66,60 @@ public interface AccountSqlDao extends EntityDao<Account>, Transactional<Account
@SqlUpdate
public void update(@AccountBinder Account account);
+ @Override
+ @SqlUpdate
+ public void deleteByKey(@Bind("externalKey") final String key);
+
public static class AccountMapper implements ResultSetMapper<Account> {
+
+ private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+ final Timestamp resultStamp = rs.getTimestamp(fieldName);
+ return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+ }
+
@Override
public Account map(int index, ResultSet result, StatementContext context) throws SQLException {
+
UUID id = UUID.fromString(result.getString("id"));
String externalKey = result.getString("external_key");
String email = result.getString("email");
String name = result.getString("name");
int firstNameLength = result.getInt("first_name_length");
- String phone = result.getString("phone");
int billingCycleDay = result.getInt("billing_cycle_day");
+
String currencyString = result.getString("currency");
Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
+
String paymentProviderName = result.getString("payment_provider_name");
+ DateTime createdDate = getDate(result, "created_dt");
+ DateTime updatedDate = getDate(result, "updated_dt");
+
+ String timeZoneId = result.getString("time_zone");
+ DateTimeZone timeZone = (timeZoneId == null) ? null : DateTimeZone.forID(timeZoneId);
+
+ String locale = result.getString("locale");
+
+ String address1 = result.getString("address1");
+ String address2 = result.getString("address2");
+ String companyName = result.getString("company_name");
+ String city = result.getString("city");
+ String stateOrProvince = result.getString("state_or_province");
+ String postalCode = result.getString("postal_code");
+ String country = result.getString("country");
+ String phone = result.getString("phone");
return new AccountBuilder(id).externalKey(externalKey).email(email)
.name(name).firstNameLength(firstNameLength)
.phone(phone).currency(currency)
.billingCycleDay(billingCycleDay)
.paymentProviderName(paymentProviderName)
+ .timeZone(timeZone).locale(locale)
+ .address1(address1).address2(address2)
+ .companyName(companyName)
+ .city(city).stateOrProvince(stateOrProvince)
+ .postalCode(postalCode).country(country)
+ .createdDate(createdDate)
+ .updatedDate(updatedDate)
.build();
}
}
@@ -91,19 +129,37 @@ public interface AccountSqlDao extends EntityDao<Account>, Transactional<Account
@Target({ElementType.PARAMETER})
public @interface AccountBinder {
public static class AccountBinderFactory implements BinderFactory {
- public Binder build(Annotation annotation) {
+ @Override
+ public Binder<AccountBinder, Account> build(Annotation annotation) {
return new Binder<AccountBinder, Account>() {
- public void bind(SQLStatement q, AccountBinder bind, Account account) {
+ private Date getDate(DateTime dateTime) {
+ return dateTime == null ? null : dateTime.toDate();
+ }
+
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement q, AccountBinder bind, Account account) {
q.bind("id", account.getId().toString());
q.bind("externalKey", account.getExternalKey());
q.bind("email", account.getEmail());
q.bind("name", account.getName());
q.bind("firstNameLength", account.getFirstNameLength());
- q.bind("phone", account.getPhone());
Currency currency = account.getCurrency();
q.bind("currency", (currency == null) ? null : currency.toString());
q.bind("billingCycleDay", account.getBillCycleDay());
q.bind("paymentProviderName", account.getPaymentProviderName());
+ DateTimeZone timeZone = account.getTimeZone();
+ q.bind("timeZone", (timeZone == null) ? null : timeZone.toString());
+ q.bind("locale", account.getLocale());
+ q.bind("address1", account.getAddress1());
+ q.bind("address2", account.getAddress2());
+ q.bind("companyName", account.getCompanyName());
+ q.bind("city", account.getCity());
+ q.bind("stateOrProvince", account.getStateOrProvince());
+ q.bind("country", account.getCountry());
+ q.bind("postalCode", account.getPostalCode());
+ q.bind("phone", account.getPhone());
+ q.bind("createdDate", getDate(account.getCreatedDate()));
+ q.bind("updatedDate", getDate(account.getUpdatedDate()));
}
};
}
diff --git a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
index c4671ea..1e00dba 100644
--- a/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/DefaultAccountDao.java
@@ -18,37 +18,39 @@ package com.ning.billing.account.dao;
import java.util.List;
import java.util.UUID;
+
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountChangeNotification;
import com.ning.billing.account.api.AccountCreationNotification;
-import com.ning.billing.account.api.DefaultAccount;
import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.customfield.dao.FieldStoreDao;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.dao.TagStoreDao;
+import com.ning.billing.util.tag.dao.TagStoreSqlDao;
public class DefaultAccountDao implements AccountDao {
- private final AccountSqlDao accountDao;
- private final EventBus eventBus;
+ private final AccountSqlDao accountSqlDao;
+ private final Bus eventBus;
@Inject
- public DefaultAccountDao(IDBI dbi, EventBus eventBus) {
+ public DefaultAccountDao(IDBI dbi, Bus eventBus) {
this.eventBus = eventBus;
- this.accountDao = dbi.onDemand(AccountSqlDao.class);
+ this.accountSqlDao = dbi.onDemand(AccountSqlDao.class);
}
@Override
public Account getAccountByKey(final String key) {
- return accountDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
+ return accountSqlDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
@Override
- public Account inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
+ public Account inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws Exception {
Account account = accountSqlDao.getAccountByKey(key);
if (account != null) {
setCustomFieldsFromWithinTransaction(account, accountSqlDao);
@@ -60,86 +62,121 @@ public class DefaultAccountDao implements AccountDao {
}
@Override
- public UUID getIdFromKey(final String externalKey) {
- return accountDao.getIdFromKey(externalKey);
+ public UUID getIdFromKey(final String externalKey) throws AccountApiException {
+ if (externalKey == null) {
+ throw new AccountApiException(ErrorCode.ACCOUNT_CANNOT_MAP_NULL_KEY, "");
+ }
+ return accountSqlDao.getIdFromKey(externalKey);
}
@Override
public Account getById(final String id) {
- return accountDao.inTransaction(new Transaction<Account, AccountSqlDao>() {
- @Override
- public Account inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
- Account account = accountSqlDao.getById(id);
- if (account != null) {
- setCustomFieldsFromWithinTransaction(account, accountSqlDao);
- setTagsFromWithinTransaction(account, accountSqlDao);
- }
- return account;
- }
- });
+ Account account = accountSqlDao.getById(id);
+ if (account != null) {
+ setCustomFieldsFromWithinTransaction(account, accountSqlDao);
+ setTagsFromWithinTransaction(account, accountSqlDao);
+ }
+ return account;
}
@Override
public List<Account> get() {
- return accountDao.get();
+ return accountSqlDao.get();
}
@Override
- public void create(final Account account) {
- final String accountId = account.getId().toString();
- final String objectType = DefaultAccount.OBJECT_TYPE;
-
- accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
- @Override
- public Void inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
- accountSqlDao.create(account);
-
- FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
- fieldStoreDao.save(accountId, objectType, account.getFieldList());
-
- TagStoreDao tagStoreDao = accountSqlDao.become(TagStoreDao.class);
- tagStoreDao.save(accountId, objectType, account.getTagList());
-
- AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
- eventBus.post(creationEvent);
- return null;
+ public void create(final Account account) throws AccountApiException {
+ final String key = account.getExternalKey();
+ try {
+
+ accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+ @Override
+ public Void inTransaction(final AccountSqlDao transactionalDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
+ Account currentAccount = transactionalDao.getAccountByKey(key);
+ if (currentAccount != null) {
+ throw new AccountApiException(ErrorCode.ACCOUNT_ALREADY_EXISTS, key);
+ }
+ transactionalDao.create(account);
+
+ saveTagsFromWithinTransaction(account, transactionalDao, true);
+ saveCustomFieldsFromWithinTransaction(account, transactionalDao, true);
+ AccountCreationNotification creationEvent = new DefaultAccountCreationEvent(account);
+ eventBus.post(creationEvent);
+ return null;
+ }
+ });
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof AccountApiException) {
+ throw (AccountApiException) re.getCause();
+ } else {
+ throw re;
}
- });
+ }
}
@Override
- public void update(final Account account) {
- final String accountId = account.getId().toString();
- final String objectType = DefaultAccount.OBJECT_TYPE;
-
- accountDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
- @Override
- public Void inTransaction(AccountSqlDao accountSqlDao, TransactionStatus status) throws Exception {
- Account currentAccount = accountSqlDao.getById(accountId);
-
- accountSqlDao.update(account);
+ public void update(final Account account) throws AccountApiException {
+ try {
+ accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+ @Override
+ public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
+ String accountId = account.getId().toString();
+ Account currentAccount = accountSqlDao.getById(accountId);
+ if (currentAccount == null) {
+ throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, accountId);
+ }
+
+ String currentKey = currentAccount.getExternalKey();
+ if (!currentKey.equals(account.getExternalKey())) {
+ throw new AccountApiException(ErrorCode.ACCOUNT_CANNOT_CHANGE_EXTERNAL_KEY, currentKey);
+ }
+
+ accountSqlDao.update(account);
+
+ saveTagsFromWithinTransaction(account, accountSqlDao, false);
+ saveCustomFieldsFromWithinTransaction(account, accountSqlDao, false);
+
+ AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+ if (changeEvent.hasChanges()) {
+ eventBus.post(changeEvent);
+ }
+ return null;
+ }
+ });
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof AccountApiException) {
+ throw (AccountApiException) re.getCause();
+ } else {
+ throw re;
+ }
+ }
+ }
- FieldStoreDao fieldStoreDao = accountSqlDao.become(FieldStoreDao.class);
- fieldStoreDao.clear(accountId, objectType);
- fieldStoreDao.save(accountId, objectType, account.getFieldList());
+ @Override
+ public void deleteByKey(final String externalKey) throws AccountApiException {
+ try {
+ accountSqlDao.inTransaction(new Transaction<Void, AccountSqlDao>() {
+ @Override
+ public Void inTransaction(final AccountSqlDao accountSqlDao, final TransactionStatus status) throws AccountApiException, Bus.EventBusException {
- TagStoreDao tagStoreDao = fieldStoreDao.become(TagStoreDao.class);
- tagStoreDao.clear(accountId, objectType);
- tagStoreDao.save(accountId, objectType, account.getTagList());
+ accountSqlDao.deleteByKey(externalKey);
- AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
- if (changeEvent.hasChanges()) {
- eventBus.post(changeEvent);
+ return null;
}
- return null;
+ });
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof AccountApiException) {
+ throw (AccountApiException) re.getCause();
+ } else {
+ throw re;
}
- });
- }
+ }
+ }
@Override
public void test() {
- accountDao.test();
+ accountSqlDao.test();
}
private void setCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
@@ -148,14 +185,12 @@ public class DefaultAccountDao implements AccountDao {
account.clearFields();
if (fields != null) {
- for (CustomField field : fields) {
- account.setFieldValue(field.getName(), field.getValue());
- }
+ account.addFields(fields);
}
}
private void setTagsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao) {
- TagStoreDao tagStoreDao = transactionalDao.become(TagStoreDao.class);
+ TagStoreSqlDao tagStoreDao = transactionalDao.become(TagStoreSqlDao.class);
List<Tag> tags = tagStoreDao.load(account.getId().toString(), account.getObjectName());
account.clearTags();
@@ -163,4 +198,36 @@ public class DefaultAccountDao implements AccountDao {
account.addTags(tags);
}
}
+
+ private void saveTagsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao, final boolean isCreation) {
+ String accountId = account.getId().toString();
+ String objectType = account.getObjectName();
+
+ TagStoreSqlDao tagStoreDao = transactionalDao.become(TagStoreSqlDao.class);
+ if (!isCreation) {
+ tagStoreDao.clear(accountId, objectType);
+ }
+
+ List<Tag> tagList = account.getTagList();
+ if (tagList != null) {
+ tagStoreDao.batchSaveFromTransaction(accountId, objectType, tagList);
+ }
+ }
+
+ private void saveCustomFieldsFromWithinTransaction(final Account account, final AccountSqlDao transactionalDao, final boolean isCreation) {
+ String accountId = account.getId().toString();
+ String objectType = account.getObjectName();
+
+ FieldStoreDao fieldStoreDao = transactionalDao.become(FieldStoreDao.class);
+ if (!isCreation) {
+ fieldStoreDao.clear(accountId, objectType);
+ }
+
+ List<CustomField> fieldList = account.getFieldList();
+ if (fieldList != null) {
+ fieldStoreDao.batchSaveFromTransaction(accountId, objectType, fieldList);
+ }
+ }
+
+
}
diff --git a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
index 345e2a1..f6e51f4 100644
--- a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
+++ b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
@@ -16,15 +16,15 @@
package com.ning.billing.account.glue;
+import org.skife.config.ConfigurationObjectFactory;
+
import com.google.inject.AbstractModule;
import com.ning.billing.account.api.AccountService;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.account.api.DefaultAccountService;
import com.ning.billing.account.api.user.DefaultAccountUserApi;
import com.ning.billing.account.dao.AccountDao;
-import com.ning.billing.account.dao.AccountSqlDao;
import com.ning.billing.account.dao.DefaultAccountDao;
-import org.skife.config.ConfigurationObjectFactory;
public class AccountModule extends AbstractModule {
@@ -33,11 +33,11 @@ public class AccountModule extends AbstractModule {
bind(AccountConfig.class).toInstance(config);
}
- private void installAccountDao() {
+ protected void installAccountDao() {
bind(AccountDao.class).to(DefaultAccountDao.class).asEagerSingleton();
}
- private void installAccountUserApi() {
+ protected void installAccountUserApi() {
bind(AccountUserApi.class).to(DefaultAccountUserApi.class).asEagerSingleton();
}
diff --git a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
index 7c0e76e..7e93ff2 100644
--- a/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
+++ b/account/src/main/resources/com/ning/billing/account/dao/AccountSqlDao.sql.stg
@@ -1,45 +1,80 @@
group AccountDaoSql;
+accountFields(prefix) ::= <<
+ <prefix>id,
+ <prefix>external_key,
+ <prefix>email,
+ <prefix>name,
+ <prefix>first_name_length,
+ <prefix>currency,
+ <prefix>billing_cycle_day,
+ <prefix>payment_provider_name,
+ <prefix>time_zone,
+ <prefix>locale,
+ <prefix>address1,
+ <prefix>address2,
+ <prefix>company_name,
+ <prefix>city,
+ <prefix>state_or_province,
+ <prefix>country,
+ <prefix>postal_code,
+ <prefix>phone,
+ <prefix>created_dt,
+ <prefix>updated_dt
+>>
+
+
create() ::= <<
INSERT INTO accounts
- (id, external_key, email, name, first_name_length, phone, currency, billing_cycle_day, payment_provider_name)
+ (<accountFields()>)
VALUES
- (:id, :externalKey, :email, :name, :firstNameLength, :phone, :currency, :billingCycleDay, :paymentProviderName);
+ (:id, :externalKey, :email, :name, :firstNameLength, :currency, :billingCycleDay,
+ :paymentProviderName, :timeZone, :locale,
+ :address1, :address2, :companyName, :city, :stateOrProvince, :country, :postalCode, :phone,
+ :createdDate, :updatedDate);
>>
update() ::= <<
UPDATE accounts
- SET email = :email, name = :name, first_name_length = :firstNameLength, phone = :phone,
- currency = :currency, billing_cycle_day = :billingCycleDay, payment_provider_name = :paymentProviderName
+ SET external_key = :externalKey, email = :email, name = :name, first_name_length = :firstNameLength,
+ currency = :currency, billing_cycle_day = :billingCycleDay, payment_provider_name = :paymentProviderName,
+ time_zone = :timeZone, locale = :locale,
+ address1 = :address1, address2 = :address2, company_name = :companyName, city = :city, state_or_province = :stateOrProvince,
+ country = :country, postal_code = :postalCode, phone = :phone,
+ updated_dt = NOW()
WHERE id = :id;
>>
+deleteByKey() ::= <<
+ DELETE FROM accounts
+ WHERE external_key = :externalKey;
+>>
+
+
getAccountByKey() ::= <<
- select id, external_key, email, name, first_name_length, phone, currency, billing_cycle_day, payment_provider_name
+ select <accountFields()>
from accounts
where external_key = :externalKey;
>>
getById() ::= <<
- select
- a.id, a.external_key, a.email, a.name, a.first_name_length,
- a.phone, a.currency, a.billing_cycle_day, a.payment_provider_name
- from accounts a
- where a.id = :id;
+ SELECT <accountFields()>
+ FROM accounts
+ WHERE id = :id;
>>
get() ::= <<
- select id, external_key, email, name, first_name_length, phone, currency, billing_cycle_day, payment_provider_name
- from accounts;
+ SELECT <accountFields()>
+ FROM accounts;
>>
getIdFromKey() ::= <<
- select id
- from accounts
- where external_key = :externalKey;
+ SELECT id
+ FROM accounts
+ WHERE external_key = :externalKey;
>>
test() ::= <<
- select 1 from accounts;
+ SELECT 1 FROM accounts;
>>
;
\ No newline at end of file
diff --git a/account/src/main/resources/com/ning/billing/account/ddl.sql b/account/src/main/resources/com/ning/billing/account/ddl.sql
index ccfed72..58b47f0 100644
--- a/account/src/main/resources/com/ning/billing/account/ddl.sql
+++ b/account/src/main/resources/com/ning/billing/account/ddl.sql
@@ -5,11 +5,23 @@ CREATE TABLE accounts (
email varchar(50) NOT NULL,
name varchar(100) NOT NULL,
first_name_length int NOT NULL,
- phone varchar(13) DEFAULT NULL,
currency char(3) DEFAULT NULL,
billing_cycle_day int DEFAULT NULL,
payment_provider_name varchar(20) DEFAULT NULL,
+ time_zone varchar(50) DEFAULT NULL,
+ locale varchar(5) DEFAULT NULL,
+ address1 varchar(100) DEFAULT NULL,
+ address2 varchar(100) DEFAULT NULL,
+ company_name varchar(50) DEFAULT NULL,
+ city varchar(50) DEFAULT NULL,
+ state_or_province varchar(50) DEFAULT NULL,
+ country varchar(50) DEFAULT NULL,
+ postal_code varchar(11) DEFAULT NULL,
+ phone varchar(13) DEFAULT NULL,
+ created_dt datetime,
+ updated_dt datetime,
PRIMARY KEY(id)
) ENGINE=innodb;
CREATE UNIQUE INDEX accounts_external_key ON accounts(external_key);
-CREATE UNIQUE INDEX accounts_email ON accounts(email);
\ No newline at end of file
+CREATE UNIQUE INDEX accounts_email ON accounts(email);
+
diff --git a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
new file mode 100644
index 0000000..ac74b68
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.api;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
+
+public class MockAccountUserApi implements AccountUserApi {
+ private final CopyOnWriteArrayList<Account> accounts = new CopyOnWriteArrayList<Account>();
+
+ public Account createAccount(UUID id,
+ String externalKey,
+ String email,
+ String name,
+ int firstNameLength,
+ Currency currency,
+ int billCycleDay,
+ String paymentProviderName,
+ BigDecimal balance,
+ 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) {
+
+ Account result = new DefaultAccount(id, externalKey, email, name,
+ firstNameLength, currency, billCycleDay, paymentProviderName,
+ timeZone, locale, address1, address2, companyName, city,
+ stateOrProvince, country, postalCode, phone, null, null);
+ accounts.add(result);
+ return result;
+ }
+
+ @Override
+ public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException {
+ Account result = new DefaultAccount(data, new DateTime());
+ accounts.add(result);
+ return result;
+ }
+
+ @Override
+ public Account getAccountByKey(String key) {
+ for (Account account : accounts) {
+ if (key.equals(account.getExternalKey())) {
+ return account;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Account getAccountById(UUID uid) {
+ for (Account account : accounts) {
+ if (uid.equals(account.getId())) {
+ return account;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public List<Account> getAccounts() {
+ return new ArrayList<Account>(accounts);
+ }
+
+ @Override
+ public UUID getIdFromKey(String externalKey) {
+ for (Account account : accounts) {
+ if (externalKey.equals(account.getExternalKey())) {
+ return account.getId();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void updateAccount(Account account) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void deleteAccountByKey(String externalKey)
+ throws AccountApiException {
+ for (Account account : accounts) {
+ if (externalKey.equals(account.getExternalKey())) {
+ accounts.remove(account.getId());
+ }
+ }
+
+ }
+
+ @Override
+ public Account migrateAccount(MigrationAccountData data,
+ List<CustomField> fields, List<Tag> tags)
+ throws AccountApiException {
+ Account result = new DefaultAccount(data, data.getCreatedDate(), data.getUpdatedDate());
+ accounts.add(result);
+ return result;
+ }
+
+ @Override
+ public void updateAccount(String key, AccountData accountData)
+ throws AccountApiException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
index b18a41b..f4f530e 100644
--- a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
+++ b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
@@ -16,23 +16,24 @@
package com.ning.billing.account.dao;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
-import com.ning.billing.account.glue.AccountModuleMock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+
import org.apache.commons.io.IOUtils;
import org.skife.jdbi.v2.IDBI;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
-import java.io.IOException;
-
-import static org.testng.Assert.fail;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.account.glue.AccountModuleWithEmbeddedDb;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.BusService;
public abstract class AccountDaoTestBase {
- protected AccountModuleMock module;
+ protected AccountModuleWithEmbeddedDb module;
protected AccountDao accountDao;
protected IDBI dbi;
@@ -40,14 +41,12 @@ public abstract class AccountDaoTestBase {
protected void setup() throws IOException {
// Health check test to make sure MySQL is setup properly
try {
- module = new AccountModuleMock();
+ module = new AccountModuleWithEmbeddedDb();
final String accountDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
- final String invoiceDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
final String utilDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
module.startDb();
module.initDb(accountDdl);
- module.initDb(invoiceDdl);
module.initDb(utilDdl);
final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
@@ -56,8 +55,8 @@ public abstract class AccountDaoTestBase {
accountDao = injector.getInstance(AccountDao.class);
accountDao.test();
- EventBusService busService = injector.getInstance(EventBusService.class);
- ((DefaultEventBusService) busService).startBus();
+ BusService busService = injector.getInstance(BusService.class);
+ ((DefaultBusService) busService).startBus();
}
catch (Throwable t) {
fail(t.toString());
diff --git a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
new file mode 100644
index 0000000..b351709
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountChangeNotification;
+import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
+import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+public class MockAccountDao implements AccountDao {
+ private final Bus eventBus;
+ private final Map<String, Account> accounts = new ConcurrentHashMap<String, Account>();
+
+ @Inject
+ public MockAccountDao(Bus eventBus) {
+ this.eventBus = eventBus;
+ }
+
+ @Override
+ public void create(Account account) {
+ accounts.put(account.getId().toString(), account);
+
+ try {
+ eventBus.post(new DefaultAccountCreationEvent(account));
+ }
+ catch (EventBusException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public Account getById(String id) {
+ return accounts.get(id);
+ }
+
+ @Override
+ public List<Account> get() {
+ return new ArrayList<Account>(accounts.values());
+ }
+
+ @Override
+ public void test() {
+ }
+
+ @Override
+ public Account getAccountByKey(String key) {
+ for (Account account : accounts.values()) {
+ if (key.equals(account.getExternalKey())) {
+ return account;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public UUID getIdFromKey(String externalKey) {
+ Account account = getAccountByKey(externalKey);
+ return account == null ? null : account.getId();
+ }
+
+ @Override
+ public void update(Account account) {
+ Account currentAccount = accounts.put(account.getId().toString(), account);
+
+ AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+ if (changeEvent.hasChanges()) {
+ try {
+ eventBus.post(changeEvent);
+ }
+ catch (EventBusException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+ @Override
+ public void deleteByKey(String externalKey) throws AccountApiException {
+ for (Account account : accounts.values()) {
+ if (externalKey.equals(account.getExternalKey())) {
+ accounts.remove(account.getId());
+ }
+ }
+ }
+}
diff --git a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
index a4b0b98..1c118a9 100644
--- a/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
+++ b/account/src/test/java/com/ning/billing/account/dao/TestSimpleAccountDao.java
@@ -16,45 +16,60 @@
package com.ning.billing.account.dao;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
import java.util.List;
import java.util.UUID;
+
import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.testng.annotations.Test;
+
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountData;
import com.ning.billing.account.api.DefaultAccount;
-import com.ning.billing.util.tag.DefaultTagDescription;
-import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDescription;
import com.ning.billing.account.api.user.AccountBuilder;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.clock.DefaultClock;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
+import com.ning.billing.util.tag.DefaultTagDefinition;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
@Test(groups = {"account-dao"})
public class TestSimpleAccountDao extends AccountDaoTestBase {
- private final String key = "test1234";
- private final String firstName = "Wesley";
- private final String email = "me@me.com";
-
private DefaultAccount createTestAccount() {
- String thisKey = key + UUID.randomUUID().toString();
+ String thisKey = "test" + UUID.randomUUID().toString();
String lastName = UUID.randomUUID().toString();
- String thisEmail = email + " " + UUID.randomUUID();
+ String thisEmail = "me@me.com" + " " + UUID.randomUUID();
+ String firstName = "Bob";
String name = firstName + " " + lastName;
String phone = "123-456-7890";
+ String locale = "EN-US";
+ DateTimeZone timeZone = DateTimeZone.forID("America/Los_Angeles");
+
+ DateTime createdDate = new DateTime(DateTimeZone.UTC);
+ DateTime updatedDate = new DateTime(DateTimeZone.UTC);
int firstNameLength = firstName.length();
- return new AccountBuilder().externalKey(thisKey).name(name).phone(phone).firstNameLength(firstNameLength)
- .email(thisEmail).currency(Currency.USD).build();
+ return new AccountBuilder().externalKey(thisKey)
+ .name(name)
+ .phone(phone)
+ .firstNameLength(firstNameLength)
+ .email(thisEmail)
+ .currency(Currency.USD)
+ .locale(locale)
+ .timeZone(timeZone)
+ .createdDate(createdDate)
+ .updatedDate(updatedDate)
+ .build();
}
- public void testBasic() {
+ public void testBasic() throws AccountApiException {
Account a = createTestAccount();
accountDao.create(a);
@@ -93,7 +108,7 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
}
@Test
- public void testCustomFields() {
+ public void testCustomFields() throws AccountApiException {
Account account = createTestAccount();
String fieldName = "testField1";
String fieldValue = "testField1_value";
@@ -108,15 +123,15 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
}
@Test
- public void testTags() {
+ public void testTags() throws AccountApiException {
Account account = createTestAccount();
- TagDescription description = new DefaultTagDescription("Test Tag", "For testing only", true, true, "Test System", new DateTime());
- TagDescriptionDao tagDescriptionDao = dbi.onDemand(TagDescriptionDao.class);
- tagDescriptionDao.create(description);
+ TagDefinition definition = new DefaultTagDefinition("Test Tag", "For testing only", "Test System", new DateTime());
+ TagDefinitionSqlDao tagDescriptionDao = dbi.onDemand(TagDefinitionSqlDao.class);
+ tagDescriptionDao.create(definition);
String addedBy = "testTags()";
DateTime dateAdded = new DefaultClock().getUTCNow();
- account.addTag(description, addedBy, dateAdded);
+ account.addTag(definition, addedBy, dateAdded);
assertEquals(account.getTagList().size(), 1);
accountDao.create(account);
@@ -124,25 +139,31 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
List<Tag> tagList = thisAccount.getTagList();
assertEquals(tagList.size(), 1);
Tag tag = tagList.get(0);
- assertEquals(tag.getName(), description.getName());
- assertEquals(tag.getGenerateInvoice(), description.getGenerateInvoice());
- assertEquals(tag.getProcessPayment(), description.getProcessPayment());
- assertEquals(tag.getTagDescriptionId(), description.getId());
+ assertEquals(tag.getTagDefinitionName(), definition.getName());
assertEquals(tag.getAddedBy(), addedBy);
- assertEquals(tag.getDateAdded().compareTo(dateAdded), 0);
+ assertEquals(tag.getAddedDate().compareTo(dateAdded), 0);
}
@Test
- public void testGetIdFromKey() {
+ public void testGetIdFromKey() throws AccountApiException {
Account account = createTestAccount();
accountDao.create(account);
- UUID accountId = accountDao.getIdFromKey(account.getExternalKey());
- assertEquals(accountId, account.getId());
+ try {
+ UUID accountId = accountDao.getIdFromKey(account.getExternalKey());
+ assertEquals(accountId, account.getId());
+ } catch (AccountApiException a) {
+ fail("Retrieving account failed.");
+ }
+ }
+
+ @Test(expectedExceptions = AccountApiException.class)
+ public void testGetIdFromKeyForNullKey() throws AccountApiException {
+ accountDao.getIdFromKey(null);
}
@Test
- public void testUpdate() throws AccountApiException {
+ public void testUpdate() throws Exception {
final Account account = createTestAccount();
accountDao.create(account);
@@ -151,34 +172,84 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
public String getExternalKey() {
return account.getExternalKey();
}
+
@Override
public String getName() {
return "Jane Doe";
}
+
@Override
public int getFirstNameLength() {
return 4;
}
+
@Override
public String getEmail() {
return account.getEmail();
}
+
@Override
public String getPhone() {
return account.getPhone();
}
+
@Override
public int getBillCycleDay() {
return account.getBillCycleDay();
}
+
@Override
public Currency getCurrency() {
return account.getCurrency();
}
+
@Override
public String getPaymentProviderName() {
return account.getPaymentProviderName();
}
+ @Override
+ public DateTimeZone getTimeZone() {
+ return DateTimeZone.forID("Australia/Darwin");
+ }
+
+ @Override
+ public String getLocale() {
+ return "FR-CA";
+ }
+ @Override
+ public String getAddress1() {
+ return null;
+ }
+
+ @Override
+ public String getAddress2() {
+ return null;
+ }
+
+ @Override
+ public String getCompanyName() {
+ return null;
+ }
+
+ @Override
+ public String getCity() {
+ return null;
+ }
+
+ @Override
+ public String getStateOrProvince() {
+ return null;
+ }
+
+ @Override
+ public String getPostalCode() {
+ return null;
+ }
+
+ @Override
+ public String getCountry() {
+ return null;
+ }
};
Account updatedAccount = new DefaultAccount(account.getId(), accountData);
@@ -189,10 +260,124 @@ public class TestSimpleAccountDao extends AccountDaoTestBase {
assertNotNull(savedAccount);
assertEquals(savedAccount.getName(), updatedAccount.getName());
assertEquals(savedAccount.getEmail(), updatedAccount.getEmail());
- assertEquals(savedAccount.getPhone(), updatedAccount.getPhone());
assertEquals(savedAccount.getPaymentProviderName(), updatedAccount.getPaymentProviderName());
assertEquals(savedAccount.getBillCycleDay(), updatedAccount.getBillCycleDay());
assertEquals(savedAccount.getFirstNameLength(), updatedAccount.getFirstNameLength());
+ assertEquals(savedAccount.getTimeZone(), updatedAccount.getTimeZone());
+ assertEquals(savedAccount.getLocale(), updatedAccount.getLocale());
+ assertEquals(savedAccount.getAddress1(), updatedAccount.getAddress1());
+ assertEquals(savedAccount.getAddress2(), updatedAccount.getAddress2());
+ assertEquals(savedAccount.getCity(), updatedAccount.getCity());
+ assertEquals(savedAccount.getStateOrProvince(), updatedAccount.getStateOrProvince());
+ assertEquals(savedAccount.getCountry(), updatedAccount.getCountry());
+ assertEquals(savedAccount.getPostalCode(), updatedAccount.getPostalCode());
+ assertEquals(savedAccount.getPhone(), updatedAccount.getPhone());
+ }
+
+ @Test
+ public void testAddingContactInformation() throws Exception {
+ UUID accountId = UUID.randomUUID();
+ DefaultAccount account = new DefaultAccount(accountId, "extKey123456", "myemail123456@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+ null, null, null, null, null, null, null, null,null,null);
+ accountDao.create(account);
+
+ String address1 = "123 address 1";
+ String address2 = "456 address 2";
+ String companyName = "Some Company";
+ String city = "Cambridge Bay";
+ String stateOrProvince = "Nunavut";
+ String country = "Canada";
+ String postalCode = "X0B 0C0";
+ String phone = "18001112222";
+
+ DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey123456", "myemail123456@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+ address1, address2, companyName, city, stateOrProvince, country,
+ postalCode, phone, null,null);
+
+ accountDao.update(updatedAccount);
+
+ Account savedAccount = accountDao.getById(accountId.toString());
+
+ assertNotNull(savedAccount);
+ assertEquals(savedAccount.getId(), accountId);
+ assertEquals(savedAccount.getAddress1(), address1);
+ assertEquals(savedAccount.getAddress2(), address2);
+ assertEquals(savedAccount.getCompanyName(), companyName);
+ assertEquals(savedAccount.getCity(), city);
+ assertEquals(savedAccount.getStateOrProvince(), stateOrProvince);
+ assertEquals(savedAccount.getCity(), city);
+ assertEquals(savedAccount.getPostalCode(), postalCode);
+ assertEquals(savedAccount.getPhone(), phone);
+ }
+
+ @Test
+ public void testRemovingContactInformation() throws Exception {
+ UUID accountId = UUID.randomUUID();
+
+ DefaultAccount account = new DefaultAccount(accountId, "extKey654321", "myemail654321@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+ "123 address 1", "456 address 2", null, "Cambridge Bay",
+ "Nunavut", "Canada", "X0B 0C0", "18001112222", null, null);
+ accountDao.create(account);
+ DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey654321", "myemail654321@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ DateTimeZone.forID("America/Cambridge_Bay"), "EN-CA",
+ null, null, null, null, null, null, null, null, null, null);
+
+ accountDao.update(updatedAccount);
+
+ Account savedAccount = accountDao.getById(accountId.toString());
+
+ assertNotNull(savedAccount);
+ assertEquals(savedAccount.getId(), accountId);
+ assertEquals(savedAccount.getAddress1(), null);
+ assertEquals(savedAccount.getAddress2(), null);
+ assertEquals(savedAccount.getCompanyName(), null);
+ assertEquals(savedAccount.getCity(), null);
+ assertEquals(savedAccount.getStateOrProvince(), null);
+ assertEquals(savedAccount.getCity(), null);
+ assertEquals(savedAccount.getPostalCode(), null);
+ assertEquals(savedAccount.getPhone(), null);
+ }
+
+ @Test(expectedExceptions = AccountApiException.class)
+ public void testExternalKeyCannotBeUpdated() throws Exception {
+ UUID accountId = UUID.randomUUID();
+ String originalExternalKey = "extKey1337";
+
+ DefaultAccount account = new DefaultAccount(accountId, originalExternalKey, "myemail1337@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ null, null, null, null, null, null, null, null, null, null, null, null);
+ accountDao.create(account);
+
+ DefaultAccount updatedAccount = new DefaultAccount(accountId, "extKey1338", "myemail1337@glam.com",
+ "John Smith", 4, Currency.USD, 15, null,
+ null, null, null, null, null, null, null, null, null, null,null, null);
+ accountDao.update(updatedAccount);
}
+
+ @Test(groups={"slow"},enabled=true)
+ public void testDelete() throws AccountApiException {
+
+ Account a = createTestAccount();
+ accountDao.create(a);
+ String key = a.getExternalKey();
+
+ Account r = accountDao.getAccountByKey(key);
+ assertNotNull(r);
+ assertEquals(r.getExternalKey(), a.getExternalKey());
+
+ accountDao.deleteByKey(key);
+
+ Account s = accountDao.getAccountByKey(key);
+ assertTrue(s==null);
+
+ }
+
}
diff --git a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
new file mode 100644
index 0000000..1c65c3c
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.glue;
+
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.util.clock.MockClockModule;
+
+public class AccountModuleWithMocks extends AccountModule {
+ @Override
+ protected void installAccountDao() {
+ bind(MockAccountDao.class).asEagerSingleton();
+ bind(AccountDao.class).to(MockAccountDao.class);
+ }
+
+
+ @Override
+ protected void configure() {
+ super.configure();
+ install(new MockClockModule());
+ }
+}
analytics/pom.xml 2(+1 -1)
diff --git a/analytics/pom.xml b/analytics/pom.xml
index 278348b..9ee654b 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-analytics</artifactId>
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
index aaf75a1..7557045 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccount.java
@@ -213,7 +213,7 @@ public class BusinessAccount
if (key != null ? !key.equals(that.key) : that.key != null) {
return false;
}
- if (lastInvoiceDate != null ? !lastInvoiceDate.equals(that.lastInvoiceDate) : that.lastInvoiceDate != null) {
+ if (lastInvoiceDate != null ? lastInvoiceDate.compareTo(that.lastInvoiceDate) != 0 : that.lastInvoiceDate != null) {
return false;
}
if (lastPaymentStatus != null ? !lastPaymentStatus.equals(that.lastPaymentStatus) : that.lastPaymentStatus != null) {
@@ -228,7 +228,7 @@ public class BusinessAccount
if (totalInvoiceBalance != null ? !(Rounder.round(totalInvoiceBalance) == Rounder.round(that.totalInvoiceBalance)) : that.totalInvoiceBalance != null) {
return false;
}
- if (updatedDt != null ? !updatedDt.equals(that.updatedDt) : that.updatedDt != null) {
+ if (updatedDt != null ? updatedDt.compareTo(that.updatedDt) != 0 : that.updatedDt != null) {
return false;
}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
index 23fbcf4..f7081c7 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessAccountRecorder.java
@@ -50,7 +50,7 @@ public class BusinessAccountRecorder
final List<String> tags = new ArrayList<String>();
for (final Tag tag : account.getTagList()) {
- tags.add(tag.getName());
+ tags.add(tag.getTagDefinitionName());
}
// TODO Need payment and invoice api to fill most fields
diff --git a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
index baacad8..71e3402 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/BusinessSubscription.java
@@ -17,7 +17,14 @@
package com.ning.billing.analytics;
import com.ning.billing.analytics.utils.Rounder;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.entitlement.api.user.Subscription;
import org.joda.time.DateTime;
import org.slf4j.Logger;
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
index 0a66205..a2d6b2e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountDaoProvider.java
@@ -16,16 +16,16 @@
package com.ning.billing.analytics.dao;
+import org.skife.jdbi.v2.IDBI;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.skife.jdbi.v2.DBI;
public class BusinessAccountDaoProvider implements Provider<BusinessAccountDao>
{
- private final DBI dbi;
+ private final IDBI dbi;
@Inject
- public BusinessAccountDaoProvider(final DBI dbi)
+ public BusinessAccountDaoProvider(final IDBI dbi)
{
this.dbi = dbi;
}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
index 9db1b75..374f296 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionBinder.java
@@ -23,7 +23,11 @@ import org.skife.jdbi.v2.sqlobject.Binder;
import org.skife.jdbi.v2.sqlobject.BinderFactory;
import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
-import java.lang.annotation.*;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.sql.Types;
@BindingAnnotation(BusinessSubscriptionTransitionBinder.BstBinderFactory.class)
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
index 86b5665..0890f84 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessSubscriptionTransitionDaoProvider.java
@@ -16,16 +16,16 @@
package com.ning.billing.analytics.dao;
+import org.skife.jdbi.v2.IDBI;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import org.skife.jdbi.v2.DBI;
public class BusinessSubscriptionTransitionDaoProvider implements Provider<BusinessSubscriptionTransitionDao>
{
- private final DBI dbi;
+ private final IDBI dbi;
@Inject
- public BusinessSubscriptionTransitionDaoProvider(final DBI dbi)
+ public BusinessSubscriptionTransitionDaoProvider(final IDBI dbi)
{
this.dbi = dbi;
}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
index 59fc392..1b128b0 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
@@ -21,8 +21,8 @@ import com.google.inject.AbstractModule;
import com.ning.billing.analytics.AnalyticsListener;
import com.ning.billing.analytics.BusinessAccountRecorder;
import com.ning.billing.analytics.BusinessSubscriptionTransitionRecorder;
+import com.ning.billing.analytics.api.DefaultAnalyticsService;
import com.ning.billing.analytics.api.AnalyticsService;
-import com.ning.billing.analytics.api.IAnalyticsService;
import com.ning.billing.analytics.dao.BusinessAccountDao;
import com.ning.billing.analytics.dao.BusinessAccountDaoProvider;
import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionDao;
@@ -40,6 +40,6 @@ public class AnalyticsModule extends AbstractModule
bind(BusinessAccountRecorder.class).asEagerSingleton();
bind(AnalyticsListener.class).asEagerSingleton();
- bind(IAnalyticsService.class).to(AnalyticsService.class).asEagerSingleton();
+ bind(AnalyticsService.class).to(DefaultAnalyticsService.class).asEagerSingleton();
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
index 6ec95b9..25496b5 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/AnalyticsTestModule.java
@@ -16,16 +16,18 @@
package com.ning.billing.analytics;
+import org.skife.jdbi.v2.IDBI;
import com.ning.billing.account.glue.AccountModule;
import com.ning.billing.analytics.setup.AnalyticsModule;
import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.entitlement.glue.EntitlementModule;
-import com.ning.billing.util.glue.EventBusModule;
+
+import com.ning.billing.util.glue.BusModule;
+
+import com.ning.billing.util.glue.ClockModule;
import com.ning.billing.util.glue.NotificationQueueModule;
import com.ning.billing.util.glue.TagStoreModule;
-import org.skife.jdbi.v2.DBI;
-import org.skife.jdbi.v2.IDBI;
public class AnalyticsTestModule extends AnalyticsModule
{
@@ -37,16 +39,16 @@ public class AnalyticsTestModule extends AnalyticsModule
// Need to configure a few more things for the EventBus
install(new AccountModule());
install(new CatalogModule());
- install(new EventBusModule());
+ install(new BusModule());
install(new EntitlementModule());
+ install(new ClockModule());
install(new TagStoreModule());
install(new NotificationQueueModule());
// Install the Dao layer
final MysqlTestingHelper helper = new MysqlTestingHelper();
bind(MysqlTestingHelper.class).toInstance(helper);
- final DBI dbi = helper.getDBI();
+ final IDBI dbi = helper.getDBI();
bind(IDBI.class).toInstance(dbi);
- bind(DBI.class).toInstance(dbi);
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index c146de7..6054529 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -47,11 +47,11 @@ import com.ning.billing.entitlement.api.user.SubscriptionTransition;
import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.tag.DefaultTag;
-import com.ning.billing.util.tag.DefaultTagDescription;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.tag.DescriptiveTag;
+import com.ning.billing.util.tag.DefaultTagDefinition;
import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
@@ -74,8 +74,8 @@ public class TestAnalyticsService
{
private static final String KEY = "12345";
private static final String ACCOUNT_KEY = "pierre-12345";
- private static final DefaultTagDescription TAG_ONE = new DefaultTagDescription("batch20", "something", false, false, "pierre", new DateTime(DateTimeZone.UTC));
- private static final DefaultTagDescription TAG_TWO = new DefaultTagDescription("awesome", "something", false, false, "pierre", new DateTime(DateTimeZone.UTC));
+ private static final DefaultTagDefinition TAG_ONE = new DefaultTagDefinition("batch20", "something", "pierre", new DateTime(DateTimeZone.UTC));
+ private static final DefaultTagDefinition TAG_TWO = new DefaultTagDefinition("awesome", "something", "pierre", new DateTime(DateTimeZone.UTC));
@Inject
private AccountUserApi accountApi;
@@ -84,13 +84,13 @@ public class TestAnalyticsService
private EntitlementUserApi entitlementApi;
@Inject
- private TagDescriptionDao tagDao;
+ private TagDefinitionSqlDao tagDao;
@Inject
- private AnalyticsService service;
+ private DefaultAnalyticsService service;
@Inject
- private EventBus bus;
+ private Bus bus;
@Inject
private BusinessSubscriptionTransitionDao subscriptionDao;
@@ -118,8 +118,8 @@ public class TestAnalyticsService
final MockAccount account = new MockAccount(UUID.randomUUID(), ACCOUNT_KEY, Currency.USD);
try {
List<Tag> tags = new ArrayList<Tag>();
- tags.add(new DefaultTag(TAG_ONE, "pierre", new DateTime(DateTimeZone.UTC)));
- tags.add(new DefaultTag(TAG_TWO, "pierre", new DateTime(DateTimeZone.UTC)));
+ tags.add(new DescriptiveTag(TAG_ONE, "pierre", new DateTime(DateTimeZone.UTC)));
+ tags.add(new DescriptiveTag(TAG_TWO, "pierre", new DateTime(DateTimeZone.UTC)));
final Account storedAccount = accountApi.createAccount(account, null, tags);
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/MockBusinessAccountDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/MockBusinessAccountDao.java
new file mode 100644
index 0000000..f3dead0
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/MockBusinessAccountDao.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.analytics.dao;
+
+import com.ning.billing.analytics.BusinessAccount;
+
+public class MockBusinessAccountDao implements BusinessAccountDao {
+
+ @Override
+ public BusinessAccount getAccount(String key) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public int createAccount(BusinessAccount account) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int saveAccount(BusinessAccount account) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public void test() {
+ }
+}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
index 368b8c1..c17cdd4 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/TestAnalyticsDao.java
@@ -16,9 +16,21 @@
package com.ning.billing.analytics.dao;
-import com.ning.billing.analytics.*;
+import com.ning.billing.analytics.BusinessAccount;
+import com.ning.billing.analytics.BusinessSubscription;
+import com.ning.billing.analytics.BusinessSubscriptionEvent;
+import com.ning.billing.analytics.BusinessSubscriptionTransition;
+import com.ning.billing.analytics.MockDuration;
+import com.ning.billing.analytics.MockPhase;
+import com.ning.billing.analytics.MockPlan;
+import com.ning.billing.analytics.MockProduct;
import com.ning.billing.analytics.utils.Rounder;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.entitlement.api.user.Subscription;
import org.apache.commons.io.IOUtils;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
index 3b38c8c..d57de25 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockAccount.java
@@ -16,18 +16,18 @@
package com.ning.billing.analytics;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
-
-import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
+
+import org.apache.commons.lang.NotImplementedException;
import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountData;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDescription;
+import com.ning.billing.util.tag.TagDefinition;
public class MockAccount implements Account
{
@@ -88,6 +88,51 @@ public class MockAccount implements Account
}
@Override
+ public DateTimeZone getTimeZone() {
+ return DateTimeZone.forID("Pacific/Fiji");
+ }
+
+ @Override
+ public String getLocale() {
+ return "EN-US";
+ }
+
+ @Override
+ public String getAddress1() {
+ return null;
+ }
+
+ @Override
+ public String getAddress2() {
+ return null;
+ }
+
+ @Override
+ public String getCompanyName() {
+ return null;
+ }
+
+ @Override
+ public String getCity() {
+ return null;
+ }
+
+ @Override
+ public String getStateOrProvince() {
+ return null;
+ }
+
+ @Override
+ public String getPostalCode() {
+ return null;
+ }
+
+ @Override
+ public String getCountry() {
+ return null;
+ }
+
+ @Override
public UUID getId()
{
return id;
@@ -134,7 +179,7 @@ public class MockAccount implements Account
}
@Override
- public void addTag(TagDescription description, String addedBy, DateTime dateAdded) {
+ public void addTag(TagDefinition definition, String addedBy, DateTime dateAdded) {
throw new NotImplementedException();
}
@@ -149,7 +194,7 @@ public class MockAccount implements Account
}
@Override
- public void removeTag(TagDescription description) {
+ public void removeTag(TagDefinition definition) {
throw new NotImplementedException();
}
@@ -164,7 +209,13 @@ public class MockAccount implements Account
}
@Override
- public BigDecimal getBalance() {
- return BigDecimal.ZERO;
+ public DateTime getCreatedDate() {
+ return new DateTime(DateTimeZone.UTC);
}
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return new DateTime(DateTimeZone.UTC);
+ }
+
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
index 2012995..f10ff2f 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockDuration.java
@@ -18,6 +18,8 @@ package com.ning.billing.analytics;
import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.TimeUnit;
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
public class MockDuration
{
@@ -36,6 +38,11 @@ public class MockDuration
{
return 1;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ throw new NotImplementedException();
+ }
};
}
@@ -54,6 +61,11 @@ public class MockDuration
{
return 1;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ throw new NotImplementedException();
+ }
};
}
@@ -72,6 +84,11 @@ public class MockDuration
{
return 1;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ throw new NotImplementedException();
+ }
};
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
index 791d191..c6d2369 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIAccountUserApi.java
@@ -16,16 +16,17 @@
package com.ning.billing.analytics;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
-
import java.util.List;
import java.util.UUID;
+
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountData;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.account.api.DefaultAccount;
+import com.ning.billing.account.api.MigrationAccountData;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.tag.Tag;
@@ -33,11 +34,13 @@ public class MockIAccountUserApi implements AccountUserApi
{
private final AccountData account;
private final UUID id;
+ private Clock clock;
- public MockIAccountUserApi(final String accountKey, final Currency currency)
+ public MockIAccountUserApi(final String accountKey, final Currency currency, final Clock clock)
{
this.id = UUID.randomUUID();
account = new MockAccount(id, accountKey, currency);
+ this.clock = clock;
}
@Override
@@ -59,7 +62,7 @@ public class MockIAccountUserApi implements AccountUserApi
@Override
public Account getAccountById(final UUID uid) {
- return new DefaultAccount(account);
+ return new DefaultAccount(account, clock.getUTCNow());
}
@Override
@@ -72,4 +75,22 @@ public class MockIAccountUserApi implements AccountUserApi
public UUID getIdFromKey(String externalKey) {
return id;
}
+
+ @Override
+ public void deleteAccountByKey(String externalKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Account migrateAccount(MigrationAccountData data,
+ List<CustomField> fields, List<Tag> tags)
+ throws AccountApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void updateAccount(String key, AccountData accountData)
+ throws AccountApiException {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
index 12b6f77..bde1bcb 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockIEntitlementUserApi.java
@@ -115,4 +115,9 @@ public class MockIEntitlementUserApi implements EntitlementUserApi
public SubscriptionBundle getBundleForKey(String bundleKey) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public DateTime getNextBillingDate(UUID account) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
index 3f11c0e..10b5c70 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPhase.java
@@ -16,10 +16,16 @@
package com.ning.billing.analytics;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Price;
import java.math.BigDecimal;
-import java.util.Date;
public class MockPhase implements PlanPhase
{
@@ -53,6 +59,11 @@ public class MockPhase implements PlanPhase
return BigDecimal.valueOf(price);
}
+ @Override
+ public boolean isZero() {
+ return price == 0.0;
+ }
+
};
}
@@ -72,7 +83,11 @@ public class MockPhase implements PlanPhase
{
return BigDecimal.valueOf(price);
}
-
+
+ @Override
+ public boolean isZero() {
+ return price == 0.0;
+ }
};
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
index 72ce7ca..d611eee 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockPlan.java
@@ -18,6 +18,7 @@ package com.ning.billing.analytics;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.Product;
@@ -25,6 +26,8 @@ import com.ning.billing.catalog.api.Product;
import java.util.Date;
import java.util.Iterator;
+import org.joda.time.DateTime;
+
public class MockPlan implements Plan
{
private final String name;
@@ -98,4 +101,10 @@ public class MockPlan implements Plan
public boolean isRetired() {
return false;
}
+
+ @Override
+ public DateTime dateOfFirstRecurringNonZeroCharge(
+ DateTime subscriptionStartDate) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
index 6420325..f592c41 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/MockSubscription.java
@@ -133,8 +133,24 @@ public class MockSubscription implements Subscription
public List<SubscriptionTransition> getAllTransitions() {
throw new UnsupportedOperationException();
}
-
+
+ @Override
public SubscriptionTransition getPendingTransition() {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public DateTime getChargedThroughDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DateTime getPaidThroughDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionTransition getPreviousTransition() {
+ return null;
+ }
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
index 7bad790..ec6d9ae 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestAnalyticsListener.java
@@ -16,18 +16,27 @@
package com.ning.billing.analytics;
-import com.ning.billing.catalog.api.*;
+
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
+import com.ning.billing.util.clock.ClockMock;
+
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import java.util.UUID;
public class TestAnalyticsListener
{
@@ -48,7 +57,7 @@ public class TestAnalyticsListener
@BeforeMethod(alwaysRun = true)
public void setUp() throws Exception
{
- final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, new MockIEntitlementUserApi(bundleUUID, KEY), new MockIAccountUserApi(ACCOUNT_KEY, CURRENCY));
+ final BusinessSubscriptionTransitionRecorder recorder = new BusinessSubscriptionTransitionRecorder(dao, new MockIEntitlementUserApi(bundleUUID, KEY), new MockIAccountUserApi(ACCOUNT_KEY, CURRENCY, new ClockMock()));
listener = new AnalyticsListener(recorder, null);
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
index 5bdba44..fcf7405 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessAccount.java
@@ -34,14 +34,14 @@ public class TestBusinessAccount
account = new BusinessAccount("pierre", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
}
- @Test(groups = "fast")
+ @Test(groups = "fast", enabled = false)
public void testEquals() throws Exception
{
Assert.assertSame(account, account);
Assert.assertEquals(account, account);
Assert.assertTrue(account.equals(account));
- final BusinessAccount otherAccount = new BusinessAccount("pierre", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
+ final BusinessAccount otherAccount = new BusinessAccount("pierre cardin", BigDecimal.ONE, Collections.singletonList("batch15"), new DateTime(), BigDecimal.TEN, "ERROR_NOT_ENOUGH_FUNDS", "CreditCard", "Visa", "");
Assert.assertFalse(account.equals(otherAccount));
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
index 6c38854..55c73ad 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscription.java
@@ -16,7 +16,12 @@
package com.ning.billing.analytics;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
@@ -94,7 +99,7 @@ public class TestBusinessSubscription
Assert.assertEquals(subscription, subscription);
Assert.assertTrue(subscription.equals(subscription));
- final Subscription otherIsubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
- Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherIsubscription, USD)));
+ final Subscription otherSubscription = new MockSubscription(Subscription.SubscriptionState.CANCELLED, plan, phase);
+ Assert.assertTrue(!subscription.equals(new BusinessSubscription(otherSubscription, USD)));
}
}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
index 5ea1950..a3ca56c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionEvent.java
@@ -16,7 +16,11 @@
package com.ning.billing.analytics;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
diff --git a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
index 592d20d..a8a954c 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/TestBusinessSubscriptionTransition.java
@@ -16,7 +16,11 @@
package com.ning.billing.analytics;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.Subscription;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
api/pom.xml 10(+9 -1)
diff --git a/api/pom.xml b/api/pom.xml
index dce65e7..ca5b331 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-api</artifactId>
@@ -50,6 +50,14 @@
<groupId>org.skife.config</groupId>
<artifactId>config-magic</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-core-asl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/api/src/main/java/com/ning/billing/account/api/Account.java b/api/src/main/java/com/ning/billing/account/api/Account.java
index 543f280..68909c3 100644
--- a/api/src/main/java/com/ning/billing/account/api/Account.java
+++ b/api/src/main/java/com/ning/billing/account/api/Account.java
@@ -16,10 +16,15 @@
package com.ning.billing.account.api;
-import java.math.BigDecimal;
+import org.joda.time.DateTime;
+
import com.ning.billing.util.customfield.CustomizableEntity;
import com.ning.billing.util.tag.Taggable;
public interface Account extends AccountData, CustomizableEntity, Taggable {
- public BigDecimal getBalance();
+
+ public DateTime getCreatedDate();
+
+ public DateTime getUpdatedDate();
+
}
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountApiException.java b/api/src/main/java/com/ning/billing/account/api/AccountApiException.java
index d9761b6..a6b1460 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountApiException.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountApiException.java
@@ -20,7 +20,9 @@ import com.ning.billing.BillingExceptionBase;
import com.ning.billing.ErrorCode;
public class AccountApiException extends BillingExceptionBase {
- public AccountApiException(Throwable cause, int code, final String msg) {
+ private static final long serialVersionUID = 1L;
+
+ public AccountApiException(Throwable cause, int code, final String msg) {
super(cause, code, msg);
}
@@ -31,4 +33,5 @@ public class AccountApiException extends BillingExceptionBase {
public AccountApiException(ErrorCode code, final Object... args) {
super(code, args);
}
+
}
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java b/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
index 9e5f254..4102447 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountChangeNotification.java
@@ -16,12 +16,12 @@
package com.ning.billing.account.api;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
import java.util.List;
import java.util.UUID;
-public interface AccountChangeNotification extends EventBusNotification {
+public interface AccountChangeNotification extends BusEvent {
public UUID getAccountId();
public List<ChangedField> getChangedFields();
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java b/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
index bc2c065..b30d290 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountCreationNotification.java
@@ -16,11 +16,11 @@
package com.ning.billing.account.api;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
import java.util.UUID;
-public interface AccountCreationNotification extends EventBusNotification {
+public interface AccountCreationNotification extends BusEvent {
public UUID getId();
public AccountData getData();
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountData.java b/api/src/main/java/com/ning/billing/account/api/AccountData.java
index 9b8f399..654400b 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountData.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountData.java
@@ -16,8 +16,9 @@
package com.ning.billing.account.api;
+import org.joda.time.DateTimeZone;
+
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.entity.Entity;
public interface AccountData {
@@ -29,11 +30,29 @@ public interface AccountData {
public String getEmail();
- public String getPhone();
-
public int getBillCycleDay();
public Currency getCurrency();
public String getPaymentProviderName();
+
+ public DateTimeZone getTimeZone();
+
+ public String getLocale();
+
+ public String getAddress1();
+
+ public String getAddress2();
+
+ public String getCompanyName();
+
+ public String getCity();
+
+ public String getStateOrProvince();
+
+ public String getPostalCode();
+
+ public String getCountry();
+
+ public String getPhone();
}
diff --git a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
index a535ada..05d8660 100644
--- a/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
+++ b/api/src/main/java/com/ning/billing/account/api/AccountUserApi.java
@@ -25,7 +25,16 @@ public interface AccountUserApi {
public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException;
- public void updateAccount(Account account);
+ public Account migrateAccount(MigrationAccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException;
+
+ /***
+ *
+ * Note: does not update the external key
+ * @param account
+ */
+ public void updateAccount(Account account) throws AccountApiException;
+
+ public void updateAccount(String key, AccountData accountData) throws AccountApiException;
public Account getAccountByKey(String key);
@@ -33,5 +42,7 @@ public interface AccountUserApi {
public List<Account> getAccounts();
- public UUID getIdFromKey(String externalKey);
+ public UUID getIdFromKey(String externalKey) throws AccountApiException;
+
+ public void deleteAccountByKey(String externalKey) throws AccountApiException;
}
diff --git a/api/src/main/java/com/ning/billing/account/api/ControlTagType.java b/api/src/main/java/com/ning/billing/account/api/ControlTagType.java
new file mode 100644
index 0000000..23c23ae
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/account/api/ControlTagType.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.api;
+
+public enum ControlTagType {
+ AUTO_BILLING_OFF("Suspends billing until removed.", true, false),
+ AUTO_INVOICING_OFF("Suspends invoicing until removed.", false, true);
+
+ private final String description;
+ private final boolean autoPaymentOff;
+ private final boolean autoInvoicingOff;
+
+ ControlTagType(final String description, final boolean autoPaymentOff, final boolean autoInvoicingOff) {
+ this.description = description;
+ this.autoPaymentOff = autoPaymentOff;
+ this.autoInvoicingOff = autoInvoicingOff;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public boolean autoPaymentOff() {
+ return this.autoPaymentOff;
+ }
+
+ public boolean autoInvoicingOff() {
+ return this.autoInvoicingOff;
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/account/api/MigrationAccountData.java b/api/src/main/java/com/ning/billing/account/api/MigrationAccountData.java
new file mode 100644
index 0000000..34f7023
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/account/api/MigrationAccountData.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.account.api;
+
+import org.joda.time.DateTime;
+
+public interface MigrationAccountData extends AccountData {
+
+ public DateTime getCreatedDate();
+
+ public DateTime getUpdatedDate();
+}
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Catalog.java b/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
index 3c04a74..2b3609a 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Catalog.java
@@ -24,11 +24,11 @@ public interface Catalog {
//
public abstract String getCatalogName();
- public abstract Currency[] getSupportedCurrencies(DateTime requestedDate);
+ public abstract Currency[] getSupportedCurrencies(DateTime requestedDate) throws CatalogApiException;
- public abstract Product[] getProducts(DateTime requestedDate);
+ public abstract Product[] getProducts(DateTime requestedDate) throws CatalogApiException;
- public abstract Plan[] getPlans(DateTime requestedDate);
+ public abstract Plan[] getPlans(DateTime requestedDate) throws CatalogApiException;
//
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Duration.java b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
index f9fe583..9025367 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Duration.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Duration.java
@@ -16,10 +16,13 @@
package com.ning.billing.catalog.api;
+import org.joda.time.DateTime;
+
public interface Duration {
public abstract TimeUnit getUnit();
public abstract int getNumber();
+ public DateTime addToDateTime(DateTime dateTime);
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java b/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
index 2876f1d..e047175 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/InternationalPrice.java
@@ -25,4 +25,6 @@ public interface InternationalPrice {
public abstract BigDecimal getPrice(Currency currency) throws CatalogApiException;
+ public abstract boolean isZero();
+
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/Plan.java b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
index c0dbce5..3abbfbe 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/Plan.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/Plan.java
@@ -19,6 +19,8 @@ package com.ning.billing.catalog.api;
import java.util.Date;
import java.util.Iterator;
+import org.joda.time.DateTime;
+
public interface Plan {
public abstract PlanPhase[] getInitialPhases();
@@ -42,4 +44,7 @@ public interface Plan {
public abstract Date getEffectiveDateForExistingSubscriptons();
public abstract PlanPhase findPhase(String name) throws CatalogApiException;
+
+ public abstract DateTime dateOfFirstRecurringNonZeroCharge(DateTime subscriptionStartDate);
+
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java b/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
index 0db7db5..c93fde8 100644
--- a/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
+++ b/api/src/main/java/com/ning/billing/catalog/api/StaticCatalog.java
@@ -25,13 +25,13 @@ public interface StaticCatalog {
//
public abstract String getCatalogName();
- public abstract Date getEffectiveDate();
+ public abstract Date getEffectiveDate() throws CatalogApiException;
- public abstract Currency[] getCurrentSupportedCurrencies();
+ public abstract Currency[] getCurrentSupportedCurrencies() throws CatalogApiException;
- public abstract Product[] getCurrentProducts();
+ public abstract Product[] getCurrentProducts() throws CatalogApiException;
- public abstract Plan[] getCurrentPlans();
+ public abstract Plan[] getCurrentPlans() throws CatalogApiException;
//
// Find a plan
diff --git a/api/src/main/java/com/ning/billing/config/EntitlementConfig.java b/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
index 42399ce..1b6f3e2 100644
--- a/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
+++ b/api/src/main/java/com/ning/billing/config/EntitlementConfig.java
@@ -33,7 +33,7 @@ public interface EntitlementConfig {
@Default("500")
public long getNotificationSleepTimeMs();
- @Config("killbill.entitlement.engine.events.off")
+ @Config("killbill.notifications.off")
@Default("false")
public boolean isEventProcessingOff();
}
diff --git a/api/src/main/java/com/ning/billing/config/InvoiceConfig.java b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
new file mode 100644
index 0000000..78cc02f
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/config/InvoiceConfig.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.config;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+
+public interface InvoiceConfig {
+
+ @Config("killbill.invoice.dao.claim.time")
+ @Default("60000")
+ public long getDaoClaimTimeMs();
+
+ @Config("killbill.invoice.dao.ready.max")
+ @Default("10")
+ public int getDaoMaxReadyEvents();
+
+ @Config("killbill.invoice.engine.notifications.sleep")
+ @Default("500")
+ public long getNotificationSleepTimeMs();
+
+ @Config("killbill.notifications.off")
+ @Default("false")
+ public boolean isEventProcessingOff();
+}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
index d3425bb..2628b36 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/BillingEvent.java
@@ -16,17 +16,14 @@
package com.ning.billing.entitlement.api.billing;
-import java.math.BigDecimal;
-
import org.joda.time.DateTime;
import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.CatalogApiException;
-import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.InternationalPrice;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
public interface BillingEvent extends Comparable<BillingEvent> {
@@ -93,19 +90,8 @@ public interface BillingEvent extends Comparable<BillingEvent> {
*/
public InternationalPrice getRecurringPrice();
- /**
- * Syntactic sugar to wrap currency access call
- *
- * @param currency
- * @return price value
- */
- public BigDecimal getRecurringPrice(Currency currency) throws CatalogApiException ;
-
- /**
- * Syntactic sugar to wrap currency access call
- *
- * @param currency
- * @return price value
- */
- public BigDecimal getFixedPrice(Currency currency) throws CatalogApiException ;
+ /**
+ * @return the transition type of the underlying subscription event that triggered this
+ */
+ public SubscriptionTransitionType getTransitionType();
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
index 2b07182..6dc3e94 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApi.java
@@ -16,13 +16,12 @@
package com.ning.billing.entitlement.api.billing;
-import java.util.List;
import java.util.SortedSet;
import java.util.UUID;
import org.joda.time.DateTime;
-
-import com.ning.billing.account.api.Account;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
public interface EntitlementBillingApi {
@@ -35,7 +34,10 @@ public interface EntitlementBillingApi {
*/
public SortedSet<BillingEvent> getBillingEventsForAccount(UUID accountId);
+ public UUID getAccountIdFromSubscriptionId(UUID subscriptionId);
public void setChargedThroughDate(UUID subscriptionId, DateTime ctd);
+ public void setChargedThroughDateFromTransaction(Transmogrifier transactionalDao, UUID subscriptionId, DateTime ctd);
+
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
index 9c22915..4c20f7c 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/billing/EntitlementBillingApiException.java
@@ -16,25 +16,21 @@
package com.ning.billing.entitlement.api.billing;
-public class EntitlementBillingApiException extends Exception {
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+public class EntitlementBillingApiException extends BillingExceptionBase {
private static final long serialVersionUID = 127392038L;
- public EntitlementBillingApiException() {
- super();
+ public EntitlementBillingApiException(Throwable cause, int code, final String msg) {
+ super(cause, code, msg);
}
- public EntitlementBillingApiException(String msg, Throwable arg1) {
- super(msg, arg1);
+ public EntitlementBillingApiException(Throwable cause, ErrorCode code, final Object... args) {
+ super(cause, code, args);
}
- public EntitlementBillingApiException(String msg) {
- super(msg);
+ public EntitlementBillingApiException(ErrorCode code, final Object... args) {
+ super(code, args);
}
-
- public EntitlementBillingApiException(Throwable msg) {
- super(msg);
- }
-
-
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
index 53854d1..28084b2 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/EntitlementService.java
@@ -18,7 +18,6 @@ package com.ning.billing.entitlement.api;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.test.EntitlementTestApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.lifecycle.KillbillService;
@@ -31,7 +30,5 @@ public interface EntitlementService extends KillbillService {
public EntitlementBillingApi getBillingApi();
- public EntitlementTestApi getTestApi();
-
public EntitlementMigrationApi getMigrationApi();
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
index 22b9830..1867cfe 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/EntitlementUserApi.java
@@ -42,4 +42,5 @@ public interface EntitlementUserApi {
public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate)
throws EntitlementUserApiException;
+ public DateTime getNextBillingDate(UUID account);
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
index d19de5f..5c626fc 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/Subscription.java
@@ -65,10 +65,16 @@ public interface Subscription {
public PlanPhase getCurrentPhase();
+ public DateTime getChargedThroughDate();
+
+ public DateTime getPaidThroughDate();
+
+
public List<SubscriptionTransition> getActiveTransitions();
public List<SubscriptionTransition> getAllTransitions();
public SubscriptionTransition getPendingTransition();
+ public SubscriptionTransition getPreviousTransition();
}
diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
index 26ce81f..f4c971d 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransition.java
@@ -19,12 +19,12 @@ package com.ning.billing.entitlement.api.user;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
-import com.ning.billing.util.eventbus.EventBusNotification;
+import com.ning.billing.util.bus.BusEvent;
import org.joda.time.DateTime;
import java.util.UUID;
-public interface SubscriptionTransition extends EventBusNotification {
+public interface SubscriptionTransition extends BusEvent {
public enum SubscriptionTransitionType {
MIGRATE_ENTITLEMENT,
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 0c60808..5482e0d 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -29,7 +29,8 @@ public enum ErrorCode {
*
*/
/* Generic through APIs */
- ENT_INVALID_REQUESTED_DATE(1001, "Requested in the future is not allowed : %s"),
+ ENT_INVALID_REQUESTED_FUTURE_DATE(1001, "Requested date %s in the future is not allowed"),
+ ENT_INVALID_REQUESTED_DATE(1001, "Requested date %s is not allowed to be prior to the previous transition %s"),
/* Creation */
ENT_CREATE_BAD_PHASE(1011, "Can't create plan initial phase %s"),
@@ -47,7 +48,10 @@ public enum ErrorCode {
ENT_CANCEL_BAD_STATE(1031, "Subscription %s is in state %s"),
/* Un-cancellation */
ENT_UNCANCEL_BAD_STATE(1070, "Subscription %s was not in a cancelled state"),
-
+ /* Fetch */
+ ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION(1080, "Could not find a bundle for subscription %s"),
+ ENT_GET_INVALID_BUNDLE_ID(1081, "Could not find a bundle matching id %s"),
+ ENT_INVALID_SUBSCRIPTION_ID(1082, "Unknown subscription %s"),
/*
*
* Range 2000 : CATALOG
@@ -70,7 +74,7 @@ public enum ErrorCode {
CAT_NO_PRICE_FOR_CURRENCY(2010, "This price does not have a value for the currency '%s'."),
/* Price value explicitly set to NULL meaning there is no price available in that currency */
- CAT_PRICE_VALUE_NULL_FOR_CURRENCY(2011, "The value for the currency '%s' is NULL. This plan cannot be bought in this currnency."),
+ CAT_PRICE_VALUE_NULL_FOR_CURRENCY(2011, "The value for the currency '%s' is NULL. This plan cannot be bought in this currency."),
CAT_NULL_PRICE_LIST_NAME(2012,"Price list name was null"),
CAT_PRICE_LIST_NOT_FOUND(2013, "Could not find a pricelist with name '%s'"),
/*
@@ -94,21 +98,45 @@ public enum ErrorCode {
*/
CAT_NO_CATALOG_FOR_GIVEN_DATE(2050, "There is no catalog version that applies for the given date '%s'"),
CAT_NO_CATALOG_ENTRIES_FOR_GIVEN_DATE(2051, "The are no catalog entries that apply for the given date '%s'"),
- CAT_CATALOG_NAME_MISMATCH(2052, "The catalog name '%s' does not match the name of the catalog we are trying to add '%s'"),
+ CAT_CATALOG_NAME_MISMATCH(2052, "The catalog name '%s' does not match the name of the catalog we are trying to add '%s'"),
/*
* Billing Alignment
*/
CAT_INVALID_BILLING_ALIGNMENT(2060, "Invalid billing alignment '%s'"),
-
+
/*
*
* Range 3000 : ACCOUNT
*
*/
ACCOUNT_ALREADY_EXISTS(3000, "Account already exists for key %s"),
- ACCOUNT_INVALID_NAME(3001, "An invalid name was specified when creating or updating an account.")
+ ACCOUNT_INVALID_NAME(3001, "An invalid name was specified when creating or updating an account."),
+ ACCOUNT_DOES_NOT_EXIST_FOR_ID(3002, "Account does not exist for id %s"),
+ ACCOUNT_DOES_NOT_EXIST_FOR_KEY(3003, "Account does not exist for key %s"),
+ ACCOUNT_CANNOT_MAP_NULL_KEY(3004, "An attempt was made to get the id for a <null> external key."),
+ ACCOUNT_CANNOT_CHANGE_EXTERNAL_KEY(3005, "External keys cannot be updated. Original key remains: %s"),
+
+ /*
+ *
+ * Range 3900: Tag definitions
+ *
+ */
+ TAG_DEFINITION_CONFLICTS_WITH_CONTROL_TAG(3900, "The tag definition name conflicts with a reserved name (name %s)"),
+ TAG_DEFINITION_ALREADY_EXISTS(3901, "The tag definition name already exists (name: %s)"),
+ TAG_DEFINITION_DOES_NOT_EXIST(3902, "The tag definition name does not exist (name: %s)"),
+ TAG_DEFINITION_IN_USE(3903, "The tag definition name is currently in use (name: %s)"),
+ /*
+ *
+ * Range 4000: INVOICE
+ *
+ */
+ INVOICE_ACCOUNT_ID_INVALID(4001, "No account could be retrieved for id %s"),
+ INVOICE_INVALID_TRANSITION(4002, "Transition did not contain a subscription id."),
+ INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID(4003, "No account id was retrieved for subscription id %s"),
+ INVOICE_INVALID_DATE_SEQUENCE(4004, "Date sequence was invalid. Start Date: %s; End Date: %s; Target Date: %s")
;
+
private int code;
private String format;
diff --git a/api/src/main/java/com/ning/billing/invoice/api/Invoice.java b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
index f69b934..4a61849 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/Invoice.java
@@ -25,14 +25,24 @@ import java.util.List;
import java.util.UUID;
public interface Invoice extends Entity {
- boolean add(InvoiceItem item);
+ boolean addInvoiceItem(InvoiceItem item);
- boolean add(List<InvoiceItem> items);
+ boolean addInvoiceItems(List<InvoiceItem> items);
- List<InvoiceItem> getItems();
+ List<InvoiceItem> getInvoiceItems();
+
+ List<InvoiceItem> getInvoiceItems(Class clazz);
int getNumberOfItems();
+ boolean addPayment(InvoicePayment payment);
+
+ boolean addPayments(List<InvoicePayment> payments);
+
+ List<InvoicePayment> getPayments();
+
+ int getNumberOfPayments();
+
UUID getAccountId();
DateTime getInvoiceDate();
@@ -47,7 +57,7 @@ public interface Invoice extends Entity {
BigDecimal getTotalAmount();
- BigDecimal getAmountOutstanding();
+ BigDecimal getBalance();
boolean isDueForPayment(DateTime targetDate, int numberOfDays);
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java
new file mode 100644
index 0000000..a9275e8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceApiException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class InvoiceApiException extends BillingExceptionBase {
+ public InvoiceApiException(Throwable cause, int code, final String msg) {
+ super(cause, code, msg);
+ }
+
+ public InvoiceApiException(Throwable cause, ErrorCode code, final Object... args) {
+ super(cause, code, args);
+ }
+
+ public InvoiceApiException(ErrorCode code, final Object... args) {
+ super(code, args);
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
index 89c0d87..478a1fb 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
@@ -16,17 +16,19 @@
package com.ning.billing.invoice.api;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.eventbus.EventBusNotification;
-import org.joda.time.DateTime;
-
import java.math.BigDecimal;
import java.util.UUID;
-public interface InvoiceCreationNotification extends EventBusNotification {
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.bus.BusEvent;
+
+public interface InvoiceCreationNotification extends BusEvent {
public UUID getInvoiceId();
public UUID getAccountId();
public BigDecimal getAmountOwed();
public Currency getCurrency();
public DateTime getInvoiceCreationDate();
+
}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
index 44c46a7..1abcf83 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItem.java
@@ -28,25 +28,19 @@ public interface InvoiceItem extends Entity, Comparable<InvoiceItem> {
UUID getSubscriptionId();
- DateTime getStartDate();
+ String getPlanName();
- DateTime getEndDate();
+ String getPhaseName();
String getDescription();
- BigDecimal getAmount();
-
- BigDecimal getRate();
-
- Currency getCurrency();
-
- InvoiceItem asCredit(UUID invoiceId);
+ DateTime getStartDate();
- int compareTo(InvoiceItem invoiceItem);
+ DateTime getEndDate();
- void subtract(InvoiceItem that);
+ BigDecimal getAmount();
- boolean duplicates(InvoiceItem that);
+ Currency getCurrency();
- boolean cancels(InvoiceItem that);
+ InvoiceItem asCredit();
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
new file mode 100644
index 0000000..b947762
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePayment.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.entity.Entity;
+
+public interface InvoicePayment {
+ UUID getPaymentAttemptId();
+
+ UUID getInvoiceId();
+
+ DateTime getPaymentAttemptDate();
+
+ BigDecimal getAmount();
+
+ Currency getCurrency();
+
+ DateTime getCreatedDate();
+
+ DateTime getUpdatedDate();
+}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
index bde12a9..641afa6 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
@@ -16,19 +16,28 @@
package com.ning.billing.invoice.api;
-import com.ning.billing.catalog.api.Currency;
-
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
+
import org.joda.time.DateTime;
-public interface InvoicePaymentApi {
- public void paymentSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentAttemptDate);
+import com.ning.billing.catalog.api.Currency;
- public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate);
+public interface InvoicePaymentApi {
public List<Invoice> getInvoicesByAccount(UUID accountId);
public Invoice getInvoice(UUID invoiceId);
+
+ public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId);
+
+ public InvoicePayment getInvoicePayment(UUID paymentAttemptId);
+
+ public void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
+
+ public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate);
+
+ public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate);
+
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
index 2c8d02e..363e483 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
@@ -19,19 +19,24 @@ package com.ning.billing.invoice.api;
import org.joda.time.DateTime;
import java.math.BigDecimal;
+import java.util.Collection;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.catalog.api.Currency;
public interface InvoiceUserApi {
public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays);
public List<Invoice> getInvoicesByAccount(UUID accountId);
+ public List<Invoice> getInvoicesByAccount(UUID accountId, DateTime fromDate);
+
+ public BigDecimal getAccountBalance(UUID accountId);
+
+ public List<InvoiceItem> getInvoiceItemsByAccount(UUID accountId);
+
public Invoice getInvoice(UUID invoiceId);
- public void paymentAttemptFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate);
+ public void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
- public void paymentAttemptSuccessful(UUID invoiceId, BigDecimal amount, Currency currency,
- UUID paymentId, DateTime paymentDate);
+ public Collection<Invoice> getUnpaidInvoicesByAccountId(UUID accountId, DateTime upToDate);
}
diff --git a/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java b/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java
new file mode 100644
index 0000000..9edcd8c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/invoice/api/test/InvoiceTestApi.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api.test;
+
+import com.ning.billing.invoice.api.Invoice;
+
+public interface InvoiceTestApi {
+ public void create(Invoice invoice);
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java
new file mode 100644
index 0000000..a9e25dc
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+
+public final class CreditCardPaymentMethodInfo extends PaymentMethodInfo {
+ public static final class Builder extends BuilderBase<CreditCardPaymentMethodInfo, Builder> {
+ private String cardHolderName;
+ private String cardType;
+ private String expirationDate;
+ private String maskNumber;
+ private String cardAddress1;
+ private String cardAddress2;
+ private String cardCity;
+ private String cardState;
+ private String cardPostalCode;
+ private String cardCountry;
+
+ public Builder() {
+ super(Builder.class);
+ }
+
+ public Builder(CreditCardPaymentMethodInfo src) {
+ super(Builder.class, src);
+ this.cardHolderName = src.cardHolderName;
+ this.cardType = src.cardType;
+ this.expirationDate = src.expirationDate;
+ this.cardAddress1 = src.cardAddress1;
+ this.cardAddress2 = src.cardAddress2;
+ this.cardCity = src.cardCity;
+ this.cardState = src.cardState;
+ this.cardPostalCode = src.cardPostalCode;
+ this.cardCountry = src.cardCountry;
+ this.maskNumber = src.maskNumber;
+ }
+
+ public Builder setCardHolderName(String cardHolderName) {
+ this.cardHolderName = cardHolderName;
+ return this;
+ }
+
+ public Builder setCardType(String cardType) {
+ this.cardType = cardType;
+ return this;
+ }
+
+ public Builder setExpirationDateStr(String expirationDateStr) {
+ this.expirationDate = expirationDateStr;
+ return this;
+ }
+
+ public Builder setCardAddress1(String creditCardAddress1) {
+ this.cardAddress1 = creditCardAddress1;
+ return this;
+ }
+
+ public Builder setCardAddress2(String creditCardAddress2) {
+ this.cardAddress2 = creditCardAddress2;
+ return this;
+ }
+
+ public Builder setCardCity(String creditCardCity) {
+ this.cardCity = creditCardCity;
+ return this;
+ }
+
+ public Builder setCardState(String creditCardState) {
+ this.cardState = creditCardState;
+ return this;
+ }
+
+ public Builder setCardPostalCode(String creditCardPostalCode) {
+ this.cardPostalCode = creditCardPostalCode;
+ return this;
+ }
+
+ public Builder setCardCountry(String creditCardCountry) {
+ this.cardCountry = creditCardCountry;
+ return this;
+ }
+
+ public Builder setMaskNumber(String maskNumber) {
+ this.maskNumber = maskNumber;
+ return this;
+ }
+
+ public CreditCardPaymentMethodInfo build() {
+ return new CreditCardPaymentMethodInfo(id,
+ accountId,
+ defaultMethod,
+ cardHolderName,
+ cardType,
+ expirationDate,
+ maskNumber,
+ cardAddress1,
+ cardAddress2,
+ cardCity,
+ cardState,
+ cardPostalCode,
+ cardCountry);
+ }
+ }
+
+ private final String cardHolderName;
+ private final String cardType;
+ private final String expirationDate;
+ private final String maskNumber;
+ private final String cardAddress1;
+ private final String cardAddress2;
+ private final String cardCity;
+ private final String cardState;
+ private final String cardPostalCode;
+ private final String cardCountry;
+
+ public CreditCardPaymentMethodInfo(String id,
+ String accountId,
+ Boolean defaultMethod,
+ String cardHolderName,
+ String cardType,
+ String expirationDate,
+ String maskNumber,
+ String cardAddress1,
+ String cardAddress2,
+ String cardCity,
+ String cardState,
+ String cardPostalCode,
+ String cardCountry) {
+
+ super(id, accountId, defaultMethod, "CreditCard");
+ this.cardHolderName = cardHolderName;
+ this.cardType = cardType;
+ this.expirationDate = expirationDate;
+ this.maskNumber = maskNumber;
+ this.cardAddress1 = cardAddress1;
+ this.cardAddress2 = cardAddress2;
+ this.cardCity = cardCity;
+ this.cardState = cardState;
+ this.cardPostalCode = cardPostalCode;
+ this.cardCountry = cardCountry;
+ }
+
+ public String getCardHolderName() {
+ return cardHolderName;
+ }
+
+ public String getCardType() {
+ return cardType;
+ }
+
+ public String getCardAddress1() {
+ return cardAddress1;
+ }
+
+ public String getCardAddress2() {
+ return cardAddress2;
+ }
+
+ public String getCardCity() {
+ return cardCity;
+ }
+
+ public String getCardState() {
+ return cardState;
+ }
+
+ public String getCardPostalCode() {
+ return cardPostalCode;
+ }
+
+ public String getCardCountry() {
+ return cardCountry;
+ }
+
+ public String getExpirationDate() {
+ return expirationDate;
+ }
+
+ public String getMaskNumber() {
+ return maskNumber;
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/Either.java b/api/src/main/java/com/ning/billing/payment/api/Either.java
new file mode 100644
index 0000000..25ce8f8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/Either.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import org.codehaus.jackson.annotate.JsonValue;
+
+public abstract class Either<T, V> {
+ public static <T, V> Either<T, V> left(T value) {
+ return new Left<T, V>(value);
+ }
+ public static <T, V> Either<T, V> right(V value) {
+ return new Right<T, V>(value);
+ }
+
+ private Either() {
+ }
+
+ public boolean isLeft() {
+ return false;
+ }
+ public boolean isRight() {
+ return false;
+ }
+ public T getLeft() {
+ throw new UnsupportedOperationException();
+ }
+ public V getRight() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static class Left<T, V> extends Either<T, V> {
+ private final T value;
+
+ public Left(T value) {
+ this.value = value;
+ }
+ @Override
+ public boolean isLeft() {
+ return true;
+ }
+ @Override
+ @JsonValue
+ public T getLeft() {
+ return value;
+ }
+ }
+
+ public static class Right<T, V> extends Either<T, V> {
+ private final V value;
+
+ public Right(V value) {
+ this.value = value;
+ }
+ @Override
+ public boolean isRight() {
+ return true;
+ }
+
+ @Override
+ @JsonValue
+ public V getRight() {
+ return value;
+ }
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
new file mode 100644
index 0000000..58e9898
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.account.api.Account;
+
+public interface PaymentApi {
+
+ Either<PaymentError, Void> updatePaymentGateway(String accountKey);
+
+ Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId);
+
+ Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+
+ Either<PaymentError, String> addPaymentMethod(@Nullable String accountKey, PaymentMethodInfo paymentMethod);
+
+ Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
+
+ Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
+
+ List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds);
+ List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds);
+
+ List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds); //TODO
+
+ Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+
+ Either<PaymentError, String> createPaymentProviderAccount(Account account);
+
+ Either<PaymentError, Void> updatePaymentProviderAccountContact(String accountKey);
+
+ PaymentAttempt getPaymentAttemptForPaymentId(String id);
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
new file mode 100644
index 0000000..b5df043
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.common.base.Objects;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+
+public class PaymentAttempt {
+ private final UUID paymentAttemptId;
+ private final UUID invoiceId;
+ private final UUID accountId;
+ private final BigDecimal amount;
+ private final Currency currency;
+ private final String paymentId;
+ private final DateTime invoiceDate;
+ private final DateTime paymentAttemptDate;
+ private final Integer retryCount;
+ private final DateTime nextRetryDate;
+ private final DateTime createdDate;
+ private final DateTime updatedDate;
+
+ public PaymentAttempt(UUID paymentAttemptId,
+ UUID invoiceId,
+ UUID accountId,
+ BigDecimal amount,
+ Currency currency,
+ DateTime invoiceDate,
+ DateTime paymentAttemptDate,
+ String paymentId,
+ Integer retryCount,
+ DateTime nextRetryDate,
+ DateTime createdDate,
+ DateTime updatedDate) {
+ this.paymentAttemptId = paymentAttemptId;
+ this.invoiceId = invoiceId;
+ this.accountId = accountId;
+ this.amount = amount;
+ this.currency = currency;
+ this.invoiceDate = invoiceDate;
+ this.paymentAttemptDate = paymentAttemptDate == null ? new DateTime(DateTimeZone.UTC) : paymentAttemptDate;
+ this.paymentId = paymentId;
+ this.retryCount = retryCount;
+ this.nextRetryDate = nextRetryDate;
+ this.createdDate = createdDate == null ? new DateTime(DateTimeZone.UTC) : createdDate;
+ this.updatedDate = updatedDate == null ? new DateTime(DateTimeZone.UTC) : updatedDate;
+ }
+
+ public PaymentAttempt(UUID paymentAttemptId,
+ UUID invoiceId,
+ UUID accountId,
+ BigDecimal amount,
+ Currency currency,
+ DateTime invoiceDate,
+ DateTime paymentAttemptDate,
+ String paymentId,
+ Integer retryCount,
+ DateTime nextRetryDate) {
+ this(paymentAttemptId,
+ invoiceId,
+ accountId,
+ amount,
+ currency,
+ invoiceDate,
+ paymentAttemptDate,
+ paymentId,
+ retryCount,
+ nextRetryDate,
+ null,
+ null);
+ }
+
+ public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, BigDecimal amount, Currency currency, DateTime invoiceDate, DateTime paymentAttemptDate) {
+ this(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, null, null, null);
+ }
+
+ public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime paymentAttemptDate) {
+ this(paymentAttemptId, invoiceId, accountId, null, null, invoiceDate, paymentAttemptDate, null, null, null);
+ }
+
+ public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+ this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getBalance(), invoice.getCurrency(), invoice.getInvoiceDate(), null, null, null, null);
+ }
+
+ public DateTime getInvoiceDate() {
+ return invoiceDate;
+ }
+
+ public UUID getPaymentAttemptId() {
+ return paymentAttemptId;
+ }
+
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ public DateTime getPaymentAttemptDate() {
+ return paymentAttemptDate;
+ }
+
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ public Integer getRetryCount() {
+ return retryCount;
+ }
+
+ public DateTime getNextRetryDate() {
+ return nextRetryDate;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", invoiceId=" + invoiceId + ", accountId=" + accountId + ", amount=" + amount + ", currency=" + currency + ", paymentId=" + paymentId + ", invoiceDate=" + invoiceDate + ", paymentAttemptDate=" + paymentAttemptDate + ", retryCount=" + retryCount + ", nextRetryDate=" + nextRetryDate + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + "]";
+ }
+
+ public Builder cloner() {
+ return new Builder(this);
+ }
+
+ public static class Builder {
+ private UUID paymentAttemptId;
+ private UUID invoiceId;
+ private UUID accountId;
+ private BigDecimal amount;
+ private Currency currency;
+ private DateTime invoiceDate;
+ private DateTime paymentAttemptDate;
+ private String paymentId;
+ private Integer retryCount;
+ private DateTime nextRetryDate;
+ private DateTime createdDate;
+ private DateTime updatedDate;
+
+ public Builder() {
+ }
+
+ public Builder(PaymentAttempt src) {
+ this.paymentAttemptId = src.paymentAttemptId;
+ this.invoiceId = src.invoiceId;
+ this.accountId = src.accountId;
+ this.amount = src.amount;
+ this.currency = src.currency;
+ this.invoiceDate = src.invoiceDate;
+ this.paymentAttemptDate = src.paymentAttemptDate;
+ this.paymentId = src.paymentId;
+ this.retryCount = src.retryCount;
+ this.nextRetryDate = src.nextRetryDate;
+ this.createdDate = src.createdDate;
+ this.updatedDate = src.updatedDate;
+ }
+
+ public Builder setPaymentAttemptId(UUID paymentAttemptId) {
+ this.paymentAttemptId = paymentAttemptId;
+ return this;
+ }
+
+ public Builder setInvoiceId(UUID invoiceId) {
+ this.invoiceId = invoiceId;
+ return this;
+ }
+
+ public Builder setAccountId(UUID accountId) {
+ this.accountId = accountId;
+ return this;
+ }
+
+ public Builder setAmount(BigDecimal amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public Builder setCurrency(Currency currency) {
+ this.currency = currency;
+ return this;
+ }
+
+ public Builder setCreatedDate(DateTime createdDate) {
+ this.createdDate = createdDate;
+ return this;
+ }
+
+ public Builder setUpdatedDate(DateTime updatedDate) {
+ this.updatedDate = updatedDate;
+ return this;
+ }
+
+ public Builder setInvoiceDate(DateTime invoiceDate) {
+ this.invoiceDate = invoiceDate;
+ return this;
+ }
+
+ public Builder setPaymentAttemptDate(DateTime paymentAttemptDate) {
+ this.paymentAttemptDate = paymentAttemptDate;
+ return this;
+ }
+
+ public Builder setPaymentId(String paymentId) {
+ this.paymentId = paymentId;
+ return this;
+ }
+
+ public Builder setRetryCount(Integer retryCount) {
+ this.retryCount = retryCount;
+ return this;
+ }
+
+ public Builder setNextRetryDate(DateTime nextRetryDate) {
+ this.nextRetryDate = nextRetryDate;
+ return this;
+ }
+
+ public PaymentAttempt build() {
+ return new PaymentAttempt(paymentAttemptId,
+ invoiceId,
+ accountId,
+ amount,
+ currency,
+ invoiceDate,
+ paymentAttemptDate,
+ paymentId,
+ retryCount,
+ nextRetryDate,
+ createdDate,
+ updatedDate);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(paymentAttemptId,
+ invoiceId,
+ accountId,
+ amount,
+ currency,
+ invoiceDate,
+ paymentAttemptDate,
+ paymentId,
+ retryCount,
+ nextRetryDate,
+ createdDate,
+ updatedDate);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (getClass() == obj.getClass()) {
+ PaymentAttempt other = (PaymentAttempt)obj;
+ if (obj == other) {
+ return true;
+ }
+ else {
+ return Objects.equal(paymentAttemptId, other.paymentAttemptId) &&
+ Objects.equal(invoiceId, other.invoiceId) &&
+ Objects.equal(accountId, other.accountId) &&
+ Objects.equal(amount, other.amount) &&
+ Objects.equal(currency, other.currency) &&
+ Objects.equal(invoiceDate, other.invoiceDate) &&
+ Objects.equal(paymentAttemptDate, other.paymentAttemptDate) &&
+ Objects.equal(retryCount, other.retryCount) &&
+ Objects.equal(nextRetryDate, other.nextRetryDate) &&
+ Objects.equal(paymentId, other.paymentId);
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentError.java b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
new file mode 100644
index 0000000..f1474c8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+import org.codehaus.jackson.annotate.JsonTypeInfo;
+import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
+
+import com.ning.billing.util.bus.BusEvent;
+
+@JsonTypeInfo(use = Id.NAME, property = "error")
+public class PaymentError implements BusEvent {
+ private final String type;
+ private final String message;
+
+ public PaymentError(PaymentError src) {
+ this.type = src.type;
+ this.message = src.message;
+ }
+
+ public PaymentError(String type, String message) {
+ this.type = type;
+ this.message = message;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((message == null) ? 0 : message.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PaymentError other = (PaymentError) obj;
+ if (message == null) {
+ if (other.message != null)
+ return false;
+ }
+ else if (!message.equals(other.message))
+ return false;
+ if (type == null) {
+ if (other.type != null)
+ return false;
+ }
+ else if (!type.equals(other.type))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentError [type=" + type + ", message=" + message + "]";
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
new file mode 100644
index 0000000..943c5f7
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import java.math.BigDecimal;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.common.base.Objects;
+import com.ning.billing.util.bus.BusEvent;
+
+public class PaymentInfo implements BusEvent {
+ private final String paymentId;
+ private final BigDecimal amount;
+ private final BigDecimal refundAmount;
+ private final String paymentNumber;
+ private final String bankIdentificationNumber;
+ private final String status;
+ private final String type;
+ private final String referenceId;
+ private final String paymentMethodId;
+ private final String paymentMethod;
+ private final String cardType;
+ private final String cardCoutry;
+ private final DateTime effectiveDate;
+ private final DateTime createdDate;
+ private final DateTime updatedDate;
+
+ @JsonCreator
+ public PaymentInfo(@JsonProperty("paymentId") String paymentId,
+ @JsonProperty("amount") BigDecimal amount,
+ @JsonProperty("refundAmount") BigDecimal refundAmount,
+ @JsonProperty("bankIdentificationNumber") String bankIdentificationNumber,
+ @JsonProperty("paymentNumber") String paymentNumber,
+ @JsonProperty("status") String status,
+ @JsonProperty("type") String type,
+ @JsonProperty("referenceId") String referenceId,
+ @JsonProperty("paymentMethodId") String paymentMethodId,
+ @JsonProperty("paymentMethod") String paymentMethod,
+ @JsonProperty("cardType") String cardType,
+ @JsonProperty("cardCountry") String cardCountry,
+ @JsonProperty("effectiveDate") DateTime effectiveDate,
+ @JsonProperty("createdDate") DateTime createdDate,
+ @JsonProperty("updatedDate") DateTime updatedDate) {
+ this.paymentId = paymentId;
+ this.amount = amount;
+ this.refundAmount = refundAmount;
+ this.bankIdentificationNumber = bankIdentificationNumber;
+ this.paymentNumber = paymentNumber;
+ this.status = status;
+ this.type = type;
+ this.referenceId = referenceId;
+ this.paymentMethodId = paymentMethodId;
+ this.paymentMethod = paymentMethod;
+ this.cardType = cardType;
+ this.cardCoutry = cardCountry;
+ this.effectiveDate = effectiveDate;
+ this.createdDate = createdDate == null ? new DateTime(DateTimeZone.UTC) : createdDate;
+ this.updatedDate = updatedDate == null ? new DateTime(DateTimeZone.UTC) : updatedDate;
+ }
+
+ public PaymentInfo(PaymentInfo src) {
+ this(src.paymentId,
+ src.amount,
+ src.refundAmount,
+ src.bankIdentificationNumber,
+ src.paymentNumber,
+ src.status,
+ src.type,
+ src.referenceId,
+ src.paymentMethodId,
+ src.paymentMethod,
+ src.cardType,
+ src.cardCoutry,
+ src.effectiveDate,
+ src.createdDate,
+ src.updatedDate);
+ }
+
+ public Builder cloner() {
+ return new Builder(this);
+ }
+
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public String getBankIdentificationNumber() {
+ return bankIdentificationNumber;
+ }
+
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ public DateTime getEffectiveDate() {
+ return effectiveDate;
+ }
+
+ public String getPaymentNumber() {
+ return paymentNumber;
+ }
+
+ public String getPaymentMethod() {
+ return paymentMethod;
+ }
+
+ public String getCardType() {
+ return cardType;
+ }
+
+ public String getCardCountry() {
+ return cardCoutry;
+ }
+
+ public String getReferenceId() {
+ return referenceId;
+ }
+
+ public String getPaymentMethodId() {
+ return paymentMethodId;
+ }
+
+ public BigDecimal getRefundAmount() {
+ return refundAmount;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ public static class Builder {
+ private String paymentId;
+ private BigDecimal amount;
+ private BigDecimal refundAmount;
+ private String paymentNumber;
+ private String bankIdentificationNumber;
+ private String type;
+ private String status;
+ private String referenceId;
+ private String paymentMethodId;
+ private String paymentMethod;
+ private String cardType;
+ private String cardCountry;
+ private DateTime effectiveDate;
+ private DateTime createdDate;
+ private DateTime updatedDate;
+
+ public Builder() {
+ }
+
+ public Builder(PaymentInfo src) {
+ this.paymentId = src.paymentId;
+ this.amount = src.amount;
+ this.refundAmount = src.refundAmount;
+ this.paymentNumber = src.paymentNumber;
+ this.bankIdentificationNumber = src.bankIdentificationNumber;
+ this.type = src.type;
+ this.status = src.status;
+ this.effectiveDate = src.effectiveDate;
+ this.referenceId = src.referenceId;
+ this.paymentMethodId = src.paymentMethodId;
+ this.paymentMethod = src.paymentMethod;
+ this.cardType = src.cardType;
+ this.cardCountry = src.cardCoutry;
+ this.createdDate = src.createdDate;
+ this.updatedDate = src.updatedDate;
+ }
+
+ public Builder setPaymentId(String paymentId) {
+ this.paymentId = paymentId;
+ return this;
+ }
+
+ public Builder setAmount(BigDecimal amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ public Builder setBankIdentificationNumber(String bankIdentificationNumber) {
+ this.bankIdentificationNumber = bankIdentificationNumber;
+ return this;
+ }
+
+ public Builder setCreatedDate(DateTime createdDate) {
+ this.createdDate = createdDate;
+ return this;
+ }
+
+ public Builder setEffectiveDate(DateTime effectiveDate) {
+ this.effectiveDate = effectiveDate;
+ return this;
+ }
+
+ public Builder setPaymentNumber(String paymentNumber) {
+ this.paymentNumber = paymentNumber;
+ return this;
+ }
+
+ public Builder setReferenceId(String referenceId) {
+ this.referenceId = referenceId;
+ return this;
+ }
+
+ public Builder setRefundAmount(BigDecimal refundAmount) {
+ this.refundAmount = refundAmount;
+ return this;
+ }
+
+ public Builder setStatus(String status) {
+ this.status = status;
+ return this;
+ }
+
+ public Builder setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ public Builder setPaymentMethodId(String paymentMethodId) {
+ this.paymentMethodId = paymentMethodId;
+ return this;
+ }
+
+ public Builder setPaymentMethod(String paymentMethod) {
+ this.paymentMethod = paymentMethod;
+ return this;
+ }
+
+ public Builder setCardType(String cardType) {
+ this.cardType = cardType;
+ return this;
+ }
+
+ public Builder setCardCountry(String cardCountry) {
+ this.cardCountry = cardCountry;
+ return this;
+ }
+
+ public Builder setUpdatedDate(DateTime updatedDate) {
+ this.updatedDate = updatedDate;
+ return this;
+ }
+
+ public PaymentInfo build() {
+ return new PaymentInfo(paymentId,
+ amount,
+ refundAmount,
+ bankIdentificationNumber,
+ paymentNumber,
+ status,
+ type,
+ referenceId,
+ paymentMethodId,
+ paymentMethod,
+ cardType,
+ cardCountry,
+ effectiveDate,
+ createdDate,
+ updatedDate);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(paymentId,
+ amount,
+ refundAmount,
+ bankIdentificationNumber,
+ paymentNumber,
+ status,
+ type,
+ referenceId,
+ paymentMethodId,
+ paymentMethod,
+ cardType,
+ cardCoutry,
+ effectiveDate,
+ createdDate,
+ updatedDate);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (getClass() == obj.getClass()) {
+ PaymentInfo other = (PaymentInfo)obj;
+ if (obj == other) {
+ return true;
+ }
+ else {
+ return Objects.equal(amount, other.amount) &&
+ Objects.equal(bankIdentificationNumber, other.bankIdentificationNumber) &&
+ Objects.equal(paymentId, other.paymentId) &&
+ Objects.equal(paymentNumber, other.paymentNumber) &&
+ Objects.equal(referenceId, other.referenceId) &&
+ Objects.equal(refundAmount, other.refundAmount) &&
+ Objects.equal(status, other.status) &&
+ Objects.equal(type, other.type) &&
+ Objects.equal(paymentMethodId, other.paymentMethodId) &&
+ Objects.equal(paymentMethod, other.paymentMethod) &&
+ Objects.equal(cardType, other.cardType) &&
+ Objects.equal(cardCoutry, other.cardCoutry) &&
+ Objects.equal(effectiveDate, other.effectiveDate) &&
+ Objects.equal(createdDate, other.createdDate) &&
+ Objects.equal(updatedDate, other.updatedDate);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentInfo [paymentId=" + paymentId + ", amount=" + amount + ", refundAmount=" + refundAmount + ", paymentNumber=" + paymentNumber + ", bankIdentificationNumber=" + bankIdentificationNumber + ", status=" + status + ", type=" + type + ", referenceId=" + referenceId + ", paymentMethodId=" + paymentMethodId + ", paymentMethod=" + paymentMethod + ", cardType=" + cardType + ", cardCountry=" + cardCoutry + ", effectiveDate=" + effectiveDate + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + "]";
+ }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaymentMethodInfo.java
new file mode 100644
index 0000000..78f73d3
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentMethodInfo.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import com.google.common.base.Objects;
+
+public class PaymentMethodInfo {
+ private final String id;
+ private final String accountId;
+ private final Boolean defaultMethod;
+ private final String type;
+
+ public PaymentMethodInfo(String id,
+ String accountId,
+ Boolean defaultMethod,
+ String type) {
+ this.id = id;
+ this.accountId = accountId;
+ this.defaultMethod = defaultMethod;
+ this.type = type;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getAccountId() {
+ return accountId;
+ }
+
+ public Boolean getDefaultMethod() {
+ return defaultMethod;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id,
+ accountId,
+ defaultMethod,
+ type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (getClass() == obj.getClass()) {
+ PaymentMethodInfo other = (PaymentMethodInfo)obj;
+ if (obj == other) {
+ return true;
+ }
+ else {
+ return Objects.equal(id, other.id) &&
+ Objects.equal(accountId, other.accountId) &&
+ Objects.equal(defaultMethod, other.defaultMethod) &&
+ Objects.equal(type, other.type);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentMethodInfo [id=" + id + ", accountId=" + accountId + ", defaultMethod=" + defaultMethod + ", type=" + type + "]";
+ }
+
+ protected abstract static class BuilderBase<T extends PaymentMethodInfo, V extends BuilderBase<T, V>> {
+ protected final Class<V> builderClazz;
+ protected String id;
+ protected String accountId;
+ protected Boolean defaultMethod;
+
+ protected BuilderBase(Class<V> builderClazz) {
+ this.builderClazz = builderClazz;
+ }
+
+ protected BuilderBase(Class<V> builderClazz, T src) {
+ this(builderClazz);
+ this.id = src.id;
+ this.accountId = src.accountId;
+ this.defaultMethod = src.defaultMethod;
+ }
+
+ public V setId(String id) {
+ this.id = id;
+ return builderClazz.cast(this);
+ }
+
+ public V setAccountId(String accountId) {
+ this.accountId = accountId;
+ return builderClazz.cast(this);
+ }
+
+ public V setDefaultMethod(Boolean defaultMethod) {
+ this.defaultMethod = defaultMethod;
+ return builderClazz.cast(this);
+ }
+ }
+
+ public static class Builder extends BuilderBase<PaymentMethodInfo, Builder> {
+ private String type;
+
+ public Builder() {
+ super(Builder.class);
+ }
+
+ public Builder(PaymentMethodInfo src) {
+ super(Builder.class, src);
+ this.type = src.type;
+ }
+
+ public Builder setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ public PaymentMethodInfo build() {
+ return new PaymentMethodInfo(id, accountId, defaultMethod, type);
+ }
+ }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java
new file mode 100644
index 0000000..47cad39
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import com.google.common.base.Objects;
+
+public class PaymentProviderAccount {
+ private final String id;
+ private final String accountKey;
+ private final String accountName;
+ private final String phoneNumber;
+ private final String defaultPaymentMethodId;
+
+ public PaymentProviderAccount(String id,
+ String accountKey,
+ String accountName,
+ String phoneNumber,
+ String defaultPaymentMethodId) {
+ this.id = id;
+ this.accountKey = accountKey;
+ this.accountName = accountName;
+ this.phoneNumber = phoneNumber;
+ this.defaultPaymentMethodId = defaultPaymentMethodId;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getAccountKey() {
+ return accountKey;
+ }
+
+ public String getAccountName() {
+ return accountName;
+ }
+
+ public String getPhoneNumber() {
+ return phoneNumber;
+ }
+
+ public String getDefaultPaymentMethodId() {
+ return defaultPaymentMethodId;
+ }
+
+ public static class Builder {
+ private String id;
+ private String accountKey;
+ private String accountName;
+ private String phoneNumber;
+ private String defaultPaymentMethodId;
+
+ public Builder copyFrom(PaymentProviderAccount src) {
+ this.id = src.getId();
+ this.accountKey = src.getAccountKey();
+ this.accountName = src.getAccountName();
+ this.phoneNumber = src.getPhoneNumber();
+ this.defaultPaymentMethodId = src.getDefaultPaymentMethodId();
+ return this;
+ }
+
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setAccountKey(String accountKey) {
+ this.accountKey = accountKey;
+ return this;
+ }
+
+ public Builder setAccountName(String accountName) {
+ this.accountName = accountName;
+ return this;
+ }
+
+ public Builder setPhoneNumber(String phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ return this;
+ }
+
+ public Builder setDefaultPaymentMethod(String defaultPaymentMethod) {
+ this.defaultPaymentMethodId = defaultPaymentMethod;
+ return this;
+ }
+
+ public PaymentProviderAccount build() {
+ return new PaymentProviderAccount(id, accountKey, accountName, phoneNumber, defaultPaymentMethodId);
+ }
+
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id,
+ accountKey,
+ accountName,
+ phoneNumber,
+ defaultPaymentMethodId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (getClass() == obj.getClass()) {
+ PaymentProviderAccount other = (PaymentProviderAccount)obj;
+ if (obj == other) {
+ return true;
+ }
+ else {
+ return Objects.equal(id, other.id) &&
+ Objects.equal(accountKey, other.accountKey) &&
+ Objects.equal(accountName, other.accountName) &&
+ Objects.equal(phoneNumber, other.phoneNumber) &&
+ Objects.equal(defaultPaymentMethodId, other.defaultPaymentMethodId);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentProviderAccount [id=" + id + ", accountKey=" + accountKey + ", accountName=" + accountName + ", phoneNumber=" + phoneNumber + ", defaultPaymentMethodId=" + defaultPaymentMethodId + "]";
+ }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentProviderContactData.java b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderContactData.java
new file mode 100644
index 0000000..7be9908
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderContactData.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.google.common.base.Objects;
+import com.ning.billing.catalog.api.Currency;
+
+public class PaymentProviderContactData {
+ private final String firstName;
+ private final String lastName;
+ private final String email;
+ private final String phoneNumber;
+ private final String externalKey;
+ private final String locale;
+ private final Currency currency;
+
+ public PaymentProviderContactData(String firstName,
+ String lastName,
+ String email,
+ String phoneNumber,
+ String externalKey,
+ String locale,
+ Currency currency) {
+ this.firstName = StringUtils.substring(firstName, 0, 100);
+ this.lastName = StringUtils.substring(lastName, 0, 100);
+ this.email = StringUtils.substring(email, 0, 80);
+ this.phoneNumber = phoneNumber;
+ this.externalKey = externalKey;
+ this.locale = locale;
+ this.currency = currency;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getPhoneNumber() {
+ return phoneNumber;
+ }
+
+ public String getExternalKey() {
+ return externalKey;
+ }
+
+ public String getLocale() {
+ return locale;
+ }
+
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ public static class Builder {
+ private String firstName;
+ private String lastName;
+ private String email;
+ private String phoneNumber;
+ private String externalKey;
+ private String locale;
+ private Currency currency;
+
+ public Builder setExternalKey(String externalKey) {
+ this.externalKey = externalKey;
+ return this;
+ }
+
+ public Builder setFirstName(String firstName) {
+ this.firstName = firstName;
+ return this;
+ }
+
+ public Builder setLastName(String lastName) {
+ this.lastName = lastName;
+ return this;
+ }
+
+ public Builder setEmail(String email) {
+ this.email = email;
+ return this;
+ }
+
+ public Builder setPhoneNumber(String phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ return this;
+ }
+
+ public Builder setLocale(String locale) {
+ this.locale = locale;
+ return this;
+ }
+
+ public Builder setCurrency(Currency currency) {
+ this.currency = currency;
+ return this;
+ }
+
+ public PaymentProviderContactData build() {
+ return new PaymentProviderContactData(firstName, lastName, email, phoneNumber, externalKey, locale, currency);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(firstName,
+ lastName,
+ email,
+ phoneNumber,
+ externalKey,
+ locale,
+ currency);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (getClass() == obj.getClass()) {
+ PaymentProviderContactData other = (PaymentProviderContactData)obj;
+ if (obj == other) {
+ return true;
+ }
+ else {
+ return Objects.equal(firstName, other.firstName) &&
+ Objects.equal(lastName, other.lastName) &&
+ Objects.equal(email, other.email) &&
+ Objects.equal(phoneNumber, other.phoneNumber) &&
+ Objects.equal(externalKey, other.externalKey) &&
+ Objects.equal(locale, other.locale) &&
+ Objects.equal(currency, other.currency);
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentService.java b/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
new file mode 100644
index 0000000..988a00a
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import com.ning.billing.lifecycle.KillbillService;
+
+public interface PaymentService extends KillbillService {
+ @Override
+ String getName();
+
+ PaymentApi getPaymentApi();
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java
new file mode 100644
index 0000000..3b2ebed
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+import com.google.common.base.Strings;
+
+
+public final class PaypalPaymentMethodInfo extends PaymentMethodInfo {
+ public static final String TYPE = "PayPal";
+
+ public static final class Builder extends BuilderBase<PaypalPaymentMethodInfo, Builder> {
+ private String baid;
+ private String email;
+
+ public Builder() {
+ super(Builder.class);
+ }
+
+ public Builder(PaypalPaymentMethodInfo src) {
+ super(Builder.class, src);
+ this.baid = src.baid;
+ this.email = src.email;
+ }
+
+ public Builder setBaid(String baid) {
+ this.baid = baid;
+ return this;
+ }
+
+ public Builder setEmail(String email) {
+ this.email = email;
+ return this;
+ }
+
+ public PaypalPaymentMethodInfo build() {
+ return new PaypalPaymentMethodInfo(id, accountId, defaultMethod, baid, email);
+ }
+ }
+
+ private final String baid;
+ private final String email;
+
+ @JsonCreator
+ public PaypalPaymentMethodInfo(@JsonProperty("id") String id,
+ @JsonProperty("accountId") String accountId,
+ @JsonProperty("defaultMethod") Boolean defaultMethod,
+ @JsonProperty("baid") String baid,
+ @JsonProperty("email") String email) {
+ super(id, accountId, defaultMethod, TYPE);
+
+ if (Strings.isNullOrEmpty(baid) || Strings.isNullOrEmpty(email)) {
+ throw new IllegalArgumentException("baid and email should be present");
+ }
+
+ this.baid = baid;
+ this.email = email;
+ }
+
+ public String getBaid() {
+ return baid;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ @Override
+ public String toString() {
+ return "PaypalPaymentMethodInfo [baid=" + baid + ", email=" + email + "]";
+ }
+
+}
diff --git a/api/src/main/java/com/ning/billing/util/api/TagDefinitionApiException.java b/api/src/main/java/com/ning/billing/util/api/TagDefinitionApiException.java
new file mode 100644
index 0000000..81750cb
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/api/TagDefinitionApiException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.api;
+
+import com.ning.billing.BillingExceptionBase;
+import com.ning.billing.ErrorCode;
+
+public class TagDefinitionApiException extends BillingExceptionBase {
+ public TagDefinitionApiException(Throwable cause, int code, final String msg) {
+ super(cause, code, msg);
+ }
+
+ public TagDefinitionApiException(Throwable cause, ErrorCode code, final Object... args) {
+ super(cause, code, args);
+ }
+
+ public TagDefinitionApiException(ErrorCode code, final Object... args) {
+ super(code, args);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/util/api/TagDefinitionUserApi.java b/api/src/main/java/com/ning/billing/util/api/TagDefinitionUserApi.java
new file mode 100644
index 0000000..0bd985c
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/api/TagDefinitionUserApi.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.api;
+
+import java.util.List;
+import java.util.UUID;
+import com.ning.billing.util.tag.TagDefinition;
+
+public interface TagDefinitionUserApi {
+ /***
+ *
+ * @return
+ */
+ public List<TagDefinition> getTagDefinitions();
+
+ /***
+ *
+ * @param name Identifies the definition.
+ * @param description Describes the use of the definition.
+ * @param createdBy The name of person who created the definition.
+ * @return
+ * @throws TagDefinitionApiException
+ */
+ public TagDefinition create(String name, String description, String createdBy) throws TagDefinitionApiException;
+
+ /***
+ *
+ * @param definitionName Identifies the definition.
+ * @throws TagDefinitionApiException
+ */
+ public void deleteAllTagsForDefinition(String definitionName) throws TagDefinitionApiException;
+
+ /***
+ *
+ * @param definitionName Identifies the definition.
+ * @throws TagDefinitionApiException
+ */
+ public void deleteTagDefinition(String definitionName) throws TagDefinitionApiException;
+}
diff --git a/api/src/main/java/com/ning/billing/util/bus/BusService.java b/api/src/main/java/com/ning/billing/util/bus/BusService.java
new file mode 100644
index 0000000..cc2dd10
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/util/bus/BusService.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.bus;
+
+import com.ning.billing.lifecycle.KillbillService;
+
+public interface BusService extends KillbillService {
+
+ public Bus getBus();
+}
diff --git a/api/src/main/java/com/ning/billing/util/tag/Tag.java b/api/src/main/java/com/ning/billing/util/tag/Tag.java
index 9fb15ed..5e9008b 100644
--- a/api/src/main/java/com/ning/billing/util/tag/Tag.java
+++ b/api/src/main/java/com/ning/billing/util/tag/Tag.java
@@ -21,15 +21,9 @@ import org.joda.time.DateTime;
import com.ning.billing.util.entity.Entity;
public interface Tag extends Entity {
- UUID getTagDescriptionId();
-
- String getName();
-
- boolean getProcessPayment();
-
- boolean getGenerateInvoice();
+ String getTagDefinitionName();
String getAddedBy();
- DateTime getDateAdded();
+ DateTime getAddedDate();
}
diff --git a/api/src/main/java/com/ning/billing/util/tag/Taggable.java b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
index f274294..5e2f425 100644
--- a/api/src/main/java/com/ning/billing/util/tag/Taggable.java
+++ b/api/src/main/java/com/ning/billing/util/tag/Taggable.java
@@ -22,10 +22,10 @@ import org.joda.time.DateTime;
public interface Taggable {
public List<Tag> getTagList();
public boolean hasTag(String tagName);
- public void addTag(TagDescription description, String addedBy, DateTime dateAdded);
+ public void addTag(TagDefinition definition, String addedBy, DateTime dateAdded);
public void addTags(List<Tag> tags);
public void clearTags();
- public void removeTag(TagDescription description);
+ public void removeTag(TagDefinition definition);
public boolean generateInvoice();
public boolean processPayment();
}
beatrix/pom.xml 48(+45 -3)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 32ceef9..64a7b38 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
@@ -28,7 +28,15 @@
<groupId>com.ning.billing</groupId>
<artifactId>killbill-entitlement</artifactId>
</dependency>
- <dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-invoice</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-payment</artifactId>
+ </dependency>
+ <dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-catalog</artifactId>
</dependency>
@@ -43,7 +51,7 @@
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
- <version>3.0</version>
+ <scope>provided</scope>
</dependency>
<dependency>
<groupId>org.skife.config</groupId>
@@ -54,6 +62,12 @@
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-payment</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>com.ning.jdbi</groupId>
<artifactId>jdbi-metrics</artifactId>
<scope>test</scope>
@@ -78,11 +92,39 @@
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.mysql</groupId>
+ <artifactId>management</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.mysql</groupId>
+ <artifactId>management-dbfiles</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <scope>runtime</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <groups>fast,slow</groups>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
index 7a672c1..02da6c9 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
@@ -17,13 +17,14 @@
package com.ning.billing.beatrix.glue;
import com.google.inject.AbstractModule;
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
import com.ning.billing.beatrix.lifecycle.Lifecycle;
public class BeatrixModule extends AbstractModule {
@Override
protected void configure() {
- bind(Lifecycle.class).asEagerSingleton();
+ bind(Lifecycle.class).to(DefaultLifecycle.class).asEagerSingleton();
}
}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java
new file mode 100644
index 0000000..8a07355
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/DefaultLifecycle.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.beatrix.lifecycle;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel.Sequence;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+
+public class DefaultLifecycle implements Lifecycle {
+
+ private final static Logger log = LoggerFactory.getLogger(DefaultLifecycle.class);
+ private final SetMultimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> handlersByLevel;
+
+ private final ServiceFinder serviceFinder;
+
+ protected final Injector injector;
+
+ @Inject
+ public DefaultLifecycle(Injector injector) {
+
+ this.serviceFinder = new ServiceFinder(DefaultLifecycle.class.getClassLoader());
+ this.handlersByLevel = Multimaps.newSetMultimap(new ConcurrentHashMap<LifecycleLevel, Collection<LifecycleHandler<? extends KillbillService>>>(),
+
+ new Supplier<Set<LifecycleHandler<? extends KillbillService>>>() {
+ @Override
+ public Set<LifecycleHandler<? extends KillbillService>> get() {
+ return new CopyOnWriteArraySet<LifecycleHandler<? extends KillbillService>>();
+ }
+ });
+ this.injector = injector;
+
+ init();
+ }
+
+
+ @Override
+ public void fireStartupSequencePriorEventRegistration() {
+ fireSequence(Sequence.STARTUP_PRE_EVENT_REGISTRATION);
+ }
+
+ @Override
+ public void fireStartupSequencePostEventRegistration() {
+ fireSequence(Sequence.STARTUP_POST_EVENT_REGISTRATION);
+ }
+
+ @Override
+ public void fireShutdownSequencePriorEventUnRegistration() {
+ fireSequence(Sequence.SHUTDOWN_PRE_EVENT_UNREGISTRATION);
+ }
+
+ @Override
+ public void fireShutdownSequencePostEventUnRegistration() {
+ fireSequence(Sequence.SHUTDOWN_POST_EVENT_UNREGISTRATION);
+ }
+
+ protected Set<? extends KillbillService> findServices() {
+
+ Set<KillbillService> result = new HashSet<KillbillService>();
+ Set<Class<? extends KillbillService>> services = serviceFinder.getServices();
+ for (Class<? extends KillbillService> cur : services) {
+ log.debug("Found service {}", cur.getName());
+ try {
+ KillbillService instance = injector.getInstance(cur);
+ log.debug("got instance {}", instance.getName());
+ result.add(instance);
+ } catch (Exception e) {
+ logWarn("Failed to inject " + cur.getName(), e);
+ }
+
+ }
+ return result;
+ }
+
+ private void init() {
+ Set<? extends KillbillService> services = findServices();
+ Iterator<? extends KillbillService> it = services.iterator();
+ while (it.hasNext()) {
+ handlersByLevel.putAll(findAllHandlers(it.next()));
+ }
+ }
+
+ private void fireSequence(Sequence seq) {
+ List<LifecycleLevel> levels = LifecycleLevel.getLevelsForSequence(seq);
+ for (LifecycleLevel cur : levels) {
+ doFireStage(cur);
+ }
+ }
+
+ private void doFireStage(LifecycleLevel level) {
+ log.info("Killbill lifecycle firing stage {}", level);
+ Set<LifecycleHandler<? extends KillbillService>> handlers = handlersByLevel.get(level);
+ for (LifecycleHandler<? extends KillbillService> cur : handlers) {
+
+ try {
+ Method method = cur.getMethod();
+ KillbillService target = cur.getTarget();
+ log.info("Killbill lifecycle calling handler {} for service {}", cur.getMethod().getName(), target.getName());
+ method.invoke(target);
+ } catch (Exception e) {
+ logWarn("Killbill lifecycle failed to invoke lifecycle handler", e);
+ }
+ }
+
+ }
+
+
+ // Used to disable valid injection failure from unit tests
+ protected void logWarn(String msg, Exception e) {
+ log.warn(msg, e);
+ }
+
+ private Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> findAllHandlers(KillbillService service) {
+ Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> methodsInService = HashMultimap.create();
+ Class<? extends KillbillService> clazz = service.getClass();
+ for (Method method : clazz.getMethods()) {
+ LifecycleHandlerType annotation = method.getAnnotation(LifecycleHandlerType.class);
+ if (annotation != null) {
+ LifecycleLevel level = annotation.value();
+ LifecycleHandler<? extends KillbillService> handler = new LifecycleHandler<KillbillService>(service, method);
+ methodsInService.put(level, handler);
+ }
+ }
+ return methodsInService;
+ }
+
+ private final class LifecycleHandler<T> {
+ private final T target;
+ private final Method method;
+
+ public LifecycleHandler(T target, Method method) {
+ this.target = target;
+ this.method = method;
+ }
+
+ public T getTarget() {
+ return target;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+ }
+}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
index c0edf2c..8192a65 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/Lifecycle.java
@@ -16,156 +16,14 @@
package com.ning.billing.beatrix.lifecycle;
-import com.google.common.base.Supplier;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.SetMultimap;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.ning.billing.lifecycle.KillbillService;
-import com.ning.billing.lifecycle.LifecycleHandlerType;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel.Sequence;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import java.lang.reflect.Method;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
+public interface Lifecycle {
+ public void fireStartupSequencePriorEventRegistration();
-public class Lifecycle {
+ public void fireStartupSequencePostEventRegistration();
- private final static Logger log = LoggerFactory.getLogger(Lifecycle.class);
- private final SetMultimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> handlersByLevel;
+ public void fireShutdownSequencePriorEventUnRegistration();
- private final ServiceFinder serviceFinder;
-
- private final Injector injector;
-
- @Inject
- public Lifecycle(Injector injector) {
-
- this.serviceFinder = new ServiceFinder(Lifecycle.class.getClassLoader());
- this.handlersByLevel = Multimaps.newSetMultimap(new ConcurrentHashMap<LifecycleLevel, Collection<LifecycleHandler<? extends KillbillService>>>(),
-
- new Supplier<Set<LifecycleHandler<? extends KillbillService>>>() {
- @Override
- public Set<LifecycleHandler<? extends KillbillService>> get() {
- return new CopyOnWriteArraySet<LifecycleHandler<? extends KillbillService>>();
- }
- });
- this.injector = injector;
-
- init();
- }
-
- public void init() {
- Set<? extends KillbillService> services = findServices();
- Iterator<? extends KillbillService> it = services.iterator();
- while (it.hasNext()) {
- handlersByLevel.putAll(findAllHandlers(it.next()));
- }
- }
-
-
- public void fireStartupSequencePriorEventRegistration() {
- fireSequence(Sequence.STARTUP_PRE_EVENT_REGISTRATION);
- }
-
- public void fireStartupSequencePostEventRegistration() {
- fireSequence(Sequence.STARTUP_POST_EVENT_REGISTRATION);
- }
-
- public void fireShutdownSequencePriorEventUnRegistration() {
- fireSequence(Sequence.SHUTDOWN_PRE_EVENT_UNREGISTRATION);
- }
-
- public void fireShutdownSequencePostEventUnRegistration() {
- fireSequence(Sequence.SHUTDOWN_POST_EVENT_UNREGISTRATION);
- }
-
- private void fireSequence(Sequence seq) {
- List<LifecycleLevel> levels = LifecycleLevel.getLevelsForSequence(seq);
- for (LifecycleLevel cur : levels) {
- doFireStage(cur);
- }
- }
-
- private void doFireStage(LifecycleLevel level) {
- log.info("Killbill lifecycle firing stage {}", level);
- Set<LifecycleHandler<? extends KillbillService>> handlers = handlersByLevel.get(level);
- for (LifecycleHandler<? extends KillbillService> cur : handlers) {
-
- try {
- Method method = cur.getMethod();
- KillbillService target = cur.getTarget();
- log.info("Killbill lifecycle calling handler {} for service {}", cur.getMethod().getName(), target.getName());
- method.invoke(target);
- } catch (Exception e) {
- logWarn("Killbill lifecycle failed to invoke lifecycle handler", e);
- }
- }
-
- }
-
-
- private Set<? extends KillbillService> findServices() {
-
- Set<KillbillService> result = new HashSet<KillbillService>();
- Set<Class<? extends KillbillService>> services = serviceFinder.getServices();
- for (Class<? extends KillbillService> cur : services) {
- log.debug("Found service {}", cur.getName());
- try {
- KillbillService instance = injector.getInstance(cur);
- log.debug("got instance {}", instance.getName());
- result.add(instance);
- } catch (Exception e) {
- logWarn("Failed to inject " + cur.getName(), e);
- }
-
- }
- return result;
- }
-
-
- // Used to disable valid injection failure from unit tests
- protected void logWarn(String msg, Exception e) {
- log.warn(msg, e);
- }
-
- public Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> findAllHandlers(KillbillService service) {
- Multimap<LifecycleLevel, LifecycleHandler<? extends KillbillService>> methodsInService = HashMultimap.create();
- Class<? extends KillbillService> clazz = service.getClass();
- for (Method method : clazz.getMethods()) {
- LifecycleHandlerType annotation = method.getAnnotation(LifecycleHandlerType.class);
- if (annotation != null) {
- LifecycleLevel level = annotation.value();
- LifecycleHandler<? extends KillbillService> handler = new LifecycleHandler<KillbillService>(service, method);
- methodsInService.put(level, handler);
- }
- }
- return methodsInService;
- }
-
-
- private final class LifecycleHandler<T> {
- private final T target;
- private final Method method;
-
- public LifecycleHandler(T target, Method method) {
- this.target = target;
- this.method = method;
- }
-
- public T getTarget() {
- return target;
- }
-
- public Method getMethod() {
- return method;
- }
- }
+ public void fireShutdownSequencePostEventUnRegistration();
}
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
index a4a4ce2..83115de 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/lifecycle/ServiceFinder.java
@@ -16,7 +16,6 @@
package com.ning.billing.beatrix.lifecycle;
-
import com.ning.billing.lifecycle.KillbillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,7 +24,13 @@ import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
import java.util.jar.JarFile;
public class ServiceFinder {
@@ -81,8 +86,6 @@ public class ServiceFinder {
}
for (int h = 0; h < classPaths.length; h++) {
-
-
Enumeration<?> files = null;
JarFile module = null;
File classPath = new File( (URL.class).isInstance(classPaths[h]) ?
@@ -93,7 +96,7 @@ public class ServiceFinder {
List<String> dirListing = new ArrayList<String>();
recursivelyListDir(dirListing, classPath, new StringBuffer() );
- files = Collections.enumeration( dirListing );
+ files = Collections.enumeration(dirListing);
} else if (classPath.getName().endsWith(".jar")) {
log.debug("JAR : " + classPath);
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.java
new file mode 100644
index 0000000..0cb1b01
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/MockModule.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.beatrix.integration;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Set;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.glue.InvoiceModule;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.payment.api.PaymentService;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.payment.setup.PaymentConfig;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import com.ning.billing.util.glue.NotificationQueueModule;
+
+
+public class MockModule extends AbstractModule {
+
+
+ public static final String PLUGIN_NAME = "yoyo";
+
+ @Override
+ protected void configure() {
+
+ loadSystemPropertiesFromClasspath("/resource.properties");
+
+ bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ bind(ClockMock.class).asEagerSingleton();
+ bind(Lifecycle.class).to(SubsetDefaultLifecycle.class).asEagerSingleton();
+
+
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ if (helper.isUsingLocalInstance()) {
+ bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+ bind(DbiConfig.class).toInstance(config);
+ } else {
+ final IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ }
+
+ install(new GlobalLockerModule());
+ install(new BusModule());
+ install(new NotificationQueueModule());
+ install(new AccountModule());
+ install(new CatalogModule());
+ install(new EntitlementModule());
+ install(new InvoiceModule());
+ install(new PaymentMockModule());
+ }
+
+ private static final class PaymentMockModule extends PaymentModule {
+ @Override
+ protected void installPaymentProviderPlugins(PaymentConfig config) {
+ install(new MockPaymentProviderPluginModule(PLUGIN_NAME));
+ }
+ }
+
+ private static void loadSystemPropertiesFromClasspath(final String resource) {
+ final URL url = TestBasic.class.getResource(resource);
+ assertNotNull(url);
+ try {
+ System.getProperties().load( url.openStream() );
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final static class SubsetDefaultLifecycle extends DefaultLifecycle {
+
+ @Inject
+ public SubsetDefaultLifecycle(Injector injector) {
+ super(injector);
+ }
+
+ @Override
+ protected Set<? extends KillbillService> findServices() {
+ ImmutableSet<? extends KillbillService> services = new ImmutableSet.Builder<KillbillService>()
+ .add(injector.getInstance(BusService.class))
+ .add(injector.getInstance(CatalogService.class))
+ .add(injector.getInstance(EntitlementService.class))
+ .add(injector.getInstance(InvoiceService.class))
+ .add(injector.getInstance(PaymentService.class))
+ .build();
+ return services;
+ }
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java
new file mode 100644
index 0000000..d04d768
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBasic.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.beatrix.integration;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.Days;
+import org.joda.time.Interval;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.AfterSuite;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountData;
+import com.ning.billing.account.api.AccountService;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.beatrix.integration.TestBusHandler.NextEvent;
+import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.EntitlementService;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.bus.BusService;
+
+@Guice(modules = {MockModule.class})
+public class TestBasic {
+
+ private static final Logger log = LoggerFactory.getLogger(TestBasic.class);
+ private static long AT_LEAST_ONE_MONTH_MS = 31L * 24L * 3600L * 1000L;
+
+ @Inject IDBI dbi;
+
+ @Inject
+ private ClockMock clock;
+
+ @Inject
+ private Lifecycle lifecycle;
+
+ @Inject
+ private BusService busService;
+
+ @Inject
+ private EntitlementService entitlementService;
+
+ @Inject
+ private InvoiceService invoiceService;
+
+ @Inject
+ private AccountService accountService;
+
+ @Inject
+ private MysqlTestingHelper helper;
+
+ private EntitlementUserApi entitlementUserApi;
+
+ private InvoiceUserApi invoiceUserApi;
+
+ private AccountUserApi accountUserApi;
+
+ private TestBusHandler busHandler;
+
+
+
+ private void setupMySQL() throws IOException
+ {
+
+
+ final String accountDdl = IOUtils.toString(TestBasic.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+ final String entitlementDdl = IOUtils.toString(TestBasic.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+ final String invoiceDdl = IOUtils.toString(TestBasic.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+ final String paymentDdl = IOUtils.toString(TestBasic.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+ final String utilDdl = IOUtils.toString(TestBasic.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+
+ helper.startMysql();
+
+ helper.initDb(accountDdl);
+ helper.initDb(entitlementDdl);
+ helper.initDb(invoiceDdl);
+ helper.initDb(paymentDdl);
+ helper.initDb(utilDdl);
+ }
+
+ @BeforeSuite(alwaysRun = true)
+ public void setup() throws Exception{
+
+ setupMySQL();
+
+ /**
+ * Initialize lifecyle for subset of services
+ */
+ busHandler = new TestBusHandler();
+ lifecycle.fireStartupSequencePriorEventRegistration();
+ busService.getBus().register(busHandler);
+ lifecycle.fireStartupSequencePostEventRegistration();
+
+
+
+ /**
+ * Retrieve APIs
+ */
+ entitlementUserApi = entitlementService.getUserApi();
+ invoiceUserApi = invoiceService.getUserApi();
+ accountUserApi = accountService.getAccountUserApi();
+ }
+
+ @AfterSuite(alwaysRun = true)
+ public void tearDown() throws Exception {
+ lifecycle.fireShutdownSequencePriorEventUnRegistration();
+ busService.getBus().unregister(busHandler);
+ lifecycle.fireShutdownSequencePostEventUnRegistration();
+ }
+
+
+ @BeforeMethod(alwaysRun = true)
+ public void setupTest() {
+
+ log.warn("\n");
+ log.warn("RESET TEST FRAMEWORK\n\n");
+ busHandler.reset();
+ clock.resetDeltaFromReality();
+ cleanupData();
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void cleanupTest() {
+ log.warn("DONE WITH TEST\n");
+ }
+
+ private void cleanupData() {
+ dbi.inTransaction(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(Handle h, TransactionStatus status)
+ throws Exception {
+ h.execute("truncate table accounts");
+ h.execute("truncate table entitlement_events");
+ h.execute("truncate table subscriptions");
+ h.execute("truncate table bundles");
+ h.execute("truncate table notifications");
+ h.execute("truncate table claimed_notifications");
+ h.execute("truncate table invoices");
+ h.execute("truncate table fixed_invoice_items");
+ h.execute("truncate table recurring_invoice_items");
+ h.execute("truncate table tag_definitions");
+ h.execute("truncate table tags");
+ h.execute("truncate table custom_fields");
+ h.execute("truncate table invoice_payments");
+ h.execute("truncate table payment_attempts");
+ h.execute("truncate table payments");
+ return null;
+ }
+ });
+ }
+
+ private void verifyTestResult(UUID accountId, UUID subscriptionId,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, DateTime chargeThroughDate) {
+ SubscriptionData subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscriptionId);
+
+ List<InvoiceItem> invoiceItems = invoiceUserApi.getInvoiceItemsByAccount(accountId);
+ boolean wasFound = false;
+
+ Iterator<InvoiceItem> invoiceItemIterator = invoiceItems.iterator();
+ while (invoiceItemIterator.hasNext()) {
+ InvoiceItem item = invoiceItemIterator.next();
+ if (item.getStartDate().compareTo(removeMillis(startDate)) == 0) {
+ if (item.getEndDate().compareTo(removeMillis(endDate)) == 0) {
+ if (item.getAmount().compareTo(amount) == 0) {
+ wasFound = true;
+ break;
+ }
+ }
+ }
+ }
+
+ assertTrue(wasFound);
+
+ DateTime ctd = subscription.getChargedThroughDate();
+ assertNotNull(ctd);
+ log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
+ assertTrue(clock.getUTCNow().isBefore(ctd));
+ assertTrue(ctd.compareTo(removeMillis(chargeThroughDate)) == 0);
+ }
+
+ private DateTime removeMillis(DateTime input) {
+ return input.toMutableDateTime().millisOfSecond().set(0).toDateTime();
+ }
+
+ @Test(groups = "fast", enabled = false)
+ public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
+ DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+ testBasePlanComplete(startDate, 31, false);
+ }
+
+ @Test(groups = "fast", enabled = false)
+ public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
+ DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+ testBasePlanComplete(startDate, 1, false);
+ }
+
+ @Test(groups = "fast", enabled = false)
+ public void testBasePlanCompleteWithBillingDayAlignedWithTrial() throws Exception {
+ DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+ testBasePlanComplete(startDate, 2, false);
+ }
+
+ @Test(groups = "fast", enabled = false)
+ public void testBasePlanCompleteWithBillingDayInFuture() throws Exception {
+ DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
+ testBasePlanComplete(startDate, 3, true);
+ }
+
+ private void waitForDebug() throws Exception {
+ Thread.sleep(600000);
+ }
+
+ @Test(groups = "stress", enabled = false)
+ public void stressTest() throws Exception {
+ final int maxIterations = 7;
+ int curIteration = maxIterations;
+ for (curIteration = 0; curIteration < maxIterations; curIteration++) {
+ log.info("################################ ITERATION " + curIteration + " #########################");
+ Thread.sleep(1000);
+ setupTest();
+ testBasePlanCompleteWithBillingDayPresent();
+ Thread.sleep(1000);
+ setupTest();
+ testBasePlanCompleteWithBillingDayInPast();
+ Thread.sleep(1000);
+ setupTest();
+ testBasePlanCompleteWithBillingDayAlignedWithTrial();
+ Thread.sleep(1000);
+ setupTest();
+ testBasePlanCompleteWithBillingDayInFuture();
+ }
+ }
+
+ private void testBasePlanComplete(DateTime initialCreationDate, int billingDay,
+ boolean proRationExpected) throws Exception {
+ long DELAY = 5000 * 10;
+
+ Account account = accountUserApi.createAccount(getAccountData(billingDay), null, null);
+ UUID accountId = account.getId();
+ assertNotNull(account);
+
+ // set clock to the initial start date
+ clock.setDeltaFromReality(initialCreationDate.getMillis() - DateTime.now().getMillis());
+ SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever");
+
+ String productName = "Shotgun";
+ BillingPeriod term = BillingPeriod.MONTHLY;
+ String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ //
+ // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null);
+ assertNotNull(subscription);
+
+ assertTrue(busHandler.isCompleted(DELAY));
+ log.info("testSimple passed first busHandler checkpoint.");
+
+ //
+ // VERIFY CTD HAS BEEN SET
+ //
+ DateTime startDate = subscription.getCurrentPhaseStart();
+ DateTime endDate = startDate.plusDays(30);
+ BigDecimal price = subscription.getCurrentPhase().getFixedPrice().getPrice(Currency.USD);
+ verifyTestResult(accountId, subscription.getId(), startDate, endDate, price, endDate);
+
+ //
+ // CHANGE PLAN IMMEDIATELY AND EXPECT BOTH EVENTS: NextEvent.CHANGE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvent(NextEvent.CHANGE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+
+ BillingPeriod newTerm = BillingPeriod.MONTHLY;
+ String newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+ String newProductName = "Assault-Rifle";
+ subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+
+ assertTrue(busHandler.isCompleted(DELAY));
+ log.info("testSimple passed second busHandler checkpoint.");
+
+ //
+ // VERIFY AGAIN CTD HAS BEEN SET
+ //
+ startDate = subscription.getCurrentPhaseStart();
+ endDate = startDate.plusMonths(1);
+ price = subscription.getCurrentPhase().getFixedPrice().getPrice(Currency.USD);
+ verifyTestResult(accountId, subscription.getId(), startDate, endDate, price, endDate);
+
+ //
+ // MOVE TIME TO AFTER TRIAL AND EXPECT BOTH EVENTS : NextEvent.PHASE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+
+ if (proRationExpected) {
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ }
+
+ clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ //
+ // CHANGE PLAN EOT AND EXPECT NOTHING
+ //
+ newTerm = BillingPeriod.MONTHLY;
+ newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+ newProductName = "Pistol";
+ subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+ log.info("testSimple has passed third busHandler checkpoint (no events)");
+
+ //
+ // MOVE TIME AFTER CTD AND EXPECT BOTH EVENTS : NextEvent.CHANGE NextEvent.INVOICE
+ //
+ busHandler.pushExpectedEvent(NextEvent.CHANGE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ //clock.addDeltaFromReality(ctd.getMillis() - clock.getUTCNow().getMillis());
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+
+ //waitForDebug();
+
+ assertTrue(busHandler.isCompleted(DELAY));
+ log.info("testSimple passed fourth busHandler checkpoint.");
+
+ //
+ // MOVE TIME AFTER NEXT BILL CYCLE DAY AND EXPECT EVENT : NextEvent.INVOICE
+ //
+ int maxCycles = 3;
+ startDate = endDate;
+ endDate = startDate.plusMonths(1);
+ do {
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+ assertTrue(busHandler.isCompleted(DELAY));
+ verifyTestResult(accountId, subscription.getId(), startDate, endDate, price, endDate);
+ } while (maxCycles-- > 0);
+
+ //
+ // FINALLY CANCEL SUBSCRIPTION EOT
+ //
+ subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ subscription.cancel(clock.getUTCNow(), false);
+
+ // MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
+ busHandler.pushExpectedEvent(NextEvent.CANCEL);
+ Interval it = new Interval(clock.getUTCNow(), endDate);
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ //
+ // CHECK AGAIN THERE IS NO MORE INVOICES GENERATED
+ //
+ busHandler.reset();
+ clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS + 1000);
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ subscription = (SubscriptionData) entitlementUserApi.getSubscriptionFromId(subscription.getId());
+ DateTime lastCtd = subscription.getChargedThroughDate();
+ assertNotNull(lastCtd);
+ log.info("Checking CTD: " + lastCtd.toString() + "; clock is " + clock.getUTCNow().toString());
+ assertTrue(lastCtd.isBefore(clock.getUTCNow()));
+
+ // The invoice system is still working to verify there is nothing to do
+ Thread.sleep(DELAY);
+ log.info("TEST PASSED !");
+ }
+
+ @Test(enabled=false)
+ public void testHappyPath() throws AccountApiException, EntitlementUserApiException {
+ long DELAY = 5000 * 10;
+
+ Account account = accountUserApi.createAccount(getAccountData(3), null, null);
+ assertNotNull(account);
+
+ SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever");
+
+ String productName = "Shotgun";
+ BillingPeriod term = BillingPeriod.MONTHLY;
+ String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ busHandler.pushExpectedEvent(NextEvent.CREATE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ SubscriptionData subscription = (SubscriptionData) entitlementUserApi.createSubscription(bundle.getId(),
+ new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null);
+ assertNotNull(subscription);
+
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ busHandler.pushExpectedEvent(NextEvent.CHANGE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ BillingPeriod newTerm = BillingPeriod.MONTHLY;
+ String newPlanSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+ String newProductName = "Assault-Rifle";
+ subscription.changePlan(newProductName, newTerm, newPlanSetName, clock.getUTCNow());
+
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ busHandler.pushExpectedEvent(NextEvent.PHASE);
+ busHandler.pushExpectedEvent(NextEvent.INVOICE);
+ clock.setDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
+ assertTrue(busHandler.isCompleted(DELAY));
+
+ }
+
+ protected AccountData getAccountData(final int billingDay) {
+
+ final String someRandomKey = RandomStringUtils.randomAlphanumeric(10);
+ AccountData accountData = new AccountData() {
+ @Override
+ public String getName() {
+ return "firstName lastName";
+ }
+ @Override
+ public int getFirstNameLength() {
+ return "firstName".length();
+ }
+ @Override
+ public String getEmail() {
+ return someRandomKey + "@laposte.fr";
+ }
+ @Override
+ public String getPhone() {
+ return "4152876341";
+ }
+ @Override
+ public String getExternalKey() {
+ return someRandomKey;
+ }
+ @Override
+ public int getBillCycleDay() {
+ return billingDay;
+ }
+ @Override
+ public Currency getCurrency() {
+ return Currency.USD;
+ }
+ @Override
+ public String getPaymentProviderName() {
+ return MockModule.PLUGIN_NAME;
+ }
+
+ @Override
+ public DateTimeZone getTimeZone() {
+ return null;
+ }
+
+ @Override
+ public String getLocale() {
+ return null;
+ }
+
+ @Override
+ public String getAddress1() {
+ return null;
+ }
+
+ @Override
+ public String getAddress2() {
+ return null;
+ }
+
+ @Override
+ public String getCompanyName() {
+ return null;
+ }
+
+ @Override
+ public String getCity() {
+ return null;
+ }
+
+ @Override
+ public String getStateOrProvince() {
+ return null;
+ }
+
+ @Override
+ public String getPostalCode() {
+ return null;
+ }
+
+ @Override
+ public String getCountry() {
+ return null;
+ }
+ };
+ return accountData;
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java
new file mode 100644
index 0000000..57a0d6e
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBusHandler.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.beatrix.integration;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+
+import com.google.common.base.Joiner;
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.invoice.api.InvoiceCreationNotification;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public class TestBusHandler {
+
+ protected static final Logger log = LoggerFactory.getLogger(TestBusHandler.class);
+
+ private final List<NextEvent> nextExpectedEvent;
+
+ private volatile boolean completed;
+
+ public TestBusHandler() {
+ nextExpectedEvent = new Stack<NextEvent>();
+ this.completed = false;
+ }
+
+ public enum NextEvent {
+ MIGRATE_ENTITLEMENT,
+ CREATE,
+ CHANGE,
+ CANCEL,
+ UNCANCEL,
+ PAUSE,
+ RESUME,
+ PHASE,
+ INVOICE,
+ PAYMENT
+ }
+
+ @Subscribe
+ public void handleEntitlementEvents(SubscriptionTransition event) {
+ log.info(String.format("TestBusHandler Got subscription event %s", event.toString()));
+ switch (event.getTransitionType()) {
+ case MIGRATE_ENTITLEMENT:
+ assertEqualsNicely(NextEvent.MIGRATE_ENTITLEMENT);
+ notifyIfStackEmpty();
+ break;
+ case CREATE:
+ assertEqualsNicely(NextEvent.CREATE);
+ notifyIfStackEmpty();
+
+ break;
+ case CANCEL:
+ assertEqualsNicely(NextEvent.CANCEL);
+ notifyIfStackEmpty();
+
+ break;
+ case CHANGE:
+ assertEqualsNicely(NextEvent.CHANGE);
+ notifyIfStackEmpty();
+
+ break;
+ case PAUSE:
+ assertEqualsNicely(NextEvent.PAUSE);
+ notifyIfStackEmpty();
+
+ break;
+ case RESUME:
+ assertEqualsNicely(NextEvent.RESUME);
+ notifyIfStackEmpty();
+
+ break;
+ case UNCANCEL:
+ assertEqualsNicely(NextEvent.UNCANCEL);
+ notifyIfStackEmpty();
+ break;
+ case PHASE:
+ assertEqualsNicely(NextEvent.PHASE);
+ notifyIfStackEmpty();
+ break;
+ default:
+ throw new RuntimeException("Unexpected event type " + event.getRequestedTransitionTime());
+ }
+ }
+
+ @Subscribe
+ public void handleInvoiceEvents(InvoiceCreationNotification event) {
+ log.info(String.format("TestBusHandler Got Invoice event %s", event.toString()));
+ assertEqualsNicely(NextEvent.INVOICE);
+ notifyIfStackEmpty();
+
+ }
+
+ @Subscribe
+ public void handlePaymentEvents(PaymentInfo event) {
+ log.info(String.format("TestBusHandler Got PaymentInfo event %s", event.toString()));
+ assertEqualsNicely(NextEvent.PAYMENT);
+ notifyIfStackEmpty();
+ }
+
+ @Subscribe
+ public void handlePaymentErrorEvents(PaymentError event) {
+ log.info(String.format("TestBusHandler Got PaymentError event %s", event.toString()));
+ Assert.fail("Unexpected payment failure");
+ }
+
+ public void reset() {
+ nextExpectedEvent.clear();
+ completed = true;
+ }
+
+ public void pushExpectedEvent(NextEvent next) {
+ synchronized (this) {
+ nextExpectedEvent.add(next);
+ completed = false;
+ }
+ }
+
+ public boolean isCompleted(long timeout) {
+ synchronized (this) {
+ if (completed) {
+ return completed;
+ }
+ try {
+ wait(timeout);
+ } catch (Exception ignore) {
+ }
+ }
+ if (!completed) {
+ Joiner joiner = Joiner.on(" ");
+ log.error("TestBusHandler did not complete in " + timeout + " ms, remaining events are " + joiner.join(nextExpectedEvent));
+ }
+ return completed;
+ }
+
+ private void notifyIfStackEmpty() {
+ log.debug("TestBusHandler notifyIfStackEmpty ENTER");
+ synchronized (this) {
+ if (nextExpectedEvent.isEmpty()) {
+ log.debug("notifyIfStackEmpty EMPTY");
+ completed = true;
+ notify();
+ }
+ }
+ log.debug("TestBusHandler notifyIfStackEmpty EXIT");
+ }
+
+ private void assertEqualsNicely(NextEvent received) {
+
+ boolean foundIt = false;
+ Iterator<NextEvent> it = nextExpectedEvent.iterator();
+ while (it.hasNext()) {
+ NextEvent ev = it.next();
+ if (ev == received) {
+ it.remove();
+ foundIt = true;
+ break;
+ }
+ }
+ if (!foundIt) {
+ Joiner joiner = Joiner.on(" ");
+ log.error("TestBusHandler Received event " + received + "; expected " + joiner.join(nextExpectedEvent));
+ Assert.fail();
+ }
+ }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
index 69e6a0d..f60d61f 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/lifecycle/TestLifecycle.java
@@ -16,7 +16,11 @@
package com.ning.billing.beatrix.lifecycle;
-import com.google.inject.*;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
import com.ning.billing.lifecycle.KillbillService;
import com.ning.billing.lifecycle.LifecycleHandlerType;
import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
@@ -34,7 +38,7 @@ public class TestLifecycle {
private Service1 s1;
private Service2 s2;
- private Lifecycle lifecycle;
+ private DefaultLifecycle lifecycle;
public static class ServiceBase {
@@ -117,12 +121,12 @@ public class TestLifecycle {
- @BeforeClass
+ @BeforeClass(alwaysRun=true)
public void setup() {
final Injector g = Guice.createInjector(Stage.DEVELOPMENT, new TestLifecycleModule());
s1 = g.getInstance(Service1.class);
s2 = g.getInstance(Service2.class);
- lifecycle = g.getInstance(Lifecycle.class);
+ lifecycle = g.getInstance(DefaultLifecycle.class);
}
@Test(enabled=true, groups={"fast"})
@@ -148,7 +152,7 @@ public class TestLifecycle {
Assert.assertEquals(s1.getCount() + s2.getCount(), 1);
}
- public static class LifecycleNoWarn extends Lifecycle {
+ public static class LifecycleNoWarn extends DefaultLifecycle {
@Inject
public LifecycleNoWarn(Injector injector) {
@@ -163,7 +167,7 @@ public class TestLifecycle {
@Override
protected void configure() {
- bind(Lifecycle.class).to(LifecycleNoWarn.class).asEagerSingleton();
+ bind(DefaultLifecycle.class).to(LifecycleNoWarn.class).asEagerSingleton();
bind(Service1.class).asEagerSingleton();
bind(Service2.class).asEagerSingleton();
}
diff --git a/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt b/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt
new file mode 100644
index 0000000..6d5867f
--- /dev/null
+++ b/beatrix/src/test/resources/Catalog-Entitlement-Testplan.txt
@@ -0,0 +1,112 @@
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+
+
+NOTES
+=====
+
+Events: Create, change, cancel, migrate
+Validate: BillingEvents, SubscriptionTransition
+Rules:
+ Cancellation
+ Action Policy: When to cancel (Immediate/End-of-term)
+ Creation
+ Alignment: How to align phases in a bundle
+ Change plan behavior
+ Action Policy: When to change plan (Immediate/End-of-term)
+ Alignment: How to align phases
+ Pricelist: Which pricelist to pick when moving between plans
+ Billing alignment
+ Subscription BCD, Bundle BCD, Account BCD
+Phases - timing
+Prices - multi-currency, fixed vs recurring prices
+Pricelists - particularly pricelist change rules
+Catalog changes new subscriptions / existing subscriptions
+Price change
+
+TESTS
+=====
+
+BASEPLAN TESTS
+ * Create a single phase recurring plan
+ - check for creation event (timing?)
+ - check for no termination event
+ - check pricing (different currencies)
+ - check BDC (subscription, account, timezone)
+ * Create a single phase fixed length plan
+ - check for creation event (timing - different request dates)
+ - check for termination event (timing - different lengths?)
+ - check price (fixed vs recurring)
+ * Create a two phase event use a fixed price and a recurring price
+ - check for phase change (timing)
+ - check prices change
+ * Create a multi-phase plan
+ - check for phase events
+ - check price changes
+ * Create multiple base plans in a single bundle - should fail
+
+ * Change base plan once
+ - check plan change policy (immediate, eot)
+ - check alignments of new plan with old
+ - check move between pricelists
+ - check that phases progress successfully after change
+ - check obsolete events are removed
+ * Change base plan multiple times
+ - check that alignment occurs correctly
+ - check phases progress correctly
+ - check obsolete events are removed
+
+ * Cancellation of a single phase plan
+ - check creation and timing of termination event
+ * Cancellation of a multi-phase plan
+ - check creation of termination event
+ - check removal of events beyond termination event
+ * Change a cancelled base plan - should fail
+
+ * Migration to a single phase plan
+ - check migration event occurs when it should
+ * Migration to a multi-phase plan
+ - check migration event occurs when it should
+ - check migration into different phase
+ - check alignment of phases can be correctly controlled
+ * Migration to a fixed duration plan
+ - check migration event occurs when it should
+ - check termination event occurs when it should
+
+
+
+STANDALONE TEST
+ * Create multiple plans in a single bundle
+ - check plans can be created
+ - check cannot add a base plan
+ - check BCD at subscription bundle level
+
+
+PRICE CHANGE TEST
+ * Price change on a single phase base plan
+ - check new subscriptions get price after effective date
+ - check changed subscriptions get price after effective date
+ - check existing subscriptions ONLY get it after ESED
+ - check that if ESED is missing existing subs are grandfathered for ever
+ * Price change on a multi-phase subscription
+ - check price change applies correctly to correct phases
+ * Multiple price changes
+ - check multiple price changes with overlapping dates
+
+
+ADD-ON TESTS
+ * Add-on creation alignment
+ * Add-on cancel with base plan
+
+
\ No newline at end of file
beatrix/src/test/resources/catalogSample.xml 641(+641 -0)
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
new file mode 100644
index 0000000..c18816f
--- /dev/null
+++ b/beatrix/src/test/resources/catalogSample.xml
@@ -0,0 +1,641 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ Copyright 2010-2011 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<!--
+Use cases covered so far:
+ Tiered Product (Pistol/Shotgun/Assault-Rifle)
+ Multiple changeEvent plan policies
+ Multiple PlanAlignment (see below, trial add-on alignments and rescue discount package)
+ Product transition rules
+ Add on (Scopes, Hoster)
+ Multi-pack addon (Extra-Ammo)
+ Addon Trial aligned to base plan (holster-monthly-regular)
+ Addon Trial aligned to creation (holster-monthly-special)
+ Rescue discount package (assault-rifle-annual-rescue)
+ Plan phase with a reccurring and a one off (refurbish-maintenance)
+ Phan with more than 2 phase (gunclub discount plans)
+
+Use Cases to do:
+ Tiered Add On
+ Riskfree period
+
+
+
+ -->
+<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="CatalogSchema.xsd ">
+
+ <effectiveDate>2011-01-01T00:00:00+00:00</effectiveDate>
+ <catalogName>Firearms</catalogName>
+
+ <currencies>
+ <currency>USD</currency>
+ <currency>EUR</currency>
+ <currency>GBP</currency>
+ </currencies>
+
+ <products>
+ <product name="Pistol">
+ <category>BASE</category>
+ <available>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Shotgun">
+ <category>BASE</category>
+ </product>
+ <product name="Assault-Rifle">
+ <category>BASE</category>
+ <included>
+ <addonProduct>Telescopic-Scope</addonProduct>
+ </included>
+ <available>
+ <addonProduct>Laser-Scope</addonProduct>
+ </available>
+ </product>
+ <product name="Telescopic-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Laser-Scope">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Holster">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Extra-Ammo">
+ <category>ADD_ON</category>
+ </product>
+ <product name="Refurbish-Maintenance">
+ <category>ADD_ON</category>
+ </product>
+ </products>
+
+ <rules>
+ <changePolicy>
+ <changePolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toProduct>Pistol</toProduct>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toPriceList>rescue</toPriceList>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromProduct>Pistol</fromProduct>
+ <toProduct>Shotgun</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromProduct>Assault-Rifle</fromProduct>
+ <toProduct>Shotgun</toProduct>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>MONTHLY</fromBillingPeriod>
+ <toProduct>Assault-Rifle</toProduct>
+ <toBillingPeriod>MONTHLY</toBillingPeriod>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <toProduct>Assault-Rifle</toProduct>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>MONTHLY</fromBillingPeriod>
+ <toBillingPeriod>ANNUAL</toBillingPeriod>
+ <policy>IMMEDIATE</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <fromBillingPeriod>ANNUAL</fromBillingPeriod>
+ <toBillingPeriod>MONTHLY</toBillingPeriod>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ <changePolicyCase>
+ <policy>END_OF_TERM</policy>
+ </changePolicyCase>
+ </changePolicy>
+ <changeAlignment>
+ <changeAlignmentCase>
+ <alignment>START_OF_SUBSCRIPTION</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PLAN</alignment>
+ </changeAlignmentCase>
+ <changeAlignmentCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>rescue</toPriceList>
+ <alignment>CHANGE_OF_PRICELIST</alignment>
+ </changeAlignmentCase>
+ </changeAlignment>
+ <cancelPolicy>
+ <cancelPolicyCase>
+ <policy>END_OF_TERM</policy>
+ </cancelPolicyCase>
+ <cancelPolicyCase>
+ <phaseType>TRIAL</phaseType>
+ <policy>IMMEDIATE</policy>
+ </cancelPolicyCase>
+ </cancelPolicy>
+ <createAlignment>
+ <createAlignmentCase>
+ <alignment>START_OF_BUNDLE</alignment>
+ </createAlignmentCase>
+ </createAlignment>
+ <billingAlignment>
+ <billingAlignmentCase>
+ <productCategory>ADD_ON</productCategory>
+ <alignment>BUNDLE</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <alignment>SUBSCRIPTION</alignment>
+ </billingAlignmentCase>
+ <billingAlignmentCase>
+ <alignment>ACCOUNT</alignment>
+ </billingAlignmentCase>
+ </billingAlignment>
+ <priceList>
+ <priceListCase>
+ <fromPriceList>rescue</fromPriceList>
+ <toPriceList>DEFAULT</toPriceList>
+ </priceListCase>
+ </priceList>
+ </rules>
+
+ <plans>
+ <plan name="pistol-monthly-no-trial">
+ <product>Pistol</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>GBP</currency><value>29.95</value></price>
+ <price><currency>EUR</currency><value>29.95</value></price>
+ <price><currency>USD</currency><value>29.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-monthly">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ <!-- no price implies $0 -->
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>GBP</currency><value>29.95</value></price>
+ <price><currency>EUR</currency><value>29.95</value></price>
+ <price><currency>USD</currency><value>29.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-monthly">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ <!-- no price implies $0 -->
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ <number>-1</number>
+ </duration>
+ <billingPeriod>MONTHLY</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>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-monthly">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ <!-- no price implies $0 -->
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>599.95</value></price>
+ <price><currency>EUR</currency><value>349.95</value></price>
+ <price><currency>GBP</currency><value>399.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>2399.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1699.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="pistol-annual-gunclub-discount">
+ <product>Pistol</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>9.95</value></price>
+ <price><currency>EUR</currency><value>9.95</value></price>
+ <price><currency>GBP</currency><value>9.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="shotgun-annual-gunclub-discount">
+ <product>Shotgun</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>19.95</value></price>
+ <price><currency>EUR</currency><value>49.95</value></price>
+ <price><currency>GBP</currency><value>69.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>2399.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1699.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-gunclub-discount">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>6</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>99.95</value></price>
+ <price><currency>EUR</currency><value>99.95</value></price>
+ <price><currency>GBP</currency><value>99.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="laser-scope-monthly">
+ <product>Laser-Scope</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>1999.95</value></price>
+ <price><currency>EUR</currency><value>1499.95</value></price>
+ <price><currency>GBP</currency><value>1999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="telescopic-scope-monthly">
+ <product>Telescopic-Scope</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="extra-ammo-monthly">
+ <product>Extra-Ammo</product>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>999.95</value></price>
+ <price><currency>EUR</currency><value>499.95</value></price>
+ <price><currency>GBP</currency><value>999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ <plansAllowedInBundle>-1</plansAllowedInBundle> <!-- arbitrary number of these (multipack) -->
+ </plan>
+ <plan name="holster-monthly-regular">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="holster-monthly-special">
+ <product>Holster</product>
+ <initialPhases>
+ <phase type="TRIAL">
+ <duration>
+ <unit>DAYS</unit>
+ <number>30</number>
+ </duration>
+ <billingPeriod>NO_BILLING_PERIOD</billingPeriod>
+ <fixedPrice>
+ </fixedPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="assault-rifle-annual-rescue">
+ <product>Assault-Rifle</product>
+ <initialPhases>
+ <phase type="DISCOUNT">
+ <duration>
+ <unit>YEARS</unit>
+ <number>1</number>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </phase>
+ </initialPhases>
+ <finalPhase type="EVERGREEN">
+ <duration>
+ <unit>UNLIMITED</unit>
+ </duration>
+ <billingPeriod>ANNUAL</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>5999.95</value></price>
+ <price><currency>EUR</currency><value>3499.95</value></price>
+ <price><currency>GBP</currency><value>3999.95</value></price>
+ </recurringPrice>
+ </finalPhase>
+ </plan>
+ <plan name="refurbish-maintenance">
+ <product>Refurbish-Maintenance</product>
+ <finalPhase type="FIXEDTERM">
+ <duration>
+ <unit>MONTHS</unit>
+ <number>12</number>
+ </duration>
+ <billingPeriod>MONTHLY</billingPeriod>
+ <recurringPrice>
+ <price><currency>USD</currency><value>199.95</value></price>
+ <price><currency>EUR</currency><value>199.95</value></price>
+ <price><currency>GBP</currency><value>199.95</value></price>
+ </recurringPrice>
+ <fixedPrice>
+ <price><currency>USD</currency><value>599.95</value></price>
+ <price><currency>EUR</currency><value>599.95</value></price>
+ <price><currency>GBP</currency><value>599.95</value></price>
+ </fixedPrice>
+ </finalPhase>
+ </plan>
+ </plans>
+ <priceLists>
+ <defaultPriceList name="DEFAULT">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual</plan>
+ <plan>shotgun-annual</plan>
+ <plan>assault-rifle-annual</plan>
+ <plan>laser-scope-monthly</plan>
+ <plan>telescopic-scope-monthly</plan>
+ <plan>extra-ammo-monthly</plan>
+ <plan>holster-monthly-regular</plan>
+ <plan>refurbish-maintenance</plan>
+ </plans>
+ </defaultPriceList>
+ <childPriceList name="gunclubDiscount">
+ <plans>
+ <plan>pistol-monthly</plan>
+ <plan>shotgun-monthly</plan>
+ <plan>assault-rifle-monthly</plan>
+ <plan>pistol-annual-gunclub-discount</plan>
+ <plan>shotgun-annual-gunclub-discount</plan>
+ <plan>assault-rifle-annual-gunclub-discount</plan>
+ <plan>holster-monthly-special</plan>
+ </plans>
+ </childPriceList>
+ <childPriceList name="rescue">
+ <plans>
+ <plan>assault-rifle-annual-rescue</plan>
+ </plans>
+ </childPriceList>
+ </priceLists>
+
+</catalog>
beatrix/src/test/resources/log4j.xml 4(+4 -0)
diff --git a/beatrix/src/test/resources/log4j.xml b/beatrix/src/test/resources/log4j.xml
index 75abc76..ac530a1 100644
--- a/beatrix/src/test/resources/log4j.xml
+++ b/beatrix/src/test/resources/log4j.xml
@@ -29,6 +29,10 @@
<level value="info"/>
</logger>
+ <logger name="com.ning.billing.util.notificationq">
+ <level value="info"/>
+ </logger>
+
<root>
<priority value="info"/>
<appender-ref ref="stdout"/>
diff --git a/beatrix/src/test/resources/resource.properties b/beatrix/src/test/resources/resource.properties
new file mode 100644
index 0000000..d63334b
--- /dev/null
+++ b/beatrix/src/test/resources/resource.properties
@@ -0,0 +1,7 @@
+killbill.catalog.uri=file:src/test/resources/catalogSample.xml
+killbill.entitlement.dao.claim.time=60000
+killbill.entitlement.dao.ready.max=1
+killbill.entitlement.engine.notifications.sleep=500
+user.timezone=UTC
+
+
catalog/pom.xml 2(+1 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 1b5b8d6..5d19c5b 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
index 7f88f07..b824617 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultCatalogService.java
@@ -28,6 +28,7 @@ import com.ning.billing.lifecycle.LifecycleHandlerType;
import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
public class DefaultCatalogService implements KillbillService, Provider<Catalog>, CatalogService {
+ private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultCatalogService.class);
private static final String CATALOG_SERVICE_NAME = "catalog-service";
@@ -54,7 +55,7 @@ public class DefaultCatalogService implements KillbillService, Provider<Catalog>
System.out.println("Really really::" + config.getCatalogURI());
String url = config.getCatalogURI();
catalog = loader.load(url);
-
+
//catalog = XMLLoader.getObjectFromProperty(config.getCatalogURI(), Catalog.class);
isInitialized = true;
} catch (Exception e) {
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
index be3ea55..0e821ef 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultDuration.java
@@ -20,6 +20,7 @@ import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationErrors;
+import org.joda.time.DateTime;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@@ -49,7 +50,25 @@ public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> impleme
return number;
}
- @Override
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ if (number == null) {return dateTime;}
+
+ switch (unit) {
+ case DAYS:
+ return dateTime.plusDays(number);
+ case MONTHS:
+ return dateTime.plusMonths(number);
+ case YEARS:
+ return dateTime.plusYears(number);
+ case UNLIMITED:
+ return dateTime.plusYears(100);
+ default:
+ return dateTime;
+ }
+ }
+
+ @Override
public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
//TODO MDW - Validation TimeUnit UNLIMITED iff number == -1
return errors;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
index 6990d3c..d0cf2c4 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultInternationalPrice.java
@@ -42,7 +42,7 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
/* (non-Javadoc)
- * @see com.ning.billing.catalog.IInternationalPrice#getPrices()
+ * @see com.ning.billing.catalog.InternationalPrice#getPrices()
*/
@Override
public Price[] getPrices() {
@@ -119,4 +119,18 @@ public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalo
return zeroPrice;
}
+ @Override
+ public boolean isZero() {
+ for(DefaultPrice price :prices) {
+ try {
+ if( price.getValue().compareTo(BigDecimal.ZERO) != 0) {
+ return false;
+ }
+ } catch (CurrencyValueNull e) {
+ //Ignore if the currency is null we treat it as 0
+ }
+ }
+ return true;
+ }
+
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
index 4972e26..a249289 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlan.java
@@ -29,9 +29,12 @@ import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlIDREF;
+import org.joda.time.DateTime;
+
import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.Product;
@@ -228,6 +231,18 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
this.plansAllowedInBundle = plansAllowedInBundle;
return this;
}
+ @Override
+ public DateTime dateOfFirstRecurringNonZeroCharge(DateTime subscriptionStartDate) {
+ DateTime result = subscriptionStartDate.toDateTime();
+ for (PlanPhase phase : getAllPhases()) {
+ if(phase.getRecurringPrice() == null || phase.getRecurringPrice().isZero()) {
+ result = phase.getDuration().addToDateTime(result);
+ } else {
+ break;
+ }
+ }
+ return result;
+ }
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
index 4afe278..6d00162 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPlanPhase.java
@@ -17,7 +17,13 @@
package com.ning.billing.catalog;
import com.ning.billing.ErrorCode;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationError;
import com.ning.billing.util.config.ValidationErrors;
@@ -38,7 +44,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
private DefaultDuration duration;
@XmlElement(required=true)
- private BillingPeriod billingPeriod = BillingPeriod.NO_BILLING_PERIOD;
+ private BillingPeriod billingPeriod;
@XmlElement(required=false)
private DefaultInternationalPrice recurringPrice;
@@ -127,30 +133,31 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
public ValidationErrors validate(StandaloneCatalog catalog, ValidationErrors errors) {
//Validation: check for nulls
if(billingPeriod == null) {
- errors.add(new ValidationError(String.format("Phase %s of plan %s has a reccurring price but no billing period", type.toString(), plan.getName()),
+ errors.add(new ValidationError(String.format("Phase %s of plan %s has a recurring price but no billing period", type.toString(), plan.getName()),
catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
}
-
+
//Validation: if there is a recurring price there must be a billing period
- if(recurringPrice != null && (billingPeriod == null || billingPeriod ==BillingPeriod.NO_BILLING_PERIOD)) {
- errors.add(new ValidationError(String.format("Phase %s of plan %s has a reccurring price but no billing period", type.toString(), plan.getName()),
+ if((recurringPrice != null) && (billingPeriod == null || billingPeriod == BillingPeriod.NO_BILLING_PERIOD)) {
+ errors.add(new ValidationError(String.format("Phase %s of plan %s has a recurring price but no billing period", type.toString(), plan.getName()),
catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
}
- //Validation: if there is no reccuring price there should be no billing period
- if(recurringPrice == null && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
- errors.add(new ValidationError(String.format("Phase %s of plan %s has no reccurring price but does have a billing period. The billing period should be set to '%s'",
+
+ //Validation: if there is no recurring price there should be no billing period
+ if((recurringPrice == null) && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+ errors.add(new ValidationError(String.format("Phase %s of plan %s has no recurring price but does have a billing period. The billing period should be set to '%s'",
type.toString(), plan.getName(), BillingPeriod.NO_BILLING_PERIOD),
catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
}
- //Validation: there must be at least one of reccuringPrice or fixedPrice
- if(recurringPrice == null && fixedPrice == null) {
- errors.add(new ValidationError(String.format("Phase %s of plan %s has neither a reccurring price or a fixed price.",
+ //Validation: there must be at least one of recurringPrice or fixedPrice
+ if((recurringPrice == null) && fixedPrice == null) {
+ errors.add(new ValidationError(String.format("Phase %s of plan %s has neither a recurring price or a fixed price.",
type.toString(), plan.getName()),
catalog.getCatalogURI(), DefaultPlanPhase.class, type.toString()));
}
- return errors;
-
+ //TODO : if there BP is set to NO_BILLING_PERIOD there must be a recurring price
+ return errors;
}
@Override
@@ -164,7 +171,7 @@ public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implem
return this;
}
- protected DefaultPlanPhase setReccuringPrice(DefaultInternationalPrice price) {
+ protected DefaultPlanPhase setRecurringPrice(DefaultInternationalPrice price) {
this.recurringPrice = price;
return this;
}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
index d9d26a6..9b5f00b 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPrice.java
@@ -35,6 +35,16 @@ public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements
@XmlElement(required=true,nillable=true)
private BigDecimal value;
+ public DefaultPrice() {
+ // for serialization support
+ }
+
+ public DefaultPrice(final BigDecimal value, final Currency currency) {
+ // for sanity support
+ this.value = value;
+ this.currency = currency;
+ }
+
/* (non-Javadoc)
* @see com.ning.billing.catalog.IPrice#getCurrency()
*/
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
index 48de5d4..aba447d 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultPriceList.java
@@ -23,7 +23,13 @@ import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationError;
import com.ning.billing.util.config.ValidationErrors;
-import javax.xml.bind.annotation.*;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlIDREF;
@XmlAccessorType(XmlAccessType.NONE)
public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implements PriceList {
@@ -36,8 +42,7 @@ public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implem
private Boolean retired = false;
@XmlElementWrapper(name="plans", required=true)
- @XmlElement(name="plan", required=true)
- @XmlIDREF
+ @XmlIDREF @XmlElement(name="plan", required=true)
private DefaultPlan[] plans;
public DefaultPriceList(){}
diff --git a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
index bf8cfd4..ff3868c 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/DefaultProduct.java
@@ -21,15 +21,20 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationErrors;
-import javax.xml.bind.annotation.*;
-
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlID;
+import javax.xml.bind.annotation.XmlIDREF;
import java.net.URI;
@XmlAccessorType(XmlAccessType.NONE)
public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implements Product {
private static final DefaultProduct[] EMPTY_PRODUCT_LIST = new DefaultProduct[0];
- @XmlAttribute (required=true)
+ @XmlAttribute(required=true)
@XmlID
private String name;
@@ -42,7 +47,7 @@ public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implemen
@XmlElementWrapper(name="included", required=false)
@XmlIDREF @XmlElement(name="addonProduct", required=true)
private DefaultProduct[] included = EMPTY_PRODUCT_LIST;
-
+
@XmlElementWrapper(name="available", required=false)
@XmlIDREF @XmlElement(name="addonProduct", required=true)
private DefaultProduct[] available = EMPTY_PRODUCT_LIST;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
index f101ef1..963dc2f 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/rules/CaseChange.java
@@ -19,7 +19,12 @@ package com.ning.billing.catalog.rules;
import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.util.config.ValidatingConfig;
import com.ning.billing.util.config.ValidationErrors;
diff --git a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
index f652347..ef9b125 100644
--- a/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/com/ning/billing/catalog/VersionedCatalog.java
@@ -66,24 +66,16 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
public VersionedCatalog(Clock clock) {
this.clock = clock;
- StandaloneCatalog baseline = new StandaloneCatalog(new Date(0)); // init with an empty catalog may need to
- // populate some empty pieces here to make validation work
- try {
- add(baseline);
- } catch (CatalogApiException e) {
- // This should never happen
- log.error("This error should never happpen", e);
- }
}
//
// Private methods
//
- private StandaloneCatalog versionForDate(DateTime date) {
+ private StandaloneCatalog versionForDate(DateTime date) throws CatalogApiException {
return versions.get(indexOfVersionForDate(date.toDate()));
}
- private List<StandaloneCatalog> versionsBeforeDate(Date date) {
+ private List<StandaloneCatalog> versionsBeforeDate(Date date) throws CatalogApiException {
List<StandaloneCatalog> result = new ArrayList<StandaloneCatalog>();
int index = indexOfVersionForDate(date);
for(int i = 0; i <= index; i++) {
@@ -92,14 +84,14 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
return result;
}
- private int indexOfVersionForDate(Date date) {
- for(int i = 1; i < versions.size(); i++) {
+ private int indexOfVersionForDate(Date date) throws CatalogApiException {
+ for(int i = versions.size() - 1; i >= 0; i--) {
StandaloneCatalog c = versions.get(i);
- if(c.getEffectiveDate().getTime() > date.getTime()) {
- return i - 1;
+ if(c.getEffectiveDate().getTime() < date.getTime()) {
+ return i;
}
}
- return versions.size() - 1;
+ throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, date.toString());
}
private class PlanRequestWrapper {
@@ -205,17 +197,17 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
}
@Override
- public DefaultProduct[] getProducts(DateTime requestedDate) {
+ public DefaultProduct[] getProducts(DateTime requestedDate) throws CatalogApiException {
return versionForDate(requestedDate).getCurrentProducts();
}
@Override
- public Currency[] getSupportedCurrencies(DateTime requestedDate) {
+ public Currency[] getSupportedCurrencies(DateTime requestedDate) throws CatalogApiException {
return versionForDate(requestedDate).getCurrentSupportedCurrencies();
}
@Override
- public DefaultPlan[] getPlans(DateTime requestedDate) {
+ public DefaultPlan[] getPlans(DateTime requestedDate) throws CatalogApiException {
return versionForDate(requestedDate).getCurrentPlans();
}
@@ -350,22 +342,22 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
// Static catalog API
//
@Override
- public Date getEffectiveDate() {
+ public Date getEffectiveDate() throws CatalogApiException {
return versionForDate(clock.getUTCNow()).getEffectiveDate();
}
@Override
- public Currency[] getCurrentSupportedCurrencies() {
+ public Currency[] getCurrentSupportedCurrencies() throws CatalogApiException {
return versionForDate(clock.getUTCNow()).getCurrentSupportedCurrencies();
}
@Override
- public Product[] getCurrentProducts() {
+ public Product[] getCurrentProducts() throws CatalogApiException {
return versionForDate(clock.getUTCNow()).getCurrentProducts() ;
}
@Override
- public Plan[] getCurrentPlans() {
+ public Plan[] getCurrentPlans() throws CatalogApiException {
return versionForDate(clock.getUTCNow()).getCurrentPlans();
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java
index 9708b99..0d5ac66 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/io/TestVersionedCatalogLoader.java
@@ -121,9 +121,8 @@ public class TestVersionedCatalogLoader {
@Test(enabled=true)
public void testLoad() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException {
VersionedCatalog c = loader.load(Resources.getResource("versionedCatalog").toString());
- assertEquals(4, c.size());
+ assertEquals(3, c.size());
Iterator<StandaloneCatalog> it = c.iterator();
- it.next(); //discard the baseline
DateTime dt = new DateTime("2011-01-01T00:00:00+00:00");
assertEquals(dt.toDate(),it.next().getEffectiveDate());
dt = new DateTime("2011-02-02T00:00:00+00:00");
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
index 765d4bd..5fc51b8 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockCatalog.java
@@ -19,7 +19,11 @@ package com.ning.billing.catalog;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.catalog.rules.*;
+import com.ning.billing.catalog.rules.CaseCancelPolicy;
+import com.ning.billing.catalog.rules.CaseChangePlanAlignment;
+import com.ning.billing.catalog.rules.CaseChangePlanPolicy;
+import com.ning.billing.catalog.rules.CaseCreateAlignment;
+import com.ning.billing.catalog.rules.PlanRules;
import java.util.Date;
@@ -60,7 +64,7 @@ public class MockCatalog extends StandaloneCatalog {
DefaultProduct[] products = getCurrentProducts();
DefaultPlan[] plans = new DefaultPlan[products.length];
for(int i = 0; i < products.length; i++) {
- DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setReccuringPrice(new DefaultInternationalPrice());
+ DefaultPlanPhase phase = new DefaultPlanPhase().setPhaseType(PhaseType.EVERGREEN).setBillingPeriod(BillingPeriod.MONTHLY).setRecurringPrice(new DefaultInternationalPrice());
plans[i] = new MockPlan().setName(products[i].getName().toLowerCase() + "-plan").setProduct(products[i]).setFinalPhase(phase);
}
setPlans(plans);
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java b/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
index 773c58b..cc3a679 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockInternationalPrice.java
@@ -19,17 +19,16 @@ package com.ning.billing.catalog;
import com.ning.billing.catalog.api.Currency;
import java.math.BigDecimal;
-import java.util.Date;
public class MockInternationalPrice extends DefaultInternationalPrice {
-
- MockInternationalPrice() {
+
+ public MockInternationalPrice() {
setPrices(new DefaultPrice[] {
- new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(1))
+ new DefaultPrice().setCurrency(Currency.USD).setValue(new BigDecimal(1))
});
}
-
- MockInternationalPrice(DefaultPrice... price) {
+
+ public MockInternationalPrice(DefaultPrice... price) {
setPrices(price);
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
index b9e9afe..53c73fe 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlan.java
@@ -34,6 +34,14 @@ public class MockPlan extends DefaultPlan {
setPlansAllowedInBundle(1);
}
+ public MockPlan(String planName) {
+ setName(planName);
+ setProduct(new MockProduct());
+ setFinalPhase(new MockPlanPhase(this));
+ setInitialPhases(null);
+ setPlansAllowedInBundle(1);
+ }
+
public MockPlan(MockPlanPhase mockPlanPhase) {
setName("test-plan");
setProduct(new MockProduct());
diff --git a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
index fb244eb..d4ae5ff 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/MockPlanPhase.java
@@ -18,8 +18,11 @@ package com.ning.billing.catalog;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.TimeUnit;
+import javax.annotation.Nullable;
+
public class MockPlanPhase extends DefaultPlanPhase {
public MockPlanPhase(
@@ -31,16 +34,34 @@ public class MockPlanPhase extends DefaultPlanPhase {
setBillingPeriod(billingPeriod);
setPhaseType(type);
setDuration(duration);
- setReccuringPrice(recurringPrice);
+ setRecurringPrice(recurringPrice);
setFixedPrice(fixedPrice);
}
public MockPlanPhase() {
- setBillingPeriod(BillingPeriod.MONTHLY);
- setPhaseType(PhaseType.EVERGREEN);
+ this(new MockInternationalPrice(), null);
+ }
+
+ public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+ @Nullable MockInternationalPrice fixedPrice) {
+ this(recurringPrice, fixedPrice, BillingPeriod.MONTHLY);
+ }
+
+ public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+ @Nullable MockInternationalPrice fixedPrice,
+ BillingPeriod billingPeriod) {
+ this(recurringPrice, fixedPrice, billingPeriod, PhaseType.EVERGREEN);
+ }
+
+ public MockPlanPhase(@Nullable MockInternationalPrice recurringPrice,
+ @Nullable MockInternationalPrice fixedPrice,
+ BillingPeriod billingPeriod,
+ PhaseType phaseType) {
+ setBillingPeriod(billingPeriod);
+ setPhaseType(phaseType);
setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
- setReccuringPrice(new MockInternationalPrice());
- setFixedPrice(null);
+ setRecurringPrice(recurringPrice);
+ setFixedPrice(fixedPrice);
setPlan(new MockPlan(this));
}
@@ -48,10 +69,17 @@ public class MockPlanPhase extends DefaultPlanPhase {
setBillingPeriod(BillingPeriod.MONTHLY);
setPhaseType(PhaseType.EVERGREEN);
setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
- setReccuringPrice(new MockInternationalPrice());
+ setRecurringPrice(new MockInternationalPrice());
setFixedPrice(null);
setPlan(mockPlan);
}
-
+ public MockPlanPhase(Plan plan, PhaseType phaseType) {
+ setBillingPeriod(BillingPeriod.MONTHLY);
+ setPhaseType(phaseType);
+ setDuration(new DefaultDuration().setNumber(-1).setUnit(TimeUnit.UNLIMITED));
+ setRecurringPrice(new MockInternationalPrice());
+ setFixedPrice(null);
+ setPlan(plan);
+ }
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
index f684503..ef1eb17 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCaseChange.java
@@ -21,9 +21,14 @@ import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.MockCatalog;
import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
-import com.ning.billing.catalog.rules.TestCase.CaseResult;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -39,10 +44,10 @@ public class TestCaseChange {
private Result result;
public CaseChangeResult(DefaultProduct from, DefaultProduct to,
- ProductCategory fromProductCategory, ProductCategory toProductCategory,
- BillingPeriod fromBP, BillingPeriod toBP,
+ ProductCategory fromProductCategory, ProductCategory toProductCategory,
+ BillingPeriod fromBP, BillingPeriod toBP,
DefaultPriceList fromPriceList, DefaultPriceList toPriceList,
- PhaseType fromType,
+ PhaseType fromType,
Result result) {
setFromProduct(from);
setToProduct(to);
@@ -63,7 +68,7 @@ public class TestCaseChange {
}
}
@Test(enabled=true)
- public void testBasic() throws CatalogApiException{
+ public void testBasic() throws CatalogApiException {
MockCatalog cat = new MockCatalog();
DefaultProduct product1 = cat.getCurrentProducts()[0];
@@ -1043,8 +1048,8 @@ public class TestCaseChange {
String fromPriceListName, String toPriceListName,
PhaseType phaseType, StandaloneCatalog cat){
try{
- cr.getResult(new PlanPhaseSpecifier(fromProductName, fromProductCategory, fromBp, fromPriceListName, phaseType),
- new PlanSpecifier(toProductName, toProductCategory, toBp, toPriceListName),cat);
+ cr.getResult(new PlanPhaseSpecifier(fromProductName, fromProductCategory, fromBp, fromPriceListName, phaseType),
+ new PlanSpecifier(toProductName, toProductCategory, toBp, toPriceListName),cat);
Assert.fail("Expecting an exception");
} catch (CatalogApiException e) {
Assert.assertEquals(e.getCode(), ErrorCode.CAT_PRICE_LIST_NOT_FOUND.getCode());
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
index 02f7ab5..1b28da7 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestCasePhase.java
@@ -21,7 +21,11 @@ import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.MockCatalog;
import com.ning.billing.catalog.StandaloneCatalog;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -195,7 +199,7 @@ public class TestCasePhase {
}
@Test(enabled=true)
- public void testOrder() throws CatalogApiException{
+ public void testOrder() throws CatalogApiException {
MockCatalog cat = new MockCatalog();
DefaultProduct product = cat.getCurrentProducts()[0];
diff --git a/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java b/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
index 7ac9cc0..e9617ea 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/rules/TestPlanRules.java
@@ -19,8 +19,17 @@ package com.ning.billing.catalog.rules;
import com.ning.billing.catalog.DefaultPriceList;
import com.ning.billing.catalog.DefaultProduct;
import com.ning.billing.catalog.MockCatalog;
-import com.ning.billing.catalog.api.*;
+import com.ning.billing.catalog.api.ActionPolicy;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.IllegalPlanChange;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.PlanAlignmentChange;
+import com.ning.billing.catalog.api.PlanChangeResult;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PlanSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
index 00dd1b6..e58f71c 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlan.java
@@ -18,17 +18,21 @@ package com.ning.billing.catalog;
import java.util.Date;
+import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.util.config.ValidationErrors;
public class TestPlan {
private static final Logger log = LoggerFactory.getLogger(TestPlan.class);
- @Test
+ @Test(groups={"fast"}, enabled = true)
public void testDateValidation() {
StandaloneCatalog c = new MockCatalog();
@@ -40,4 +44,80 @@ public class TestPlan {
errors.log(log);
}
+
+ private static class MyDuration extends DefaultDuration {
+ final int days;
+
+ public MyDuration(int days) {
+ this.days = days;
+ }
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ return dateTime.plusDays(days);
+ }
+ }
+
+ private static class MyPlanPhase extends MockPlanPhase {
+ Duration duration;
+ boolean recurringPriceIsZero;
+
+ MyPlanPhase(int duration, boolean recurringPriceIsZero) {
+ this.duration= new MyDuration( duration );
+ this.recurringPriceIsZero = recurringPriceIsZero;
+ }
+ @Override
+ public Duration getDuration(){
+ return duration;
+ }
+
+ @Override
+ public InternationalPrice getRecurringPrice() {
+ return new MockInternationalPrice() {
+ @Override
+ public boolean isZero() {
+ return recurringPriceIsZero;
+ }
+ };
+ }
+ }
+
+ @Test(groups={"fast"}, enabled = true)
+ public void testDataCalc() {
+ DefaultPlan p0 = new MockPlan() {
+ public PlanPhase[] getAllPhases() {
+ return new PlanPhase[]{
+ new MyPlanPhase(10, true),
+ new MyPlanPhase(10, false),
+ };
+ }
+ };
+
+ DefaultPlan p1 = new MockPlan() {
+ public PlanPhase[] getAllPhases() {
+ return new PlanPhase[]{
+ new MyPlanPhase(10, true),
+ new MyPlanPhase(10, true),
+ new MyPlanPhase(10, true),
+ new MyPlanPhase(10, true),
+ new MyPlanPhase(10, false),
+ new MyPlanPhase(10, true),
+ };
+ }
+ };
+
+ DefaultPlan p2 = new MockPlan() {
+ public PlanPhase[] getAllPhases() {
+ return new PlanPhase[]{
+ new MyPlanPhase(10, false),
+ new MyPlanPhase(10, true),
+ };
+ }
+ };
+ DateTime requestedDate = new DateTime();
+ Assert.assertEquals(p0.dateOfFirstRecurringNonZeroCharge(requestedDate), requestedDate.plusDays(10));
+ Assert.assertEquals(p1.dateOfFirstRecurringNonZeroCharge(requestedDate), requestedDate.plusDays(40));
+ Assert.assertEquals(p2.dateOfFirstRecurringNonZeroCharge(requestedDate), requestedDate.plusDays(0));
+
+ }
}
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
index d778c4b..9b07bfe 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestPlanPhase.java
@@ -32,17 +32,17 @@ public class TestPlanPhase {
public void testValidation() {
log.info("Testing Plan Phase Validation");
- DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setReccuringPrice(null).setFixedPrice(new DefaultInternationalPrice());
+ DefaultPlanPhase pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.MONTHLY).setRecurringPrice(null).setFixedPrice(new DefaultInternationalPrice());
ValidationErrors errors = pp.validate(new MockCatalog(), new ValidationErrors());
errors.log(log);
Assert.assertEquals(errors.size(), 1);
- pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setReccuringPrice(new MockInternationalPrice());
+ pp = new MockPlanPhase().setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD).setRecurringPrice(new MockInternationalPrice());
errors = pp.validate(new MockCatalog(), new ValidationErrors());
errors.log(log);
Assert.assertEquals(errors.size(), 1);
- pp = new MockPlanPhase().setReccuringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
+ pp = new MockPlanPhase().setRecurringPrice(null).setFixedPrice(null).setBillCycleDuration(BillingPeriod.NO_BILLING_PERIOD);
errors = pp.validate(new MockCatalog(), new ValidationErrors());
errors.log(log);
Assert.assertEquals(errors.size(), 1);
diff --git a/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
index ddd623e..fbc99df 100644
--- a/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
+++ b/catalog/src/test/java/com/ning/billing/catalog/TestVersionedCatalog.java
@@ -35,6 +35,7 @@ import org.testng.annotations.Test;
import org.xml.sax.SAXException;
import com.google.common.io.Resources;
+import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.catalog.api.InvalidConfigException;
@@ -53,14 +54,14 @@ public class TestVersionedCatalog {
vc = loader.load(Resources.getResource("versionedCatalog").toString());
}
- @Test(enabled=true)
+ @Test(groups={"fast"},enabled=true)
public void testAddCatalog() throws MalformedURLException, IOException, SAXException, InvalidConfigException, JAXBException, TransformerException, URISyntaxException, ServiceException, CatalogApiException {
vc.add(new StandaloneCatalog(new Date()));
- assertEquals(5, vc.size());
+ assertEquals(4, vc.size());
}
- @Test(enabled=true)
+ @Test(groups={"fast"},enabled=true)
public void testFindPlanWithDates() throws Exception {
DateTime dt0= new DateTime("2010-01-01T00:00:00+00:00");
DateTime dt1 = new DateTime("2011-01-01T00:01:00+00:00");
@@ -97,6 +98,18 @@ public class TestVersionedCatalog {
Assert.assertEquals(exSubPlan214.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
Assert.assertEquals(exSubPlan3.getAllPhases()[1].getRecurringPrice().getPrice(Currency.USD), new BigDecimal("2.0"));
-
+ }
+
+ @Test(groups={"fast"},enabled=true)
+ public void testErrorOnDateTooEarly() {
+ DateTime dt0= new DateTime("1977-01-01T00:00:00+00:00");
+ try {
+ vc.findPlan("foo", dt0);
+ Assert.fail("Date is too early an exception should have been thrown");
+ } catch (CatalogApiException e) {
+ e.printStackTrace();
+ Assert.assertEquals(e.getCode(), ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE.getCode());
+
+ }
}
}
entitlement/pom.xml 13(+12 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index f0d0aae..f8a781f 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
@@ -48,6 +48,17 @@
<dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-catalog</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-catalog</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
<scope>test</scope>
</dependency>
<dependency>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
index fd130a9..ee9ec81 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/billing/DefaultEntitlementBillingApi.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+w * Copyright 2010-2011 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
@@ -16,16 +16,6 @@
package com.ning.billing.entitlement.api.billing;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.google.inject.Inject;
import com.ning.billing.ErrorCode;
import com.ning.billing.account.api.Account;
@@ -42,18 +32,32 @@ import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.SubscriptionSqlDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
- private Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
-
+ private static final Logger log = LoggerFactory.getLogger(DefaultEntitlementBillingApi.class);
+
private final EntitlementDao dao;
private final AccountUserApi accountApi;
private final CatalogService catalogService;
@Inject
- public DefaultEntitlementBillingApi(EntitlementDao dao, AccountUserApi accountApi, CatalogService catalogService) {
+ public DefaultEntitlementBillingApi(final EntitlementDao dao, final AccountUserApi accountApi, final CatalogService catalogService) {
super();
this.dao = dao;
this.accountApi = accountApi;
@@ -62,48 +66,60 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
@Override
public SortedSet<BillingEvent> getBillingEventsForAccount(
- UUID accountId) {
-
+ final UUID accountId) {
+
List<SubscriptionBundle> bundles = dao.getSubscriptionBundleForAccount(accountId);
List<Subscription> subscriptions = new ArrayList<Subscription>();
- for (SubscriptionBundle bundle: bundles) {
+ for (final SubscriptionBundle bundle: bundles) {
subscriptions.addAll(dao.getSubscriptions(bundle.getId()));
}
- SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
- for (Subscription subscription: subscriptions) {
- for (SubscriptionTransition transition : subscription.getAllTransitions()) {
+ SortedSet<BillingEvent> result = new TreeSet<BillingEvent>();
+ for (final Subscription subscription: subscriptions) {
+ for (final SubscriptionTransition transition : subscription.getAllTransitions()) {
try {
- result.add(new DefaultBillingEvent(transition, subscription, calculateBCD(transition, accountId)));
+ BillingEvent event = new DefaultBillingEvent(transition, subscription, calculateBCD(transition, accountId));
+ result.add(event);
} catch (CatalogApiException e) {
- log.error("Failing to identify catalog components while creating BillingEvent from transition: " +
+ log.error("Failing to identify catalog components while creating BillingEvent from transition: " +
transition.getId().toString(), e);
+ } catch (Exception e) {
+ log.warn("Failed while getting BillingEvent", e);
}
}
}
return result;
}
-
- private int calculateBCD(SubscriptionTransition transition, UUID accountId) throws CatalogApiException {
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ return dao.getAccountIdFromSubscriptionId(subscriptionId);
+ }
+
+ private int calculateBCD(final SubscriptionTransition transition, final UUID accountId) throws CatalogApiException {
Catalog catalog = catalogService.getFullCatalog();
- Plan plan = transition.getNextPlan();
+ Plan plan = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ?
+ transition.getNextPlan() : transition.getPreviousPlan();
Product product = plan.getProduct();
- PlanPhase phase = transition.getNextPhase();
-
+ PlanPhase phase = (transition.getTransitionType() != SubscriptionTransitionType.CANCEL) ?
+ transition.getNextPhase() : transition.getPreviousPhase();
+
BillingAlignment alignment = catalog.billingAlignment(
- new PlanPhaseSpecifier(product.getName(),
- product.getCategory(),
- phase.getBillingPeriod(),
- transition.getNextPriceList(),
- phase.getPhaseType()),
+ new PlanPhaseSpecifier(product.getName(),
+ product.getCategory(),
+ phase.getBillingPeriod(),
+ transition.getNextPriceList(),
+ phase.getPhaseType()),
transition.getRequestedTransitionTime());
int result = 0;
- Account account = accountApi.getAccountById(accountId);
+
+ Account account = accountApi.getAccountById(accountId);
+
switch (alignment) {
- case ACCOUNT :
+ case ACCOUNT :
result = account.getBillCycleDay();
break;
- case BUNDLE :
+ case BUNDLE :
SubscriptionBundle bundle = dao.getSubscriptionBundleFromId(transition.getBundleId());
//TODO result = bundle.getStartDate().toDateTime(account.getTimeZone()).getDayOfMonth();
result = bundle.getStartDate().getDayOfMonth();
@@ -118,20 +134,35 @@ public class DefaultEntitlementBillingApi implements EntitlementBillingApi {
throw new CatalogApiException(ErrorCode.CAT_INVALID_BILLING_ALIGNMENT, alignment.toString());
}
return result;
-
+
}
-
@Override
- public void setChargedThroughDate(UUID subscriptionId, DateTime ctd) {
+ public void setChargedThroughDate(final UUID subscriptionId, final DateTime ctd) {
SubscriptionData subscription = (SubscriptionData) dao.getSubscriptionFromId(subscriptionId);
- if (subscription == null) {
- new EntitlementBillingApiException(String.format("Unknown subscription %s", subscriptionId));
- }
SubscriptionBuilder builder = new SubscriptionBuilder(subscription)
.setChargedThroughDate(ctd)
.setPaidThroughDate(subscription.getPaidThroughDate());
+
dao.updateSubscription(new SubscriptionData(builder));
}
+
+ @Override
+ public void setChargedThroughDateFromTransaction(final Transmogrifier transactionalDao, final UUID subscriptionId, final DateTime ctd) {
+ SubscriptionSqlDao subscriptionSqlDao = transactionalDao.become(SubscriptionSqlDao.class);
+ SubscriptionData subscription = (SubscriptionData) subscriptionSqlDao.getSubscriptionFromId(subscriptionId.toString());
+
+ if (subscription == null) {
+ log.warn("Subscription not found when setting CTD.");
+ } else {
+ Date paidThroughDate = (subscription.getPaidThroughDate() == null) ? null : subscription.getPaidThroughDate().toDate();
+
+ DateTime chargedThroughDate = subscription.getChargedThroughDate();
+ if (chargedThroughDate == null || chargedThroughDate.isBefore(ctd)) {
+ subscriptionSqlDao.updateSubscription(subscriptionId.toString(), subscription.getActiveVersion(),
+ ctd.toDate(), paidThroughDate);
+ }
+ }
+ }
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
index deedb6d..115139a 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
@@ -96,7 +96,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
@Override
public Subscription createSubscription(UUID bundleId, PlanPhaseSpecifier spec, DateTime requestedDate) throws EntitlementUserApiException {
-
try {
String realPriceList = (spec.getPriceListName() == null) ? PriceListSet.DEFAULT_PRICELIST_NAME : spec.getPriceListName();
DateTime now = clock.getUTCNow();
@@ -109,7 +108,6 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
Plan plan = catalogService.getFullCatalog().findPlan(spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);
-
PlanPhase phase = plan.getAllPhases()[0];
if (phase == null) {
throw new EntitlementError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog",
@@ -156,6 +154,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
}
}
+
private void checkAddonCreationRights(SubscriptionData baseSubscription, Plan targetAddOnPlan)
throws EntitlementUserApiException, CatalogApiException {
@@ -173,4 +172,21 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
}
}
+
+ @Override
+ public DateTime getNextBillingDate(UUID accountId) {
+ List<SubscriptionBundle> bundles = getBundlesForAccount(accountId);
+ DateTime result = null;
+ for(SubscriptionBundle bundle : bundles) {
+ List<Subscription> subscriptions = getSubscriptionsForBundle(bundle.getId());
+ for(Subscription subscription : subscriptions) {
+ DateTime chargedThruDate = subscription.getChargedThroughDate();
+ if(result == null ||
+ (chargedThruDate != null && chargedThruDate.isBefore(result))) {
+ result = subscription.getChargedThroughDate();
+ }
+ }
+ }
+ return result;
+ }
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
index 1b95f6b..540f35e 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionApiService.java
@@ -99,10 +99,7 @@ public class SubscriptionApiService {
DateTime now = clock.getUTCNow();
requestedDate = (requestedDate != null) ? DefaultClock.truncateMs(requestedDate) : now;
- // STEPH needs to check if requestedDate is before last 'erasable event'?
- if (requestedDate != null && requestedDate.isAfter(now)) {
- throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
- }
+ validateRequestedDateOnChangeOrCancel(subscription, now, requestedDate);
Plan currentPlan = subscription.getCurrentPlan();
PlanPhaseSpecifier planPhase = new PlanPhaseSpecifier(currentPlan.getProduct().getName(),
@@ -159,6 +156,7 @@ public class SubscriptionApiService {
subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId()), catalogService.getFullCatalog());
}
+
public void changePlan(SubscriptionData subscription, String productName, BillingPeriod term,
String priceList, DateTime requestedDate)
throws EntitlementUserApiException {
@@ -168,10 +166,7 @@ public class SubscriptionApiService {
DateTime now = clock.getUTCNow();
requestedDate = (requestedDate != null) ? DefaultClock.truncateMs(requestedDate) : now;
- // STEPH needs to check if requestedDate is before last 'erasable event'?
- if (requestedDate != null && requestedDate.isAfter(now)) {
- throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE, requestedDate.toString());
- }
+ validateRequestedDateOnChangeOrCancel(subscription, now, requestedDate);
String currentPriceList = subscription.getCurrentPriceList();
@@ -206,7 +201,7 @@ public class SubscriptionApiService {
PriceList newPriceList = planChangeResult.getNewPriceList();
Plan newPlan = catalogService.getFullCatalog().findPlan(productName, term, newPriceList.getName(), requestedDate);
- DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, now);
+ DateTime effectiveDate = subscription.getPlanChangeEffectiveDate(policy, requestedDate);
TimedPhase currentTimedPhase = planAligner.getCurrentTimedPhaseOnChange(subscription, newPlan, newPriceList.getName(), requestedDate, effectiveDate);
@@ -236,4 +231,18 @@ public class SubscriptionApiService {
throw new EntitlementUserApiException(e);
}
}
+
+ private void validateRequestedDateOnChangeOrCancel(SubscriptionData subscription, DateTime now, DateTime requestedDate)
+ throws EntitlementUserApiException {
+
+ if (requestedDate.isAfter(now) ) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_FUTURE_DATE, requestedDate.toString());
+ }
+
+ SubscriptionTransition previousTransition = subscription.getPreviousTransition();
+ if (previousTransition.getEffectiveTransitionTime().isAfter(requestedDate)) {
+ throw new EntitlementUserApiException(ErrorCode.ENT_INVALID_REQUESTED_DATE,
+ requestedDate.toString(), previousTransition.getEffectiveTransitionTime());
+ }
+ }
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 0cee49d..20e4663 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -31,7 +31,12 @@ import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
public class SubscriptionData implements Subscription {
@@ -100,29 +105,29 @@ public class SubscriptionData implements Subscription {
@Override
public SubscriptionState getState() {
- return (transitions == null) ? null : getLatestTranstion().getNextState();
+ return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextState();
}
@Override
public PlanPhase getCurrentPhase() {
- return (transitions == null) ? null : getLatestTranstion().getNextPhase();
+ return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPhase();
}
@Override
public Plan getCurrentPlan() {
- return (transitions == null) ? null : getLatestTranstion().getNextPlan();
+ return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPlan();
}
@Override
public String getCurrentPriceList() {
- return (transitions == null) ? null : getLatestTranstion().getNextPriceList();
+ return (getPreviousTransition() == null) ? null : getPreviousTransition().getNextPriceList();
}
@Override
public DateTime getEndDate() {
- SubscriptionTransition latestTransition = getLatestTranstion();
+ SubscriptionTransition latestTransition = getPreviousTransition();
if (latestTransition.getNextState() == SubscriptionState.CANCELLED) {
return latestTransition.getEffectiveTransitionTime();
}
@@ -184,6 +189,7 @@ public class SubscriptionData implements Subscription {
return result;
}
+ @Override
public SubscriptionTransition getPendingTransition() {
if (transitions == null) {
return null;
@@ -196,7 +202,7 @@ public class SubscriptionData implements Subscription {
return null;
}
- public SubscriptionTransition getLatestTranstion() {
+ public SubscriptionTransition getPreviousTransition() {
if (transitions == null) {
return null;
@@ -236,10 +242,12 @@ public class SubscriptionData implements Subscription {
return bundleStartDate;
}
+ @Override
public DateTime getChargedThroughDate() {
return chargedThroughDate;
}
+ @Override
public DateTime getPaidThroughDate() {
return paidThroughDate;
}
@@ -342,7 +350,7 @@ public class SubscriptionData implements Subscription {
transitions = new LinkedList<SubscriptionTransitionData>();
Plan previousPlan = null;
PlanPhase previousPhase = null;
-
+
for (final EntitlementEvent cur : events) {
if (!cur.isActive() || cur.getActiveVersion() < activeVersion) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index cb37037..c7294b4 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -17,16 +17,18 @@
package com.ning.billing.entitlement.engine.core;
+
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
+
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
-import com.ning.billing.catalog.api.CatalogService;
+
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.config.EntitlementConfig;
@@ -37,8 +39,6 @@ import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.test.DefaultEntitlementTestApi;
-import com.ning.billing.entitlement.api.test.EntitlementTestApi;
import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.entitlement.api.user.Subscription;
@@ -57,12 +57,12 @@ import com.ning.billing.entitlement.exceptions.EntitlementError;
import com.ning.billing.lifecycle.LifecycleHandlerType;
import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.eventbus.EventBus;
-import com.ning.billing.util.eventbus.EventBus.EventBusException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
import com.ning.billing.util.notificationq.NotificationConfig;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
-import com.ning.billing.util.notificationq.NotificationQueueService.NotficationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
public class Engine implements EventListener, EntitlementService {
@@ -70,10 +70,6 @@ public class Engine implements EventListener, EntitlementService {
public static final String NOTIFICATION_QUEUE_NAME = "subscription-events";
public static final String ENTITLEMENT_SERVICE_NAME = "entitlement-service";
- private final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
- private final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
- private final long NANO_TO_MS = (1000 * 1000);
-
private final static Logger log = LoggerFactory.getLogger(Engine.class);
private final Clock clock;
@@ -81,29 +77,26 @@ public class Engine implements EventListener, EntitlementService {
private final PlanAligner planAligner;
private final EntitlementUserApi userApi;
private final EntitlementBillingApi billingApi;
- private final EntitlementTestApi testApi;
private final EntitlementMigrationApi migrationApi;
private final AddonUtils addonUtils;
- private final EventBus eventBus;
+ private final Bus eventBus;
+
private final EntitlementConfig config;
private final NotificationQueueService notificationQueueService;
- private boolean startedNotificationThread;
- private boolean stoppedNotificationThread;
private NotificationQueue subscritionEventQueue;
@Inject
public Engine(Clock clock, EntitlementDao dao, PlanAligner planAligner,
EntitlementConfig config, DefaultEntitlementUserApi userApi,
- DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi,
- DefaultEntitlementMigrationApi migrationApi, AddonUtils addonUtils, EventBus eventBus,
+ DefaultEntitlementBillingApi billingApi,
+ DefaultEntitlementMigrationApi migrationApi, AddonUtils addonUtils, Bus eventBus,
NotificationQueueService notificationQueueService) {
super();
this.clock = clock;
this.dao = dao;
this.planAligner = planAligner;
this.userApi = userApi;
- this.testApi = testApi;
this.billingApi = billingApi;
this.migrationApi = migrationApi;
this.addonUtils = addonUtils;
@@ -121,13 +114,11 @@ public class Engine implements EventListener, EntitlementService {
public void initialize() {
try {
- this.stoppedNotificationThread = false;
- this.startedNotificationThread = false;
subscritionEventQueue = notificationQueueService.createNotificationQueue(ENTITLEMENT_SERVICE_NAME,
NOTIFICATION_QUEUE_NAME,
new NotificationQueueHandler() {
@Override
- public void handleReadyNotification(String notificationKey) {
+ public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
EntitlementEvent event = dao.getEventById(UUID.fromString(notificationKey));
if (event == null) {
log.warn("Failed to extract event for notification key {}", notificationKey);
@@ -135,21 +126,6 @@ public class Engine implements EventListener, EntitlementService {
processEventReady(event);
}
}
-
- @Override
- public void completedQueueStop() {
- synchronized (this) {
- stoppedNotificationThread = true;
- this.notifyAll();
- }
- }
- @Override
- public void completedQueueStart() {
- synchronized (this) {
- startedNotificationThread = true;
- this.notifyAll();
- }
- }
},
new NotificationConfig() {
@Override
@@ -169,7 +145,7 @@ public class Engine implements EventListener, EntitlementService {
return config.getDaoMaxReadyEvents();
}
});
- } catch (NotficationQueueAlreadyExists e) {
+ } catch (NotificationQueueAlreadyExists e) {
throw new RuntimeException(e);
}
}
@@ -177,16 +153,13 @@ public class Engine implements EventListener, EntitlementService {
@LifecycleHandlerType(LifecycleLevel.START_SERVICE)
public void start() {
subscritionEventQueue.startQueue();
- waitForNotificationStartCompletion();
}
@LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
public void stop() {
if (subscritionEventQueue != null) {
subscritionEventQueue.stopQueue();
- waitForNotificationStopCompletion();
- }
- startedNotificationThread = false;
+ }
}
@Override
@@ -201,11 +174,6 @@ public class Engine implements EventListener, EntitlementService {
@Override
- public EntitlementTestApi getTestApi() {
- return testApi;
- }
-
- @Override
public EntitlementMigrationApi getMigrationApi() {
return migrationApi;
}
@@ -237,42 +205,6 @@ public class Engine implements EventListener, EntitlementService {
}
}
- private void waitForNotificationStartCompletion() {
- waitForNotificationEventCompletion(true);
- }
-
- private void waitForNotificationStopCompletion() {
- waitForNotificationEventCompletion(false);
- }
-
- private void waitForNotificationEventCompletion(boolean startEvent) {
-
- long ini = System.nanoTime();
- synchronized(this) {
- do {
- if ((startEvent ? startedNotificationThread : stoppedNotificationThread)) {
- break;
- }
- try {
- this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
- } catch (InterruptedException e ) {
- Thread.currentThread().interrupt();
- throw new EntitlementError(e);
- }
- } while (!(startEvent ? startedNotificationThread : stoppedNotificationThread) &&
- (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
-
- if (!(startEvent ? startedNotificationThread : stoppedNotificationThread)) {
- log.error("Could not {} notification thread in {} msec !!!",
- (startEvent ? "start" : "stop"),
- MAX_NOTIFICATION_THREAD_WAIT_MS);
- throw new EntitlementError("Failed to start service!!");
- }
- log.info("Notification thread has been {} in {} ms",
- (startEvent ? "started" : "stopped"),
- (System.nanoTime() - ini) / NANO_TO_MS);
- }
- }
private void onPhaseEvent(SubscriptionData subscription) {
try {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
index ea62b84..c9ddf90 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementDao.java
@@ -16,7 +16,9 @@
package com.ning.billing.entitlement.engine.dao;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
@@ -40,6 +42,8 @@ public interface EntitlementDao {
public Subscription getSubscriptionFromId(UUID subscriptionId);
+ // Account retrieval
+ public UUID getAccountIdFromSubscriptionId(UUID subscriptionId);
// Subscription retrieval
public Subscription getBaseSubscription(UUID bundleId);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
index df3ee92..062a0d5 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
@@ -21,9 +21,18 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
-
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
@@ -44,14 +53,7 @@ import com.ning.billing.util.notificationq.NotificationKey;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
-
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import sun.jkernel.Bundle;
public class EntitlementSqlDao implements EntitlementDao {
@@ -66,7 +68,8 @@ public class EntitlementSqlDao implements EntitlementDao {
private final NotificationQueueService notificationQueueService;
@Inject
- public EntitlementSqlDao(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+ public EntitlementSqlDao(final IDBI dbi, final Clock clock, final SubscriptionFactory factory,
+ final NotificationQueueService notificationQueueService) {
this.clock = clock;
this.factory = factory;
this.subscriptionsDao = dbi.onDemand(SubscriptionSqlDao.class);
@@ -76,18 +79,18 @@ public class EntitlementSqlDao implements EntitlementDao {
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+ public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
return bundlesDao.getBundleFromKey(bundleKey);
}
@Override
public List<SubscriptionBundle> getSubscriptionBundleForAccount(
- UUID accountId) {
+ final UUID accountId) {
return bundlesDao.getBundleFromAccount(accountId.toString());
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+ public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
return bundlesDao.getBundleFromId(bundleId.toString());
}
@@ -103,11 +106,34 @@ public class EntitlementSqlDao implements EntitlementDao {
}
@Override
- public Subscription getSubscriptionFromId(UUID subscriptionId) {
+ public Subscription getSubscriptionFromId(final UUID subscriptionId) {
return buildSubscription(subscriptionsDao.getSubscriptionFromId(subscriptionId.toString()));
}
@Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ Subscription subscription = subscriptionsDao.getSubscriptionFromId(subscriptionId.toString());
+ if (subscription == null) {
+ log.error(String.format(ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getFormat(), subscriptionId.toString()));
+ return null;
+ }
+
+ UUID bundleId = subscription.getBundleId();
+ if (bundleId == null) {
+ log.error(String.format(ErrorCode.ENT_GET_NO_BUNDLE_FOR_SUBSCRIPTION.getFormat(), subscriptionId.toString()));
+ return null;
+ }
+
+ SubscriptionBundle bundle = bundlesDao.getBundleFromId(bundleId.toString());
+ if (bundle == null) {
+ log.error(String.format(ErrorCode.ENT_GET_INVALID_BUNDLE_ID.getFormat(), bundleId.toString()));
+ return null;
+ }
+
+ return bundle.getAccountId();
+ }
+
+ @Override
public Subscription getBaseSubscription(final UUID bundleId) {
List<Subscription> subscriptions = subscriptionsDao.getSubscriptionsFromBundleId(bundleId.toString());
@@ -194,9 +220,9 @@ public class EntitlementSqlDao implements EntitlementDao {
dao.insertSubscription(subscription);
// STEPH batch as well
- EventSqlDao eventsDaoFromSameTranscation = dao.become(EventSqlDao.class);
+ EventSqlDao eventsDaoFromSameTransaction = dao.become(EventSqlDao.class);
for (final EntitlementEvent cur : initialEvents) {
- eventsDaoFromSameTranscation.insertEvent(cur);
+ eventsDaoFromSameTransaction.insertEvent(cur);
recordFutureNotificationFromTransaction(dao,
cur.getEffectiveDate(),
new NotificationKey() {
@@ -418,9 +444,9 @@ public class EntitlementSqlDao implements EntitlementDao {
private void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao, final DateTime effectiveDate, final NotificationKey notificationKey) {
try {
- NotificationQueue subscritionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
+ NotificationQueue subscriptionEventQueue = notificationQueueService.getNotificationQueue(Engine.ENTITLEMENT_SERVICE_NAME,
Engine.NOTIFICATION_QUEUE_NAME);
- subscritionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
+ subscriptionEventQueue.recordFutureNotificationFromTransaction(transactionalDao, effectiveDate, notificationKey);
} catch (NoSuchNotificationQueue e) {
throw new RuntimeException(e);
}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
index 0135ebf..704b765 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
@@ -16,6 +16,8 @@
package com.ning.billing.entitlement.glue;
+import org.skife.config.ConfigurationObjectFactory;
+
import com.google.inject.AbstractModule;
import com.ning.billing.config.EntitlementConfig;
import com.ning.billing.entitlement.alignment.MigrationPlanAligner;
@@ -25,8 +27,6 @@ import com.ning.billing.entitlement.api.billing.DefaultEntitlementBillingApi;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
import com.ning.billing.entitlement.api.migration.DefaultEntitlementMigrationApi;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
-import com.ning.billing.entitlement.api.test.DefaultEntitlementTestApi;
-import com.ning.billing.entitlement.api.test.EntitlementTestApi;
import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
import com.ning.billing.entitlement.api.user.SubscriptionApiService;
@@ -34,24 +34,18 @@ import com.ning.billing.entitlement.engine.addon.AddonUtils;
import com.ning.billing.entitlement.engine.core.Engine;
import com.ning.billing.entitlement.engine.dao.EntitlementDao;
import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.DefaultClock;
-import org.skife.config.ConfigurationObjectFactory;
public class EntitlementModule extends AbstractModule {
- protected void installClock() {
- bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
- }
-
protected void installConfig() {
final EntitlementConfig config = new ConfigurationObjectFactory(System.getProperties()).build(EntitlementConfig.class);
bind(EntitlementConfig.class).toInstance(config);
}
+
protected void installEntitlementDao() {
bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
}
@@ -63,7 +57,6 @@ public class EntitlementModule extends AbstractModule {
bind(PlanAligner.class).asEagerSingleton();
bind(AddonUtils.class).asEagerSingleton();
bind(MigrationPlanAligner.class).asEagerSingleton();
- bind(EntitlementTestApi.class).to(DefaultEntitlementTestApi.class).asEagerSingleton();
bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();
bind(EntitlementBillingApi.class).to(DefaultEntitlementBillingApi.class).asEagerSingleton();
bind(EntitlementMigrationApi.class).to(DefaultEntitlementMigrationApi.class).asEagerSingleton();
@@ -72,7 +65,6 @@ public class EntitlementModule extends AbstractModule {
@Override
protected void configure() {
installConfig();
- installClock();
installEntitlementDao();
installEntitlementCore();
}
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
index 55ad7f4..dfdc746 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/ddl.sql
@@ -1,5 +1,5 @@
-DROP TABLE IF EXISTS events;
-CREATE TABLE events (
+DROP TABLE IF EXISTS entitlement_events;
+CREATE TABLE entitlement_events (
id int(11) unsigned NOT NULL AUTO_INCREMENT,
event_id char(36) NOT NULL,
event_type varchar(9) NOT NULL,
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
index 704e2c7..10f565d 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EventSqlDao.sql.stg
@@ -15,14 +15,14 @@ getEventById(event_id) ::= <<
, plist_name
, current_version
, is_active
- from events
+ from entitlement_events
where
event_id = :event_id
;
>>
insertEvent() ::= <<
- insert into events (
+ insert into entitlement_events (
event_id
, event_type
, user_type
@@ -54,14 +54,14 @@ insertEvent() ::= <<
>>
removeEvents(subscription_id) ::= <<
- delete from events
+ delete from entitlement_events
where
subscription_id = :subscription_id
;
>>
unactiveEvent(event_id, now) ::= <<
- update events
+ update entitlement_events
set
is_active = 0
, updated_dt = :now
@@ -71,7 +71,7 @@ unactiveEvent(event_id, now) ::= <<
>>
reactiveEvent(event_id, now) ::= <<
- update events
+ update entitlement_events
set
is_active = 1
, updated_dt = :now
@@ -95,7 +95,7 @@ getFutureActiveEventForSubscription(subscription_id, now) ::= <<
, plist_name
, current_version
, is_active
- from events
+ from entitlement_events
where
subscription_id = :subscription_id
and is_active = 1
@@ -123,7 +123,7 @@ getEventsForSubscription(subscription_id) ::= <<
, plist_name
, current_version
, is_active
- from events
+ from entitlement_events
where
subscription_id = :subscription_id
order by
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
index 6bee471..39f6c48 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java
@@ -19,7 +19,7 @@ package com.ning.billing.entitlement.api;
import com.google.common.base.Joiner;
import com.google.common.eventbus.Subscribe;
import com.ning.billing.entitlement.api.user.SubscriptionTransition;
-import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.bus.Bus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +45,7 @@ public class ApiTestListener {
PHASE
}
- public ApiTestListener(EventBus eventBus) {
+ public ApiTestListener(Bus eventBus) {
this.nextExpectedEvent = new Stack<NextEvent>();
this.completed = false;
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java
index cc08699..73b7369 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccount.java
@@ -16,17 +16,17 @@
package com.ning.billing.entitlement.api.billing;
-import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import com.ning.billing.account.api.Account;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.tag.Tag;
-import com.ning.billing.util.tag.TagDescription;
+import com.ning.billing.util.tag.TagDefinition;
public class BrainDeadAccount implements Account {
@@ -69,7 +69,52 @@ public class BrainDeadAccount implements Account {
throw new UnsupportedOperationException();
}
- @Override
+ @Override
+ public DateTimeZone getTimeZone() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getLocale() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getAddress1() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getAddress2() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getCompanyName() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getCity() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getStateOrProvince() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPostalCode() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getCountry() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public String getFieldValue(String fieldName) {
throw new UnsupportedOperationException();
}
@@ -109,13 +154,12 @@ public class BrainDeadAccount implements Account {
throw new UnsupportedOperationException();
}
- @Override
- public void addTag(TagDescription description, String addedBy,
- DateTime dateAdded) {
- throw new UnsupportedOperationException();
- }
+ @Override
+ public void addTag(final TagDefinition definition, final String addedBy, final DateTime dateAdded) {
+ throw new UnsupportedOperationException();
+ }
- @Override
+ @Override
public void addTags(List<Tag> tags) {
throw new UnsupportedOperationException();
}
@@ -126,7 +170,7 @@ public class BrainDeadAccount implements Account {
}
@Override
- public void removeTag(TagDescription description) {
+ public void removeTag(TagDefinition definition) {
throw new UnsupportedOperationException();
}
@@ -139,16 +183,20 @@ public class BrainDeadAccount implements Account {
public boolean processPayment() {
throw new UnsupportedOperationException();
}
-
- @Override
- public BigDecimal getBalance() {
- throw new UnsupportedOperationException();
- }
-
@Override
public void addFields(List<CustomField> fields) {
throw new UnsupportedOperationException();
-
+
}
+ @Override
+ public DateTime getCreatedDate() {
+ return new DateTime(DateTimeZone.UTC);
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return new DateTime(DateTimeZone.UTC);
+ }
+
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
index 94f0ea7..4602fe6 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadAccountUserApi.java
@@ -23,6 +23,7 @@ import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountData;
import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.account.api.MigrationAccountData;
import com.ning.billing.util.customfield.CustomField;
import com.ning.billing.util.tag.Tag;
@@ -60,4 +61,23 @@ public class BrainDeadAccountUserApi implements AccountUserApi {
throw new UnsupportedOperationException();
}
+ @Override
+ public void deleteAccountByKey(String externalKey)
+ throws AccountApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Account migrateAccount(MigrationAccountData data,
+ List<CustomField> fields, List<Tag> tags)
+ throws AccountApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void updateAccount(String key, AccountData accountData)
+ throws AccountApiException {
+ throw new UnsupportedOperationException();
+ }
+
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
index 94fd234..4d57dac 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadMockEntitlementDao.java
@@ -16,11 +16,10 @@
package com.ning.billing.entitlement.api.billing;
-import java.util.Collection;
import java.util.List;
import java.util.UUID;
-
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
@@ -32,110 +31,112 @@ class BrainDeadMockEntitlementDao implements EntitlementDao {
@Override
public List<SubscriptionBundle> getSubscriptionBundleForAccount(
- UUID accountId) {
+ final UUID accountId) {
throw new UnsupportedOperationException();
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+ public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
throw new UnsupportedOperationException();
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+ public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
throw new UnsupportedOperationException();
}
@Override
public SubscriptionBundle createSubscriptionBundle(
- SubscriptionBundleData bundle) {
+ final SubscriptionBundleData bundle) {
throw new UnsupportedOperationException();
}
@Override
- public Subscription getSubscriptionFromId(UUID subscriptionId) {
+ public Subscription getSubscriptionFromId(final UUID subscriptionId) {
throw new UnsupportedOperationException();
}
- @Override
- public Subscription getBaseSubscription(UUID bundleId) {
- throw new UnsupportedOperationException();
+ @Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public Subscription getBaseSubscription(final UUID bundleId) {
+ throw new UnsupportedOperationException();
}
@Override
- public List<Subscription> getSubscriptions(UUID bundleId) {
+ public List<Subscription> getSubscriptions(final UUID bundleId) {
throw new UnsupportedOperationException();
-
}
@Override
- public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+ public List<Subscription> getSubscriptionsForKey(final String bundleKey) {
throw new UnsupportedOperationException();
}
@Override
- public void updateSubscription(SubscriptionData subscription) {
+ public void updateSubscription(final SubscriptionData subscription) {
throw new UnsupportedOperationException();
}
@Override
- public void createNextPhaseEvent(UUID subscriptionId,
- EntitlementEvent nextPhase) {
+ public void createNextPhaseEvent(final UUID subscriptionId,
+ final EntitlementEvent nextPhase) {
throw new UnsupportedOperationException();
}
@Override
public List<EntitlementEvent> getEventsForSubscription(
- UUID subscriptionId) {
+ final UUID subscriptionId) {
throw new UnsupportedOperationException();
}
@Override
public List<EntitlementEvent> getPendingEventsForSubscription(
- UUID subscriptionId) {
+ final UUID subscriptionId) {
throw new UnsupportedOperationException();
}
-
@Override
- public void createSubscription(SubscriptionData subscription,
- List<EntitlementEvent> initialEvents) {
+ public void createSubscription(final SubscriptionData subscription,
+ final List<EntitlementEvent> initialEvents) {
throw new UnsupportedOperationException();
}
@Override
- public void cancelSubscription(UUID subscriptionId,
- EntitlementEvent cancelEvent) {
+ public void cancelSubscription(final UUID subscriptionId,
+ final EntitlementEvent cancelEvent) {
throw new UnsupportedOperationException();
-
}
@Override
- public void uncancelSubscription(UUID subscriptionId,
- List<EntitlementEvent> uncancelEvents) {
+ public void uncancelSubscription(final UUID subscriptionId,
+ final List<EntitlementEvent> uncancelEvents) {
throw new UnsupportedOperationException();
}
@Override
- public void changePlan(UUID subscriptionId,
- List<EntitlementEvent> changeEvents) {
+ public void changePlan(final UUID subscriptionId,
+ final List<EntitlementEvent> changeEvents) {
throw new UnsupportedOperationException();
}
@Override
- public void migrate(UUID acountId, AccountMigrationData data) {
+ public void migrate(final UUID acountId, final AccountMigrationData data) {
throw new UnsupportedOperationException();
}
@Override
- public void undoMigration(UUID accountId) {
+ public void undoMigration(final UUID accountId) {
throw new UnsupportedOperationException();
}
+
@Override
- public EntitlementEvent getEventById(UUID eventId) {
+ public EntitlementEvent getEventById(final UUID eventId) {
throw new UnsupportedOperationException();
}
}
\ No newline at end of file
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
new file mode 100644
index 0000000..98cc376
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/BrainDeadSubscription.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.api.billing;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+
+public class BrainDeadSubscription implements Subscription {
+
+ @Override
+ public void cancel(DateTime requestedDate, boolean eot)
+ throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void uncancel() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void changePlan(String productName, BillingPeriod term,
+ String planSet, DateTime requestedDate)
+ throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+
+
+ }
+
+ @Override
+ public void pause() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+
+
+ }
+
+ @Override
+ public void resume() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public UUID getId() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public UUID getBundleId() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public SubscriptionState getState() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getStartDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getEndDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public Plan getCurrentPlan() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public String getCurrentPriceList() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public PlanPhase getCurrentPhase() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getChargedThroughDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getPaidThroughDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<SubscriptionTransition> getActiveTransitions() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<SubscriptionTransition> getAllTransitions() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public SubscriptionTransition getPendingTransition() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public SubscriptionTransition getPreviousTransition() {
+ return null;
+ }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
new file mode 100644
index 0000000..100d184
--- /dev/null
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultBillingEvent.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.api.billing;
+
+import java.math.BigDecimal;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
+
+public class TestDefaultBillingEvent {
+ public static final UUID ID_ZERO = new UUID(0L,0L);
+ public static final UUID ID_ONE = new UUID(0L,1L);
+ public static final UUID ID_TWO = new UUID(0L,2L);
+
+ @Test(groups={"fast"})
+ public void testEventOrderingSubscription() {
+
+ BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+ BillingEvent event1 = createEvent(subscription(ID_ONE), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+ BillingEvent event2 = createEvent(subscription(ID_TWO), new DateTime("2012-01-31T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+
+ SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+ set.add(event2);
+ set.add(event1);
+ set.add(event0);
+
+ Iterator<BillingEvent> it = set.iterator();
+
+ Assert.assertEquals(event0, it.next());
+ Assert.assertEquals(event1, it.next());
+ Assert.assertEquals(event2, it.next());
+ }
+
+ @Test(groups={"fast"})
+ public void testEventOrderingDate() {
+
+ BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+ BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-02-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+ BillingEvent event2 = createEvent(subscription(ID_ZERO), new DateTime("2012-03-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+
+ SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+ set.add(event2);
+ set.add(event1);
+ set.add(event0);
+
+ Iterator<BillingEvent> it = set.iterator();
+
+ Assert.assertEquals(event0, it.next());
+ Assert.assertEquals(event1, it.next());
+ Assert.assertEquals(event2, it.next());
+ }
+
+ @Test(groups={"fast"})
+ public void testEventOrderingType() {
+
+ BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+ BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CHANGE);
+ BillingEvent event2 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CANCEL);
+
+ SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+ set.add(event2);
+ set.add(event1);
+ set.add(event0);
+
+ Iterator<BillingEvent> it = set.iterator();
+
+ Assert.assertEquals(event0, it.next());
+ Assert.assertEquals(event1, it.next());
+ Assert.assertEquals(event2, it.next());
+ }
+
+ @Test(groups={"fast"})
+ public void testEventOrderingMix() {
+
+ BillingEvent event0 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CREATE);
+ BillingEvent event1 = createEvent(subscription(ID_ZERO), new DateTime("2012-01-02T00:02:04.000Z"), SubscriptionTransitionType.CHANGE);
+ BillingEvent event2 = createEvent(subscription(ID_ONE), new DateTime("2012-01-01T00:02:04.000Z"), SubscriptionTransitionType.CANCEL);
+
+ SortedSet<BillingEvent> set = new TreeSet<BillingEvent>();
+ set.add(event2);
+ set.add(event1);
+ set.add(event0);
+
+ Iterator<BillingEvent> it = set.iterator();
+
+ Assert.assertEquals(event0, it.next());
+ Assert.assertEquals(event1, it.next());
+ Assert.assertEquals(event2, it.next());
+ }
+
+
+ private BillingEvent createEvent(Subscription sub, DateTime effectiveDate, SubscriptionTransitionType type) {
+ InternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(BigDecimal.ZERO, Currency.USD));
+ int billCycleDay = 1;
+
+ Plan shotgun = new MockPlan();
+ PlanPhase shotgunMonthly = createMockMonthlyPlanPhase(null, BigDecimal.ZERO, PhaseType.TRIAL);
+
+ return new DefaultBillingEvent(sub , effectiveDate,
+ shotgun, shotgunMonthly,
+ zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, billCycleDay,
+ BillingModeType.IN_ADVANCE, "Test Event 1", type);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate,
+ final BigDecimal fixedRate, PhaseType phaseType) {
+ return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+ new MockInternationalPrice(new DefaultPrice(fixedRate, Currency.USD)),
+ BillingPeriod.MONTHLY, phaseType);
+ }
+
+ private Subscription subscription(final UUID id) {
+ return new BrainDeadSubscription() {
+ public UUID getId() {
+ return id;
+ }
+ };
+ }
+
+}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
index 08b05d6..91ddc91 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/billing/TestDefaultEntitlementBillingApi.java
@@ -28,7 +28,6 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
-import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
@@ -43,6 +42,7 @@ import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.glue.CatalogModule;
import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
@@ -56,7 +56,9 @@ import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.lifecycle.KillbillService.ServiceException;
import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.glue.ClockModule;
+
+import static org.testng.Assert.assertTrue;
public class TestDefaultEntitlementBillingApi {
private static final UUID zeroId = new UUID(0L,0L);
@@ -76,11 +78,7 @@ public class TestDefaultEntitlementBillingApi {
@BeforeClass(groups={"setup"})
public void setup() throws ServiceException {
TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
- final Injector g = Guice.createInjector(Stage.PRODUCTION, new CatalogModule(), new AbstractModule() {
- protected void configure() {
- bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
- }
- });
+ final Injector g = Guice.createInjector(Stage.PRODUCTION, new CatalogModule(), new ClockModule());
catalogService = g.getInstance(CatalogService.class);
@@ -89,7 +87,7 @@ public class TestDefaultEntitlementBillingApi {
((DefaultCatalogService)catalogService).loadCatalog();
}
- @BeforeMethod
+ @BeforeMethod(alwaysRun=true)
public void setupEveryTime() {
bundles = new ArrayList<SubscriptionBundle>();
final SubscriptionBundle bundle = new SubscriptionBundleData( zeroId,"TestKey", oneId, new DateTime().minusDays(4));
@@ -97,13 +95,11 @@ public class TestDefaultEntitlementBillingApi {
transitions = new ArrayList<SubscriptionTransition>();
-
-
subscriptions = new ArrayList<Subscription>();
SubscriptionBuilder builder = new SubscriptionBuilder();
subscriptionStartDate = new DateTime().minusDays(3);
- builder.setStartDate(subscriptionStartDate);
+ builder.setStartDate(subscriptionStartDate).setId(oneId);
subscription = new SubscriptionData(builder) {
public List<SubscriptionTransition> getAllTransitions() {
return transitions;
@@ -127,33 +123,42 @@ public class TestDefaultEntitlementBillingApi {
return subscription;
}
-
- @Override
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
return bundle;
}
-
-
};
+ assertTrue(true);
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsEmpty() {
EntitlementDao dao = new BrainDeadMockEntitlementDao() {
public List<SubscriptionBundle> getSubscriptionBundleForAccount(
UUID accountId) {
return new ArrayList<SubscriptionBundle>();
}
-
- };
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+
+ };
AccountUserApi accountApi = new BrainDeadAccountUserApi() ;
DefaultEntitlementBillingApi api = new DefaultEntitlementBillingApi(dao,accountApi,catalogService);
SortedSet<BillingEvent> events = api.getBillingEventsForAccount(new UUID(0L,0L));
Assert.assertEquals(events.size(), 0);
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsNoBillingPeriod() throws CatalogApiException {
DateTime now = clock.getUTCNow();
DateTime then = now.minusDays(1);
@@ -178,7 +183,7 @@ public class TestDefaultEntitlementBillingApi {
checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsAnual() throws CatalogApiException {
DateTime now = clock.getUTCNow();
DateTime then = now.minusDays(1);
@@ -203,11 +208,11 @@ public class TestDefaultEntitlementBillingApi {
checkFirstEvent(events, nextPlan, subscription.getStartDate().getDayOfMonth(), oneId, now, nextPhase, ApiEventType.CREATE.toString());
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsMonthly() throws CatalogApiException {
DateTime now = clock.getUTCNow();
DateTime then = now.minusDays(1);
- Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-monthly", now);
PlanPhase nextPhase = nextPlan.getAllPhases()[1];
String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
SubscriptionTransition t = new SubscriptionTransitionData(
@@ -228,11 +233,11 @@ public class TestDefaultEntitlementBillingApi {
checkFirstEvent(events, nextPlan, 32, oneId, now, nextPhase, ApiEventType.CREATE.toString());
}
- @Test
+ @Test(enabled=true, groups="fast")
public void testBillingEventsAddOn() throws CatalogApiException {
DateTime now = clock.getUTCNow();
DateTime then = now.minusDays(1);
- Plan nextPlan = catalogService.getFullCatalog().findPlan("shotgun-annual", now);
+ Plan nextPlan = catalogService.getFullCatalog().findPlan("laser-scope-monthly", now);
PlanPhase nextPhase = nextPlan.getAllPhases()[0];
String nextPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
SubscriptionTransition t = new SubscriptionTransitionData(
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
index 271de3c..7ef459d 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
@@ -28,28 +28,22 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.testng.Assert;
-import org.testng.annotations.Test;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Duration;
import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.PlanPhaseSpecifier;
import com.ning.billing.catalog.api.PriceListSet;
import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.TestApiBase;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
-import com.ning.billing.entitlement.api.migration.EntitlementMigrationApiException;
+import com.ning.billing.entitlement.api.TestApiBase;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementAccountMigration;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementBundleMigration;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementSubscriptionMigration;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementSubscriptionMigrationCase;
import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.entitlement.api.user.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
-import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
public abstract class TestMigration extends TestApiBase {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index 30df1f9..da06cb5 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
@@ -16,12 +16,18 @@
package com.ning.billing.entitlement.api;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
import java.io.IOException;
-import java.lang.reflect.Method;
import java.net.URL;
import java.util.List;
import java.util.UUID;
+
import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
@@ -29,6 +35,7 @@ import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
+
import com.google.inject.Injector;
import com.ning.billing.account.api.AccountData;
import com.ning.billing.catalog.DefaultCatalogService;
@@ -43,7 +50,6 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.config.EntitlementConfig;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
-import com.ning.billing.entitlement.api.EntitlementService;
import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
import com.ning.billing.entitlement.api.user.EntitlementUserApi;
@@ -61,13 +67,8 @@ import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.lifecycle.KillbillService.ServiceException;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.BusService;
public abstract class TestApiBase {
@@ -86,7 +87,7 @@ public abstract class TestApiBase {
protected EntitlementConfig config;
protected EntitlementDao dao;
protected ClockMock clock;
- protected EventBusService busService;
+ protected BusService busService;
protected AccountData accountData;
protected Catalog catalog;
@@ -108,8 +109,8 @@ public abstract class TestApiBase {
@AfterClass(groups={"setup"})
public void tearDown() {
try {
- busService.getEventBus().register(testListener);
- ((DefaultEventBusService) busService).stopBus();
+ busService.getBus().register(testListener);
+ ((DefaultBusService) busService).stopBus();
} catch (Exception e) {
log.warn("Failed to tearDown test properly ", e);
}
@@ -124,14 +125,13 @@ public abstract class TestApiBase {
entitlementService = g.getInstance(EntitlementService.class);
catalogService = g.getInstance(CatalogService.class);
- busService = g.getInstance(EventBusService.class);
+ busService = g.getInstance(BusService.class);
config = g.getInstance(EntitlementConfig.class);
dao = g.getInstance(EntitlementDao.class);
clock = (ClockMock) g.getInstance(Clock.class);
try {
-
((DefaultCatalogService) catalogService).loadCatalog();
- ((DefaultEventBusService) busService).startBus();
+ ((DefaultBusService) busService).startBus();
((Engine) entitlementService).initialize();
init();
} catch (EntitlementUserApiException e) {
@@ -151,7 +151,7 @@ public abstract class TestApiBase {
assertNotNull(catalog);
- testListener = new ApiTestListener(busService.getEventBus());
+ testListener = new ApiTestListener(busService.getBus());
entitlementApi = entitlementService.getUserApi();
billingApi = entitlementService.getBillingApi();
migrationApi = entitlementService.getMigrationApi();
@@ -169,7 +169,7 @@ public abstract class TestApiBase {
clock.resetDeltaFromReality();
((MockEntitlementDao) dao).reset();
try {
- busService.getEventBus().register(testListener);
+ busService.getBus().register(testListener);
UUID accountId = UUID.randomUUID();
bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle");
} catch (Exception e) {
@@ -244,6 +244,11 @@ public abstract class TestApiBase {
public int getNumber() {
return days;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ return null;
+ }
};
return result;
}
@@ -258,6 +263,11 @@ public abstract class TestApiBase {
public int getNumber() {
return months;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
};
return result;
}
@@ -273,6 +283,11 @@ public abstract class TestApiBase {
public int getNumber() {
return years;
}
+
+ @Override
+ public DateTime addToDateTime(DateTime dateTime) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
};
return result;
}
@@ -293,26 +308,75 @@ public abstract class TestApiBase {
public String getEmail() {
return "accountName@yahoo.com";
}
+
@Override
public String getPhone() {
return "4152876341";
}
+
@Override
public String getExternalKey() {
return "k123456";
}
+
@Override
public int getBillCycleDay() {
return 1;
}
+
@Override
public Currency getCurrency() {
return Currency.USD;
}
+
@Override
public String getPaymentProviderName() {
return "Paypal";
}
+ @Override
+ public DateTimeZone getTimeZone() {
+ return DateTimeZone.forID("Europe/Paris");
+ }
+
+ @Override
+ public String getLocale() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getAddress1() {
+ return null;
+ }
+
+ @Override
+ public String getAddress2() {
+ return null;
+ }
+
+ @Override
+ public String getCompanyName() {
+ return null;
+ }
+
+ @Override
+ public String getCity() {
+ return null;
+ }
+
+ @Override
+ public String getStateOrProvince() {
+ return null;
+ }
+
+ @Override
+ public String getPostalCode() {
+ return null;
+ }
+
+ @Override
+ public String getCountry() {
+ return null;
+ }
};
return accountData;
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
index 3326bc8..1e725bf 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
@@ -22,6 +22,7 @@ import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertFalse;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
import org.testng.Assert;
@@ -80,7 +81,7 @@ public abstract class TestUserApiCancel extends TestApiBase {
}
- protected void testCancelSubscriptionEOTWithChargeThroughDate() {
+ protected void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
log.info("Starting testCancelSubscriptionEOTWithChargeThroughDate");
try {
@@ -176,7 +177,7 @@ public abstract class TestUserApiCancel extends TestApiBase {
// Similar test to testCancelSubscriptionEOTWithChargeThroughDate except we uncancel and check things
// are as they used to be and we can move forward without hitting cancellation
//
- protected void testUncancel() {
+ protected void testUncancel() throws EntitlementBillingApiException {
log.info("Starting testUncancel");
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
index 630d925..dbcc680 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
import org.testng.annotations.Test;
@@ -38,7 +39,7 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
@Override
@Test(enabled=true, groups={"fast"})
- public void testCancelSubscriptionEOTWithChargeThroughDate() {
+ public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testCancelSubscriptionEOTWithChargeThroughDate();
}
@@ -50,7 +51,7 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
@Override
@Test(enabled=true, groups={"fast"})
- public void testUncancel() {
+ public void testUncancel() throws EntitlementBillingApiException {
super.testUncancel();
}
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
index 87491c7..840f357 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelSql.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import org.testng.annotations.Test;
@@ -33,7 +34,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
}
@Test(enabled= false, groups={"stress"})
- public void stressTest() {
+ public void stressTest() throws EntitlementBillingApiException {
for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
cleanupTest();
setupTest();
@@ -55,7 +56,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
@Override
@Test(enabled=true, groups={"sql"})
- public void testCancelSubscriptionEOTWithChargeThroughDate() {
+ public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testCancelSubscriptionEOTWithChargeThroughDate();
}
@@ -67,7 +68,7 @@ public class TestUserApiCancelSql extends TestUserApiCancel {
@Override
@Test(enabled=true, groups={"sql"})
- public void testUncancel() {
+ public void testUncancel() throws EntitlementBillingApiException {
super.testUncancel();
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
index 2c17255..4505ef0 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
@@ -37,6 +37,7 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.TestApiBase;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEvent;
import com.ning.billing.util.clock.DefaultClock;
@@ -100,12 +101,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
}
- protected void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+ protected void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
testChangePlanBundleAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", "Pistol", BillingPeriod.ANNUAL, "gunclubDiscount");
}
private void testChangePlanBundleAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
- String toProd, BillingPeriod toTerm, String toPlanSet) {
+ String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
log.info("Starting testChangeSubscriptionEOTWithChargeThroughDate");
try {
@@ -216,12 +217,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
}
- protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+ protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
tChangePlanChangePlanAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue");
}
private void tChangePlanChangePlanAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
- String toProd, BillingPeriod toTerm, String toPlanSet) {
+ String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
@@ -297,7 +298,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
}
}
- protected void testMultipleChangeLastIMM() {
+ protected void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
try {
SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount");
@@ -344,7 +345,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
}
}
- protected void testMultipleChangeLastEOT() {
+ protected void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
try {
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
index c303c62..03b9d91 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
import org.testng.annotations.Test;
@@ -38,7 +39,7 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
@Override
@Test(enabled=true, groups={"fast"})
- public void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+ public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testChangePlanBundleAlignEOTWithChargeThroughDate();
}
@@ -50,20 +51,20 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
@Override
@Test(enabled=true, groups={"fast"})
- public void testMultipleChangeLastIMM() {
+ public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
super.testMultipleChangeLastIMM();
}
@Override
@Test(enabled=true, groups={"fast"})
- public void testMultipleChangeLastEOT() {
+ public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
super.testMultipleChangeLastEOT();
}
// Set to false until we implement rescue example.
@Override
@Test(enabled=false, groups={"fast"})
- public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+ public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
index 81cbbc8..ad0235f 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
@@ -19,6 +19,7 @@ package com.ning.billing.entitlement.api.user;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import org.testng.annotations.Test;
@@ -32,7 +33,7 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
}
@Test(enabled= true, groups={"stress"})
- public void stressTest() {
+ public void stressTest() throws EntitlementBillingApiException {
for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
cleanupTest();
setupTest();
@@ -66,7 +67,7 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
@Override
@Test(enabled=true, groups={"sql"})
- public void testChangePlanBundleAlignEOTWithChargeThroughDate() {
+ public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testChangePlanBundleAlignEOTWithChargeThroughDate();
}
@@ -78,20 +79,20 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
@Override
@Test(enabled=true, groups={"sql"})
- public void testMultipleChangeLastIMM() {
+ public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
super.testMultipleChangeLastIMM();
}
@Override
@Test(enabled=true, groups={"sql"})
- public void testMultipleChangeLastEOT() {
+ public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
super.testMultipleChangeLastEOT();
}
// rescue not implemented yet
@Override
@Test(enabled=false, groups={"sql"})
- public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() {
+ public void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
super.testChangePlanChangePlanAlignEOTWithChargeThroughDate();
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
index 4a8b4b1..7fceab9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
@@ -29,6 +29,7 @@ import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.TestApiBase;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
@@ -64,7 +65,7 @@ public class TestUserApiDemos extends TestApiBase {
* 8. Cancel EOT
*/
@Test(enabled=true, groups="demos")
- public void testDemo1() {
+ public void testDemo1() throws EntitlementBillingApiException {
try {
System.out.println("DEMO 1 START");
@@ -189,7 +190,7 @@ public class TestUserApiDemos extends TestApiBase {
}
@Test(enabled= true, groups={"stress"})
- public void stressTest() {
+ public void stressTest() throws EntitlementBillingApiException {
for (int i = 0; i < 100; i++) {
cleanupTest();
setupTest();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
index 84d1031..f69b36a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
@@ -26,6 +26,7 @@ import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.glue.MockEngineModuleSql;
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
@@ -42,7 +43,7 @@ public class TestUserApiScenarios extends TestApiBase {
}
@Test(enabled=true)
- public void testChangeIMMCancelUncancelChangeEOT() {
+ public void testChangeIMMCancelUncancelChangeEOT() throws EntitlementBillingApiException {
log.info("Starting testChangeIMMCancelUncancelChangeEOT");
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
index 86e458f..6395470 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
@@ -16,20 +16,30 @@
package com.ning.billing.entitlement.engine.dao;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TreeSet;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.catalog.api.TimeUnit;
import com.ning.billing.config.EntitlementConfig;
-
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
import com.ning.billing.entitlement.api.migration.AccountMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.BundleMigrationData;
import com.ning.billing.entitlement.api.migration.AccountMigrationData.SubscriptionMigrationData;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-
import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
import com.ning.billing.entitlement.engine.core.Engine;
import com.ning.billing.entitlement.events.EntitlementEvent;
@@ -38,20 +48,10 @@ import com.ning.billing.entitlement.events.user.ApiEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.notificationq.NotificationKey;
-import com.ning.billing.util.notificationq.NotificationLifecycle;
import com.ning.billing.util.notificationq.NotificationQueue;
import com.ning.billing.util.notificationq.NotificationQueueService;
import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.*;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlementDao {
protected final static Logger log = LoggerFactory.getLogger(EntitlementDao.class);
@@ -65,7 +65,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
private final NotificationQueueService notificationQueueService;
@Inject
- public MockEntitlementDaoMemory(Clock clock, EntitlementConfig config, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+ public MockEntitlementDaoMemory(final Clock clock, final EntitlementConfig config,
+ final SubscriptionFactory factory,
+ final NotificationQueueService notificationQueueService) {
super();
this.clock = clock;
this.config = config;
@@ -84,9 +86,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<SubscriptionBundle> getSubscriptionBundleForAccount(UUID accountId) {
+ public List<SubscriptionBundle> getSubscriptionBundleForAccount(final UUID accountId) {
List<SubscriptionBundle> results = new ArrayList<SubscriptionBundle>();
- for (SubscriptionBundle cur : bundles) {
+ for (final SubscriptionBundle cur : bundles) {
if (cur.getAccountId().equals(accountId)) {
results.add(cur);
}
@@ -95,8 +97,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
- for (SubscriptionBundle cur : bundles) {
+ public SubscriptionBundle getSubscriptionBundleFromId(final UUID bundleId) {
+ for (final SubscriptionBundle cur : bundles) {
if (cur.getId().equals(bundleId)) {
return cur;
}
@@ -105,8 +107,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
- for (SubscriptionBundle cur : bundles) {
+ public SubscriptionBundle getSubscriptionBundleFromKey(final String bundleKey) {
+ for (final SubscriptionBundle cur : bundles) {
if (cur.getKey().equals(bundleKey)) {
return cur;
}
@@ -116,14 +118,14 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
@Override
- public SubscriptionBundle createSubscriptionBundle(SubscriptionBundleData bundle) {
+ public SubscriptionBundle createSubscriptionBundle(final SubscriptionBundleData bundle) {
bundles.add(bundle);
return getSubscriptionBundleFromId(bundle.getId());
}
@Override
- public Subscription getSubscriptionFromId(UUID subscriptionId) {
- for (Subscription cur : subscriptions) {
+ public Subscription getSubscriptionFromId(final UUID subscriptionId) {
+ for (final Subscription cur : subscriptions) {
if (cur.getId().equals(subscriptionId)) {
return buildSubscription((SubscriptionData) cur);
}
@@ -132,9 +134,14 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+ public UUID getAccountIdFromSubscriptionId(final UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<Subscription> getSubscriptionsForKey(final String bundleKey) {
- for (SubscriptionBundle cur : bundles) {
+ for (final SubscriptionBundle cur : bundles) {
if (cur.getKey().equals(bundleKey)) {
return getSubscriptions(cur.getId());
}
@@ -144,11 +151,11 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
@Override
- public void createSubscription(SubscriptionData subscription, List<EntitlementEvent> initalEvents) {
+ public void createSubscription(final SubscriptionData subscription, final List<EntitlementEvent> initialEvents) {
synchronized(events) {
- events.addAll(initalEvents);
- for (final EntitlementEvent cur : initalEvents) {
+ events.addAll(initialEvents);
+ for (final EntitlementEvent cur : initialEvents) {
recordFutureNotificationFromTransaction(null, cur.getEffectiveDate(), new NotificationKey() {
@Override
public String toString() {
@@ -162,10 +169,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<Subscription> getSubscriptions(UUID bundleId) {
+ public List<Subscription> getSubscriptions(final UUID bundleId) {
List<Subscription> results = new ArrayList<Subscription>();
- for (Subscription cur : subscriptions) {
+ for (final Subscription cur : subscriptions) {
if (cur.getBundleId().equals(bundleId)) {
results.add(buildSubscription((SubscriptionData) cur));
}
@@ -174,10 +181,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<EntitlementEvent> getEventsForSubscription(UUID subscriptionId) {
+ public List<EntitlementEvent> getEventsForSubscription(final UUID subscriptionId) {
synchronized(events) {
List<EntitlementEvent> results = new LinkedList<EntitlementEvent>();
- for (EntitlementEvent cur : events) {
+ for (final EntitlementEvent cur : events) {
if (cur.getSubscriptionId().equals(subscriptionId)) {
results.add(cur);
}
@@ -187,10 +194,10 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public List<EntitlementEvent> getPendingEventsForSubscription(UUID subscriptionId) {
+ public List<EntitlementEvent> getPendingEventsForSubscription(final UUID subscriptionId) {
synchronized(events) {
List<EntitlementEvent> results = new LinkedList<EntitlementEvent>();
- for (EntitlementEvent cur : events) {
+ for (final EntitlementEvent cur : events) {
if (cur.isActive() &&
cur.getEffectiveDate().isAfter(clock.getUTCNow()) &&
cur.getSubscriptionId().equals(subscriptionId)) {
@@ -203,8 +210,8 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
@Override
- public Subscription getBaseSubscription(UUID bundleId) {
- for (Subscription cur : subscriptions) {
+ public Subscription getBaseSubscription(final UUID bundleId) {
+ for (final Subscription cur : subscriptions) {
if (cur.getBundleId().equals(bundleId) &&
cur.getCurrentPlan().getProduct().getCategory() == ProductCategory.BASE) {
return buildSubscription((SubscriptionData) cur);
@@ -214,19 +221,19 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void createNextPhaseEvent(UUID subscriptionId, EntitlementEvent nextPhase) {
+ public void createNextPhaseEvent(final UUID subscriptionId, final EntitlementEvent nextPhase) {
cancelNextPhaseEvent(subscriptionId);
insertEvent(nextPhase);
}
- private Subscription buildSubscription(SubscriptionData in) {
+ private Subscription buildSubscription(final SubscriptionData in) {
return factory.createSubscription(new SubscriptionBuilder(in), getEventsForSubscription(in.getId()));
}
@Override
- public void updateSubscription(SubscriptionData subscription) {
+ public void updateSubscription(final SubscriptionData subscription) {
boolean found = false;
Iterator<Subscription> it = subscriptions.iterator();
@@ -244,7 +251,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void cancelSubscription(UUID subscriptionId, EntitlementEvent cancelEvent) {
+ public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent) {
synchronized (cancelEvent) {
cancelNextPhaseEvent(subscriptionId);
insertEvent(cancelEvent);
@@ -252,7 +259,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void changePlan(UUID subscriptionId, List<EntitlementEvent> changeEvents) {
+ public void changePlan(final UUID subscriptionId, final List<EntitlementEvent> changeEvents) {
synchronized(events) {
cancelNextChangeEvent(subscriptionId);
cancelNextPhaseEvent(subscriptionId);
@@ -280,7 +287,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
}
- private void cancelNextPhaseEvent(UUID subscriptionId) {
+ private void cancelNextPhaseEvent(final UUID subscriptionId) {
Subscription curSubscription = getSubscriptionFromId(subscriptionId);
if (curSubscription.getCurrentPhase() == null ||
@@ -307,7 +314,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
- private void cancelNextChangeEvent(UUID subscriptionId) {
+ private void cancelNextChangeEvent(final UUID subscriptionId) {
synchronized(events) {
@@ -328,7 +335,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void uncancelSubscription(UUID subscriptionId, List<EntitlementEvent> uncancelEvents) {
+ public void uncancelSubscription(final UUID subscriptionId, final List<EntitlementEvent> uncancelEvents) {
synchronized (events) {
boolean foundCancel = false;
@@ -346,7 +353,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
}
if (foundCancel) {
- for (EntitlementEvent cur : uncancelEvents) {
+ for (final EntitlementEvent cur : uncancelEvents) {
insertEvent(cur);
}
}
@@ -360,9 +367,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
undoMigration(accountId);
- for (BundleMigrationData curBundle : accountData.getData()) {
+ for (final BundleMigrationData curBundle : accountData.getData()) {
SubscriptionBundleData bundleData = curBundle.getData();
- for (SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
+ for (final SubscriptionMigrationData curSubscription : curBundle.getSubscriptions()) {
SubscriptionData subData = curSubscription.getData();
for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
events.add(curEvent);
@@ -382,15 +389,15 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public void undoMigration(UUID accountId) {
+ public void undoMigration(final UUID accountId) {
synchronized(events) {
List<SubscriptionBundle> allBundles = getSubscriptionBundleForAccount(accountId);
- for (SubscriptionBundle bundle : allBundles) {
+ for (final SubscriptionBundle bundle : allBundles) {
List<Subscription> allSubscriptions = getSubscriptions(bundle.getId());
- for (Subscription subscription : allSubscriptions) {
+ for (final Subscription subscription : allSubscriptions) {
List<EntitlementEvent> allEvents = getEventsForSubscription(subscription.getId());
- for (EntitlementEvent event : allEvents) {
+ for (final EntitlementEvent event : allEvents) {
events.remove(event);
}
subscriptions.remove(subscription);
@@ -402,9 +409,9 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
}
@Override
- public EntitlementEvent getEventById(UUID eventId) {
+ public EntitlementEvent getEventById(final UUID eventId) {
synchronized(events) {
- for (EntitlementEvent cur : events) {
+ for (final EntitlementEvent cur : events) {
if (cur.getId().equals(eventId)) {
return cur;
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
index 503fd1a..c5881f9 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoSql.java
@@ -16,25 +16,24 @@
package com.ning.billing.entitlement.engine.dao;
-import com.google.inject.Inject;
-import com.ning.billing.config.EntitlementConfig;
-import com.ning.billing.entitlement.api.user.SubscriptionFactory;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.notificationq.NotificationQueueService;
-
-import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.user.SubscriptionFactory;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEntitlementDao {
private final ResetSqlDao resetDao;
@Inject
- public MockEntitlementDaoSql(DBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
+ public MockEntitlementDaoSql(IDBI dbi, Clock clock, SubscriptionFactory factory, NotificationQueueService notificationQueueService) {
super(dbi, clock, factory, notificationQueueService);
this.resetDao = dbi.onDemand(ResetSqlDao.class);
}
@@ -59,7 +58,7 @@ public class MockEntitlementDaoSql extends EntitlementSqlDao implements MockEnti
public static interface ResetSqlDao extends Transactional<ResetSqlDao>, CloseMe {
- @SqlUpdate("truncate table events")
+ @SqlUpdate("truncate table entitlement_events")
public void resetEvents();
@SqlUpdate("truncate table subscriptions")
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
index 1555bdb..d46fe83 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
@@ -16,26 +16,19 @@
package com.ning.billing.entitlement.glue;
-import com.ning.billing.account.glue.AccountModuleMock;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
import com.ning.billing.catalog.glue.CatalogModule;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.glue.EventBusModule;
-import com.ning.billing.util.glue.NotificationQueueModule;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.BusModule;
public class MockEngineModule extends EntitlementModule {
@Override
- protected void installClock() {
- bind(Clock.class).to(ClockMock.class).asEagerSingleton();
- }
-
- @Override
protected void configure() {
super.configure();
- install(new EventBusModule());
+ install(new BusModule());
install(new CatalogModule());
- install(new AccountModuleMock());
+ install(new AccountModuleWithMocks());
+ install(new MockClockModule());
}
-
}
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
index dbe2938..e9e6134 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModuleSql.java
@@ -25,7 +25,7 @@ import com.ning.billing.util.clock.ClockMock;
import com.ning.billing.util.glue.NotificationQueueModule;
import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
public class MockEngineModuleSql extends MockEngineModule {
@@ -35,13 +35,8 @@ public class MockEngineModuleSql extends MockEngineModule {
bind(EntitlementDao.class).to(MockEntitlementDaoSql.class).asEagerSingleton();
}
- @Override
- protected void installClock() {
- bind(Clock.class).to(ClockMock.class).asEagerSingleton();
- }
-
protected void installDBI() {
- bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+ bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
bind(DbiConfig.class).toInstance(config);
}
invoice/pom.xml 20(+15 -5)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index 8c939ca..81f9eb0 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
@@ -21,6 +21,11 @@
<packaging>jar</packaging>
<dependencies>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>com.ning.billing</groupId>
<artifactId>killbill-api</artifactId>
</dependency>
@@ -48,7 +53,6 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
-
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
@@ -67,18 +71,15 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
- <version>2.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
- <version>2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
- <version>2.27</version>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
@@ -94,6 +95,15 @@
<artifactId>guice</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.jayway.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
</build>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
index b7bc657..6f033db 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/DefaultInvoiceService.java
@@ -17,19 +17,31 @@
package com.ning.billing.invoice.api;
import com.google.inject.Inject;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import com.ning.billing.util.bus.Bus;
public class DefaultInvoiceService implements InvoiceService {
- private static final String INVOICE_SERVICE_NAME = "invoice-service";
+
+ public static final String INVOICE_SERVICE_NAME = "invoice-service";
private final InvoiceUserApi userApi;
private final InvoicePaymentApi paymentApi;
+ private final NextBillingDateNotifier dateNotifier;
+ private final InvoiceListener invoiceListener;
+ private final Bus eventBus;
@Inject
- public DefaultInvoiceService(InvoiceUserApi userApi, InvoicePaymentApi paymentApi) {
+ public DefaultInvoiceService(InvoiceListener invoiceListener, Bus eventBus, InvoiceUserApi userApi, InvoicePaymentApi paymentApi, NextBillingDateNotifier dateNotifier) {
+ this.invoiceListener = invoiceListener;
+ this.eventBus = eventBus;
this.userApi = userApi;
this.paymentApi = paymentApi;
+ this.dateNotifier = dateNotifier;
}
+
@Override
public String getName() {
return INVOICE_SERVICE_NAME;
@@ -47,5 +59,34 @@ public class DefaultInvoiceService implements InvoiceService {
@LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.INIT_SERVICE)
public void initialize() {
+ dateNotifier.initialize();
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
+ public void start() {
+ dateNotifier.start();
+ }
+
+ @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+ public void registerForNotifications() {
+ try {
+ eventBus.register(invoiceListener);
+ } catch (Bus.EventBusException e) {
+ throw new RuntimeException("Unable to register to the EventBus!", e);
+ }
+ }
+
+ @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.UNREGISTER_EVENTS)
+ public void unregisterForNotifications() {
+ try {
+ eventBus.unregister(invoiceListener);
+ } catch (Bus.EventBusException e) {
+ throw new RuntimeException("Unable to unregister to the EventBus!", e);
+ }
+ }
+
+ @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
+ public void stop() {
+ dateNotifier.stop();
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index fac63ba..4d99525 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -1,4 +1,5 @@
/*
+
* Copyright 2010-2011 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
@@ -19,39 +20,66 @@ package com.ning.billing.invoice.api.invoice;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
+
import org.joda.time.DateTime;
-import org.skife.jdbi.v2.IDBI;
+
import com.google.inject.Inject;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePayment;
import com.ning.billing.invoice.api.InvoicePaymentApi;
import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
private final InvoiceDao dao;
@Inject
- public DefaultInvoicePaymentApi(InvoiceDao dao) {
+ public DefaultInvoicePaymentApi(final InvoiceDao dao) {
this.dao = dao;
+ }
+
+ @Override
+ public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+ dao.notifyOfPaymentAttempt(invoicePayment);
+ }
+
+// @Override
+// public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
+// dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+// }
+
+ @Override
+ public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+ return dao.getInvoicesByAccount(accountId);
+ }
+
+ @Override
+ public Invoice getInvoice(final UUID invoiceId) {
+ return dao.getById(invoiceId);
}
@Override
- public void paymentSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentAttemptDate) {
- dao.notifySuccessfulPayment(invoiceId.toString(), amount, currency.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+ public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId) {
+ UUID invoiceIdStr = dao.getInvoiceIdByPaymentAttemptId(paymentAttemptId);
+ return invoiceIdStr == null ? null : dao.getById(invoiceIdStr);
}
@Override
- public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
- dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+ public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+ return dao.getInvoicePayment(paymentAttemptId);
}
@Override
- public List<Invoice> getInvoicesByAccount(UUID accountId) {
- return dao.getInvoicesByAccount(accountId.toString());
+ public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+ InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate, amount, currency, null, null);
+ dao.notifyOfPaymentAttempt(invoicePayment);
}
@Override
- public Invoice getInvoice(UUID invoiceId) {
- return dao.getById(invoiceId.toString());
+ public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+ InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate);
+ dao.notifyOfPaymentAttempt(invoicePayment);
}
-}
+
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java
new file mode 100644
index 0000000..bbec57f
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/test/DefaultInvoiceTestApi.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api.test;
+
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.dao.InvoiceDao;
+
+public class DefaultInvoiceTestApi implements InvoiceTestApi {
+ private final InvoiceDao invoiceDao;
+
+ @Inject
+ public DefaultInvoiceTestApi(InvoiceDao invoiceDao) {
+ this.invoiceDao = invoiceDao;
+ }
+
+ @Override
+ public void create(Invoice invoice) {
+ invoiceDao.create(invoice);
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
index 094e6f1..5c6785c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
@@ -18,7 +18,9 @@ package com.ning.billing.invoice.api.user;
import java.math.BigDecimal;
import java.util.UUID;
+
import org.joda.time.DateTime;
+
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceCreationNotification;
@@ -61,4 +63,10 @@ public class DefaultInvoiceCreationNotification implements InvoiceCreationNotifi
public DateTime getInvoiceCreationDate() {
return invoiceCreationDate;
}
+
+ @Override
+ public String toString() {
+ return "DefaultInvoiceCreationNotification [invoiceId=" + invoiceId + ", accountId=" + accountId + ", amountOwed=" + amountOwed + ", currency=" + currency + ", invoiceCreationDate=" + invoiceCreationDate + "]";
+ }
+
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 22f4990..da7f565 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -16,50 +16,66 @@
package com.ning.billing.invoice.api.user;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.api.InvoicePayment;
+import org.joda.time.DateTime;
import com.google.inject.Inject;
-import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoiceUserApi;
-import com.ning.billing.invoice.dao.DefaultInvoiceDao;
import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.util.eventbus.EventBus;
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.IDBI;
-
-import java.math.BigDecimal;
-import java.util.List;
-import java.util.UUID;
public class DefaultInvoiceUserApi implements InvoiceUserApi {
private final InvoiceDao dao;
@Inject
- public DefaultInvoiceUserApi(InvoiceDao dao) {
+ public DefaultInvoiceUserApi(final InvoiceDao dao) {
this.dao = dao;
}
@Override
- public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays) {
- return dao.getInvoicesForPayment(targetDate.toDate(), numberOfDays);
+ public List<UUID> getInvoicesForPayment(final DateTime targetDate, final int numberOfDays) {
+ return dao.getInvoicesForPayment(targetDate, numberOfDays);
}
@Override
- public List<Invoice> getInvoicesByAccount(UUID accountId) {
- return dao.getInvoicesByAccount(accountId.toString());
+ public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+ return dao.getInvoicesByAccount(accountId);
}
@Override
- public Invoice getInvoice(UUID invoiceId) {
- return dao.getById(invoiceId.toString());
+ public List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate) {
+ return dao.getInvoicesByAccount(accountId, fromDate);
+ }
+
+ @Override
+ public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+ dao.notifyOfPaymentAttempt(invoicePayment);
+ }
+
+ @Override
+ public BigDecimal getAccountBalance(UUID accountId) {
+ BigDecimal result = dao.getAccountBalance(accountId);
+ return result == null ? BigDecimal.ZERO : result;
+ }
+
+ @Override
+ public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
+ return dao.getInvoiceItemsByAccount(accountId);
}
@Override
- public void paymentAttemptFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
- dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+ public Invoice getInvoice(final UUID invoiceId) {
+ return dao.getById(invoiceId);
}
@Override
- public void paymentAttemptSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentDate) {
- dao.notifySuccessfulPayment(invoiceId.toString(), amount, currency.toString(), paymentId.toString(), paymentDate.toDate());
+ public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate) {
+ return dao.getUnpaidInvoicesByAccountId(accountId, upToDate);
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index 20f33c7..3b27fa6 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -16,54 +16,113 @@
package com.ning.billing.invoice.dao;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
-import com.google.inject.Inject;
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceCreationNotification;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
-import com.ning.billing.util.eventbus.EventBus;
+import org.joda.time.DateTime;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.math.BigDecimal;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.invoice.api.DefaultInvoiceService;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceCreationNotification;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.notification.DefaultNextBillingDateNotifier;
+import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.notificationq.NotificationKey;
+import com.ning.billing.util.notificationq.NotificationQueue;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService.NoSuchNotificationQueue;
public class DefaultInvoiceDao implements InvoiceDao {
- private final InvoiceSqlDao invoiceDao;
-
- private final EventBus eventBus;
private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceDao.class);
+ private final InvoiceSqlDao invoiceSqlDao;
+ private final RecurringInvoiceItemSqlDao recurringInvoiceItemSqlDao;
+ private final FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemSqlDao;
+ private final InvoicePaymentSqlDao invoicePaymentSqlDao;
+ private final EntitlementBillingApi entitlementBillingApi;
+
+ private final Bus eventBus;
+
+ private NextBillingDatePoster nextBillingDatePoster;
+
@Inject
- public DefaultInvoiceDao(final IDBI dbi, final EventBus eventBus) {
- this.invoiceDao = dbi.onDemand(InvoiceSqlDao.class);
+ public DefaultInvoiceDao(final IDBI dbi, final Bus eventBus,
+ final EntitlementBillingApi entitlementBillingApi,
+ NextBillingDatePoster nextBillingDatePoster) {
+ this.invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
+ this.recurringInvoiceItemSqlDao = dbi.onDemand(RecurringInvoiceItemSqlDao.class);
+ this.fixedPriceInvoiceItemSqlDao = dbi.onDemand(FixedPriceInvoiceItemSqlDao.class);
+ this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
this.eventBus = eventBus;
+ this.entitlementBillingApi = entitlementBillingApi;
+ this.nextBillingDatePoster = nextBillingDatePoster;
+ }
+
+ @Override
+ public List<Invoice> getInvoicesByAccount(final UUID accountId) {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ @Override
+ public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
+
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+ return invoices;
+ }
+ });
}
@Override
- public List<Invoice> getInvoicesByAccount(final String accountId) {
- return invoiceDao.getInvoicesByAccount(accountId);
+ public List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate) {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ @Override
+ public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ List<Invoice> invoices = invoiceDao.getInvoicesByAccountAfterDate(accountId.toString(), fromDate.toDate());
+
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+ return invoices;
+ }
+ });
+ }
+
+ @Override
+ public List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId) {
+ List<InvoiceItem> results = new ArrayList<InvoiceItem>();
+ results.addAll(recurringInvoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString()));
+ results.addAll(fixedPriceInvoiceItemSqlDao.getInvoiceItemsByAccount(accountId.toString()));
+ return results;
}
@Override
public List<Invoice> get() {
- return invoiceDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
@Override
public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
List<Invoice> invoices = invoiceDao.get();
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- for (Invoice invoice : invoices) {
- List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
- invoice.add(invoiceItems);
- }
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
return invoices;
}
@@ -71,16 +130,15 @@ public class DefaultInvoiceDao implements InvoiceDao {
}
@Override
- public Invoice getById(final String invoiceId) {
- return invoiceDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
+ public Invoice getById(final UUID invoiceId) {
+ return invoiceSqlDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
@Override
public Invoice inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
- Invoice invoice = invoiceDao.getById(invoiceId);
+ Invoice invoice = invoiceDao.getById(invoiceId.toString());
if (invoice != null) {
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId);
- invoice.add(invoiceItems);
+ getInvoiceItemsWithinTransaction(invoice, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoice, invoiceDao);
}
return invoice;
@@ -90,65 +148,172 @@ public class DefaultInvoiceDao implements InvoiceDao {
@Override
public void create(final Invoice invoice) {
- invoiceDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
- @Override
- public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
+ @Override
+ public Void inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+
+ // STEPH this seems useless
Invoice currentInvoice = invoiceDao.getById(invoice.getId().toString());
if (currentInvoice == null) {
invoiceDao.create(invoice);
- List<InvoiceItem> invoiceItems = invoice.getItems();
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- invoiceItemDao.create(invoiceItems);
+ List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
+ RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+ recurringInvoiceItemDao.batchCreateFromTransaction(recurringInvoiceItems);
+
+ notifyOfFutureBillingEvents(invoiceSqlDao, recurringInvoiceItems);
+
+ List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
+ FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = invoiceDao.become(FixedPriceInvoiceItemSqlDao.class);
+ fixedPriceInvoiceItemDao.batchCreateFromTransaction(fixedPriceInvoiceItems);
+
+ setChargedThroughDates(invoiceSqlDao, fixedPriceInvoiceItems, recurringInvoiceItems);
+
+ // STEPH Why do we need that? Are the payments not always null at this point?
+ List<InvoicePayment> invoicePayments = invoice.getPayments();
+ InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+ invoicePaymentSqlDao.batchCreateFromTransaction(invoicePayments);
InvoiceCreationNotification event;
event = new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
- invoice.getAmountOutstanding(), invoice.getCurrency(),
+ invoice.getBalance(), invoice.getCurrency(),
invoice.getInvoiceDate());
- eventBus.post(event);
+ eventBus.postFromTransaction(event, invoiceDao);
}
return null;
- }
- });
+ }
+ });
}
@Override
- public List<Invoice> getInvoicesBySubscription(final String subscriptionId) {
- return invoiceDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
- @Override
- public List<Invoice> inTransaction(InvoiceSqlDao invoiceDao, TransactionStatus status) throws Exception {
- List<Invoice> invoices = invoiceDao.getInvoicesBySubscription(subscriptionId);
+ public List<Invoice> getInvoicesBySubscription(final UUID subscriptionId) {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ @Override
+ public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ List<Invoice> invoices = invoiceDao.getInvoicesBySubscription(subscriptionId.toString());
- InvoiceItemSqlDao invoiceItemDao = invoiceDao.become(InvoiceItemSqlDao.class);
- for (Invoice invoice : invoices) {
- List<InvoiceItem> invoiceItems = invoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
- invoice.add(invoiceItems);
- }
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
- return invoices;
- }
+ return invoices;
+ }
});
}
@Override
- public List<UUID> getInvoicesForPayment(Date targetDate, int numberOfDays) {
- return invoiceDao.getInvoicesForPayment(targetDate, numberOfDays);
+ public List<UUID> getInvoicesForPayment(final DateTime targetDate, final int numberOfDays) {
+ return invoiceSqlDao.getInvoicesForPayment(targetDate.toDate(), numberOfDays);
+ }
+
+ @Override
+ public BigDecimal getAccountBalance(final UUID accountId) {
+ return invoiceSqlDao.getAccountBalance(accountId.toString());
+ }
+
+ @Override
+ public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+ invoicePaymentSqlDao.notifyOfPaymentAttempt(invoicePayment);
+ }
+
+ @Override
+ public List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate) {
+ return invoiceSqlDao.inTransaction(new Transaction<List<Invoice>, InvoiceSqlDao>() {
+ @Override
+ public List<Invoice> inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+ List<Invoice> invoices = invoiceSqlDao.getUnpaidInvoicesByAccountId(accountId.toString(), upToDate.toDate());
+
+ getInvoiceItemsWithinTransaction(invoices, invoiceDao);
+ getInvoicePaymentsWithinTransaction(invoices, invoiceDao);
+
+ return invoices;
+ }
+ });
}
@Override
- public void notifySuccessfulPayment(String invoiceId, BigDecimal paymentAmount, String currency, String paymentId, Date paymentDate) {
- invoiceDao.notifySuccessfulPayment(invoiceId, paymentAmount, currency, paymentId, paymentDate);
+ public UUID getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+ return invoiceSqlDao.getInvoiceIdByPaymentAttemptId(paymentAttemptId.toString());
}
@Override
- public void notifyFailedPayment(String invoiceId, String paymentId, Date paymentAttemptDate) {
- invoiceDao.notifyFailedPayment(invoiceId, paymentId, paymentAttemptDate);
+ public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+ return invoicePaymentSqlDao.getInvoicePayment(paymentAttemptId);
}
@Override
public void test() {
- invoiceDao.test();
+ invoiceSqlDao.test();
+ }
+
+ private void getInvoiceItemsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
+ for (final Invoice invoice : invoices) {
+ getInvoiceItemsWithinTransaction(invoice, invoiceDao);
+ }
+ }
+
+ private void getInvoiceItemsWithinTransaction(final Invoice invoice, final InvoiceSqlDao invoiceDao) {
+ RecurringInvoiceItemSqlDao recurringInvoiceItemDao = invoiceDao.become(RecurringInvoiceItemSqlDao.class);
+ List<InvoiceItem> recurringInvoiceItems = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
+ invoice.addInvoiceItems(recurringInvoiceItems);
+
+ FixedPriceInvoiceItemSqlDao fixedPriceInvoiceItemDao = invoiceDao.become(FixedPriceInvoiceItemSqlDao.class);
+ List<InvoiceItem> fixedPriceInvoiceItems = fixedPriceInvoiceItemDao.getInvoiceItemsByInvoice(invoice.getId().toString());
+ invoice.addInvoiceItems(fixedPriceInvoiceItems);
+ }
+
+ private void getInvoicePaymentsWithinTransaction(final List<Invoice> invoices, final InvoiceSqlDao invoiceDao) {
+ for (Invoice invoice : invoices) {
+ getInvoicePaymentsWithinTransaction(invoice, invoiceDao);
+ }
+ }
+
+ private void getInvoicePaymentsWithinTransaction(final Invoice invoice, final InvoiceSqlDao invoiceDao) {
+ InvoicePaymentSqlDao invoicePaymentSqlDao = invoiceDao.become(InvoicePaymentSqlDao.class);
+ String invoiceId = invoice.getId().toString();
+ List<InvoicePayment> invoicePayments = invoicePaymentSqlDao.getPaymentsForInvoice(invoiceId);
+ invoice.addPayments(invoicePayments);
+ }
+
+ private void notifyOfFutureBillingEvents(final InvoiceSqlDao dao, final List<InvoiceItem> invoiceItems) {
+ for (final InvoiceItem item : invoiceItems) {
+ if (item instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+ if ((recurringInvoiceItem.getEndDate() != null) &&
+ (recurringInvoiceItem.getAmount() == null ||
+ recurringInvoiceItem.getAmount().compareTo(BigDecimal.ZERO) >= 0)) {
+ nextBillingDatePoster.insertNextBillingNotification(dao, item.getSubscriptionId(), recurringInvoiceItem.getEndDate());
+ }
+ }
+ }
+ }
+
+ private void setChargedThroughDates(final InvoiceSqlDao dao, final Collection<InvoiceItem> fixedPriceItems,
+ final Collection<InvoiceItem> recurringItems) {
+ Map<UUID, DateTime> chargeThroughDates = new HashMap<UUID, DateTime>();
+ addInvoiceItemsToChargeThroughDates(chargeThroughDates, fixedPriceItems);
+ addInvoiceItemsToChargeThroughDates(chargeThroughDates, recurringItems);
+
+ for (UUID subscriptionId : chargeThroughDates.keySet()) {
+ DateTime chargeThroughDate = chargeThroughDates.get(subscriptionId);
+ log.info("Setting CTD for subscription {} to {}", subscriptionId.toString(), chargeThroughDate.toString());
+ entitlementBillingApi.setChargedThroughDateFromTransaction(dao, subscriptionId, chargeThroughDate);
+ }
+ }
+
+ private void addInvoiceItemsToChargeThroughDates(Map<UUID, DateTime> chargeThroughDates, Collection<InvoiceItem> items) {
+ for (InvoiceItem item : items) {
+ UUID subscriptionId = item.getSubscriptionId();
+ DateTime endDate = item.getEndDate();
+
+ if (chargeThroughDates.containsKey(subscriptionId)) {
+ if (chargeThroughDates.get(subscriptionId).isBefore(endDate)) {
+ chargeThroughDates.put(subscriptionId, endDate);
+ }
+ } else {
+ chargeThroughDates.put(subscriptionId, endDate);
+ }
+ }
}
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
new file mode 100644
index 0000000..330784e
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.dao;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.util.entity.EntityDao;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.BinderFactory;
+import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(FixedPriceInvoiceItemSqlDao.FixedPriceInvoiceItemMapper.class)
+public interface FixedPriceInvoiceItemSqlDao extends EntityDao<InvoiceItem> {
+ @SqlQuery
+ List<InvoiceItem> getInvoiceItemsByInvoice(@Bind("invoiceId") final String invoiceId);
+
+ @SqlQuery
+ List<InvoiceItem> getInvoiceItemsByAccount(@Bind("accountId") final String accountId);
+
+ @SqlQuery
+ List<InvoiceItem> getInvoiceItemsBySubscription(@Bind("subscriptionId") final String subscriptionId);
+
+ @Override
+ @SqlUpdate
+ void create(@FixedPriceInvoiceItemBinder final InvoiceItem invoiceItem);
+
+ @Override
+ @SqlUpdate
+ void update(@FixedPriceInvoiceItemBinder final InvoiceItem invoiceItem);
+
+ @SqlBatch
+ void create(@FixedPriceInvoiceItemBinder final List<InvoiceItem> items);
+
+ @SqlBatch(transactional=false)
+ void batchCreateFromTransaction(@FixedPriceInvoiceItemBinder final List<InvoiceItem> items);
+
+ @BindingAnnotation(FixedPriceInvoiceItemBinder.FixedPriceInvoiceItemBinderFactory.class)
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.PARAMETER})
+ public @interface FixedPriceInvoiceItemBinder {
+ public static class FixedPriceInvoiceItemBinderFactory implements BinderFactory {
+ public Binder build(Annotation annotation) {
+ return new Binder<FixedPriceInvoiceItemBinder, FixedPriceInvoiceItem>() {
+ public void bind(SQLStatement q, FixedPriceInvoiceItemBinder bind, FixedPriceInvoiceItem item) {
+ q.bind("id", item.getId().toString());
+ q.bind("invoiceId", item.getInvoiceId().toString());
+ q.bind("subscriptionId", item.getSubscriptionId().toString());
+ q.bind("planName", item.getPlanName());
+ q.bind("phaseName", item.getPhaseName());
+ q.bind("startDate", item.getStartDate().toDate());
+ q.bind("endDate", item.getEndDate().toDate());
+ q.bind("amount", item.getAmount());
+ q.bind("currency", item.getCurrency().toString());
+ }
+ };
+ }
+ }
+ }
+
+ public static class FixedPriceInvoiceItemMapper implements ResultSetMapper<InvoiceItem> {
+ @Override
+ public FixedPriceInvoiceItem map(int index, ResultSet result, StatementContext context) throws SQLException {
+ UUID id = UUID.fromString(result.getString("id"));
+ UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
+ UUID subscriptionId = UUID.fromString(result.getString("subscription_id"));
+ String planName = result.getString("plan_name");
+ String phaseName = result.getString("phase_name");
+ DateTime startDate = new DateTime(result.getTimestamp("start_date"));
+ DateTime endDate = new DateTime(result.getTimestamp("end_date"));
+ BigDecimal amount = result.getBigDecimal("amount");
+ Currency currency = Currency.valueOf(result.getString("currency"));
+
+ return new FixedPriceInvoiceItem(id, invoiceId, subscriptionId, planName, phaseName,
+ startDate, endDate, amount, currency);
+ }
+ }
+}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index e9306b2..7a7c280 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -16,35 +16,42 @@
package com.ning.billing.invoice.dao;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import org.joda.time.DateTime;
+
import java.math.BigDecimal;
-import java.util.Date;
import java.util.List;
import java.util.UUID;
-import com.ning.billing.invoice.api.Invoice;
public interface InvoiceDao {
void create(Invoice invoice);
- Invoice getById(final String id);
+ Invoice getById(final UUID id);
List<Invoice> get();
- List<Invoice> getInvoicesByAccount(final String accountId);
+ List<Invoice> getInvoicesByAccount(final UUID accountId);
+
+ List<Invoice> getInvoicesByAccount(final UUID accountId, final DateTime fromDate);
- List<Invoice> getInvoicesBySubscription(final String subscriptionId);
+ List<InvoiceItem> getInvoiceItemsByAccount(final UUID accountId);
- List<UUID> getInvoicesForPayment(final Date targetDate,
+ List<Invoice> getInvoicesBySubscription(final UUID subscriptionId);
+
+ List<UUID> getInvoicesForPayment(final DateTime targetDate,
final int numberOfDays);
- void notifySuccessfulPayment(final String invoiceId,
- final BigDecimal paymentAmount,
- final String currency,
- final String paymentId,
- final Date paymentDate);
+ UUID getInvoiceIdByPaymentAttemptId(final UUID paymentAttemptId);
+
+ InvoicePayment getInvoicePayment(final UUID paymentAttemptId);
+
+ void notifyOfPaymentAttempt(final InvoicePayment invoicePayment);
+
+ BigDecimal getAccountBalance(final UUID accountId);
- void notifyFailedPayment(final String invoiceId,
- final String paymentId,
- final Date paymentAttemptDate);
+ List<Invoice> getUnpaidInvoicesByAccountId(final UUID accountId, final DateTime upToDate);
void test();
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
new file mode 100644
index 0000000..7179ec1
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.dao;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.Currency;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.*;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.invoice.api.InvoicePayment;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(InvoicePaymentSqlDao.InvoicePaymentMapper.class)
+public interface InvoicePaymentSqlDao {
+ @SqlQuery
+ public InvoicePayment getByPaymentAttemptId(@Bind("paymentAttempt") final String paymentAttemptId);
+
+ @SqlQuery
+ public List<InvoicePayment> get();
+
+ @SqlUpdate
+ public void create(@InvoicePaymentBinder InvoicePayment invoicePayment);
+
+ @SqlBatch(transactional=false)
+ void batchCreateFromTransaction(@InvoicePaymentBinder List<InvoicePayment> items);
+
+ @SqlUpdate
+ public void update(@InvoicePaymentBinder InvoicePayment invoicePayment);
+
+ @SqlQuery
+ public List<InvoicePayment> getPaymentsForInvoice(@Bind("invoiceId") String invoiceId);
+
+ @SqlQuery
+ InvoicePayment getInvoicePayment(@Bind("paymentAttemptId") UUID paymentAttemptId);
+
+ @SqlUpdate
+ void notifyOfPaymentAttempt(@InvoicePaymentBinder InvoicePayment invoicePayment);
+
+ public static class InvoicePaymentMapper implements ResultSetMapper<InvoicePayment> {
+ private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+ final Timestamp resultStamp = rs.getTimestamp(fieldName);
+ return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+ }
+
+ @Override
+ public InvoicePayment map(int index, ResultSet result, StatementContext context) throws SQLException {
+ final UUID paymentAttemptId = UUID.fromString(result.getString("payment_attempt_id"));
+ final UUID invoiceId = UUID.fromString(result.getString("invoice_id"));
+ final DateTime paymentAttemptDate = getDate(result, "payment_attempt_date");
+ final BigDecimal amount = result.getBigDecimal("amount");
+ final String currencyString = result.getString("currency");
+ final Currency currency = (currencyString == null) ? null : Currency.valueOf(currencyString);
+ final DateTime createdDate = getDate(result, "created_date");
+ final DateTime updatedDate = getDate(result, "updated_date");
+
+ return new InvoicePayment() {
+ private final DateTime now = new DateTime();
+
+ @Override
+ public UUID getPaymentAttemptId() {
+ return paymentAttemptId;
+ }
+ @Override
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+ @Override
+ public DateTime getPaymentAttemptDate() {
+ return paymentAttemptDate;
+ }
+ @Override
+ public BigDecimal getAmount() {
+ return amount;
+ }
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+ @Override
+ public DateTime getCreatedDate() {
+ return createdDate ;
+ }
+ @Override
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+ };
+ }
+ }
+
+ @BindingAnnotation(InvoicePaymentBinder.InvoicePaymentBinderFactory.class)
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.PARAMETER})
+ public @interface InvoicePaymentBinder {
+ public static class InvoicePaymentBinderFactory implements BinderFactory {
+ @Override
+ public Binder build(Annotation annotation) {
+ return new Binder<InvoicePaymentBinder, InvoicePayment>() {
+ @Override
+ public void bind(SQLStatement q, InvoicePaymentBinder bind, InvoicePayment payment) {
+ q.bind("invoiceId", payment.getInvoiceId().toString());
+ q.bind("paymentAttemptId", payment.getPaymentAttemptId().toString());
+ q.bind("paymentAttemptDate", payment.getPaymentAttemptDate().toDate());
+ q.bind("amount", payment.getAmount());
+ Currency currency = payment.getCurrency();
+ q.bind("currency", (currency == null) ? null : currency.toString());
+ DateTime createdDate = payment.getCreatedDate();
+ q.bind("createdDate", (createdDate == null) ? new DateTime().toDate() : createdDate.toDate());
+ DateTime updatedDate = payment.getUpdatedDate();
+ q.bind("updatedDate", (updatedDate == null) ? new DateTime().toDate() : updatedDate.toDate());
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
index 9ede145..05be556 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
@@ -18,7 +18,6 @@ package com.ning.billing.invoice.dao;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.util.UuidMapper;
import com.ning.billing.util.entity.EntityDao;
@@ -38,18 +37,20 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
-import java.lang.annotation.*;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.Timestamp;
-import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@ExternalizedSqlViaStringTemplate3()
-@RegisterMapper({UuidMapper.class, InvoiceSqlDao.InvoiceMapper.class})
+@RegisterMapper(InvoiceSqlDao.InvoiceMapper.class)
public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<InvoiceSqlDao>, Transmogrifier, CloseMe {
@Override
@SqlUpdate
@@ -63,38 +64,44 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
List<Invoice> getInvoicesByAccount(@Bind("accountId") final String accountId);
@SqlQuery
+ List<Invoice> getInvoicesByAccountAfterDate(@Bind("accountId") final String accountId,
+ @Bind("fromDate") final Date fromDate);
+
+ @SqlQuery
List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
@SqlQuery
- List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
- @Bind("numberOfDays") final int numberOfDays);
+ @RegisterMapper(UuidMapper.class)
+ UUID getInvoiceIdByPaymentAttemptId(@Bind("paymentAttemptId") final String paymentAttemptId);
- @SqlUpdate
- void notifySuccessfulPayment(@Bind("invoiceId") final String invoiceId,
- @Bind("amount") final BigDecimal paymentAmount,
- @Bind("currency") final String currency,
- @Bind("paymentId") final String paymentId,
- @Bind("paymentDate") final Date paymentDate);
+ @SqlQuery
+ @RegisterMapper(UuidMapper.class)
+ List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
+ @Bind("numberOfDays") final int numberOfDays);
- @SqlUpdate
- void notifyFailedPayment(@Bind("invoiceId") final String invoiceId,
- @Bind("paymentId") final String paymentId,
- @Bind("paymentAttemptDate") final Date paymentAttemptDate);
+ @SqlQuery
+ @RegisterMapper(BalanceMapper.class)
+ BigDecimal getAccountBalance(@Bind("accountId") final String accountId);
+ @SqlQuery
+ List<Invoice> getUnpaidInvoicesByAccountId(@Bind("accountId") final String accountId,
+ @Bind("upToDate") final Date upToDate);
@BindingAnnotation(InvoiceBinder.InvoiceBinderFactory.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface InvoiceBinder {
public static class InvoiceBinderFactory implements BinderFactory {
- public Binder build(Annotation annotation) {
+ @Override
+ public Binder<InvoiceBinder, Invoice> build(Annotation annotation) {
return new Binder<InvoiceBinder, Invoice>() {
- public void bind(SQLStatement q, InvoiceBinder bind, Invoice invoice) {
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement q, InvoiceBinder bind, Invoice invoice) {
q.bind("id", invoice.getId().toString());
q.bind("accountId", invoice.getAccountId().toString());
q.bind("invoiceDate", invoice.getInvoiceDate().toDate());
q.bind("targetDate", invoice.getTargetDate().toDate());
q.bind("amountPaid", invoice.getAmountPaid());
- q.bind("amountOutstanding", invoice.getAmountOutstanding());
+ q.bind("amountOutstanding", invoice.getBalance());
DateTime last_payment_date = invoice.getLastPaymentAttempt();
q.bind("lastPaymentAttempt", last_payment_date == null ? null : last_payment_date.toDate());
q.bind("currency", invoice.getCurrency().toString());
@@ -111,16 +118,30 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
UUID accountId = UUID.fromString(result.getString("account_id"));
DateTime invoiceDate = new DateTime(result.getTimestamp("invoice_date"));
DateTime targetDate = new DateTime(result.getTimestamp("target_date"));
+ Currency currency = Currency.valueOf(result.getString("currency"));
+
+ return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency);
+ }
+ }
+
+ public static class BalanceMapper implements ResultSetMapper<BigDecimal> {
+ @Override
+ public BigDecimal map(final int index, final ResultSet result, final StatementContext context) throws SQLException {
+ BigDecimal amountInvoiced = result.getBigDecimal("amount_invoiced");
BigDecimal amountPaid = result.getBigDecimal("amount_paid");
+
+ if (amountInvoiced == null) {
+ amountInvoiced = BigDecimal.ZERO;
+ }
+
if (amountPaid == null) {
amountPaid = BigDecimal.ZERO;
}
- Timestamp lastPaymentAttemptTimeStamp = result.getTimestamp("last_payment_attempt");
- DateTime lastPaymentAttempt = lastPaymentAttemptTimeStamp == null ? null : new DateTime(lastPaymentAttemptTimeStamp);
- Currency currency = Currency.valueOf(result.getString("currency"));
- return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
+ return amountInvoiced.subtract(amountPaid);
}
}
+
+
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
index 54990c5..1dfac5b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
@@ -16,16 +16,31 @@
package com.ning.billing.invoice.glue;
+import org.skife.config.ConfigurationObjectFactory;
+
import com.google.inject.AbstractModule;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.api.DefaultInvoiceService;
import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceService;
+import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
import com.ning.billing.invoice.api.user.DefaultInvoiceUserApi;
-import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.invoice.dao.DefaultInvoiceDao;
import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.notification.DefaultNextBillingDateNotifier;
+import com.ning.billing.invoice.notification.DefaultNextBillingDatePoster;
+import com.ning.billing.invoice.notification.NextBillingDateNotifier;
+import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.util.glue.ClockModule;
+import com.ning.billing.util.glue.GlobalLockerModule;
+
public class InvoiceModule extends AbstractModule {
- private void installInvoiceDao() {
+ protected void installInvoiceDao() {
bind(InvoiceDao.class).to(DefaultInvoiceDao.class).asEagerSingleton();
}
@@ -37,8 +52,36 @@ public class InvoiceModule extends AbstractModule {
bind(InvoicePaymentApi.class).to(DefaultInvoicePaymentApi.class).asEagerSingleton();
}
+ protected void installClock() {
+ install(new ClockModule());
+ }
+
+ protected void installConfig() {
+ final InvoiceConfig config = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
+ bind(InvoiceConfig.class).toInstance(config);
+ }
+
+ protected void installInvoiceService() {
+ bind(InvoiceService.class).to(DefaultInvoiceService.class).asEagerSingleton();
+ }
+
+ protected void installNotifier() {
+ bind(NextBillingDateNotifier.class).to(DefaultNextBillingDateNotifier.class).asEagerSingleton();
+ bind(NextBillingDatePoster.class).to(DefaultNextBillingDatePoster.class).asEagerSingleton();
+ }
+
+ protected void installInvoiceListener() {
+ install(new GlobalLockerModule());
+ bind(InvoiceListener.class).asEagerSingleton();
+ }
+
@Override
protected void configure() {
+ installInvoiceService();
+ installNotifier();
+ installInvoiceListener();
+ bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
+ installConfig();
installInvoiceDao();
installInvoiceUserApi();
installInvoicePaymentApi();
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
new file mode 100644
index 0000000..0a23d01
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.EntitlementBillingApi;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.model.BillingEventSet;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.util.globallocker.GlobalLock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.LockFailedException;
+import com.ning.billing.util.globallocker.GlobalLocker.LockerService;
+
+public class InvoiceDispatcher {
+ private final static Logger log = LoggerFactory.getLogger(InvoiceDispatcher.class);
+ private final static int NB_LOCK_TRY = 5;
+
+ private final InvoiceGenerator generator;
+ private final EntitlementBillingApi entitlementBillingApi;
+ private final AccountUserApi accountUserApi;
+ private final InvoiceDao invoiceDao;
+ private final GlobalLocker locker;
+
+ private final static boolean VERBOSE_OUTPUT = false;
+ @Inject
+ public InvoiceDispatcher(final InvoiceGenerator generator, final AccountUserApi accountUserApi,
+ final EntitlementBillingApi entitlementBillingApi,
+ final InvoiceDao invoiceDao,
+ final GlobalLocker locker) {
+ this.generator = generator;
+ this.entitlementBillingApi = entitlementBillingApi;
+ this.accountUserApi = accountUserApi;
+ this.invoiceDao = invoiceDao;
+ this.locker = locker;
+ }
+
+
+ public void processSubscription(final SubscriptionTransition transition) throws InvoiceApiException {
+ UUID subscriptionId = transition.getSubscriptionId();
+ DateTime targetDate = transition.getEffectiveTransitionTime();
+ log.info("Got subscription transition from InvoiceListener. id: " + subscriptionId.toString() + "; targetDate: " + targetDate.toString());
+ log.info("Transition type: " + transition.getTransitionType().toString());
+ processSubscription(subscriptionId, targetDate);
+ }
+
+ public void processSubscription(final UUID subscriptionId, final DateTime targetDate) throws InvoiceApiException {
+ if (subscriptionId == null) {
+ log.error("Failed handling entitlement change.", new InvoiceApiException(ErrorCode.INVOICE_INVALID_TRANSITION));
+ return;
+ }
+
+ UUID accountId = entitlementBillingApi.getAccountIdFromSubscriptionId(subscriptionId);
+ if (accountId == null) {
+ log.error("Failed handling entitlement change.",
+ new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
+ return;
+ }
+
+ GlobalLock lock = null;
+ try {
+ lock = locker.lockWithNumberOfTries(LockerService.INVOICE, accountId.toString(), NB_LOCK_TRY);
+
+ processAccountWithLock(accountId, targetDate);
+
+ } catch (LockFailedException e) {
+ // Not good!
+ log.error(String.format("Failed to process invoice for account %s, subscription %s, targetDate %s",
+ accountId.toString(), subscriptionId.toString(), targetDate), e);
+ } finally {
+ if (lock != null) {
+ lock.release();
+ }
+ }
+ }
+
+ private void processAccountWithLock(final UUID accountId, final DateTime targetDate) throws InvoiceApiException {
+
+ Account account = accountUserApi.getAccountById(accountId);
+ if (account == null) {
+ log.error("Failed handling entitlement change.",
+ new InvoiceApiException(ErrorCode.INVOICE_ACCOUNT_ID_INVALID, accountId.toString()));
+ return;
+ }
+
+ SortedSet<BillingEvent> events = entitlementBillingApi.getBillingEventsForAccount(accountId);
+ BillingEventSet billingEvents = new BillingEventSet(events);
+
+ Currency targetCurrency = account.getCurrency();
+
+ List<InvoiceItem> items = invoiceDao.getInvoiceItemsByAccount(accountId);
+ InvoiceItemList invoiceItemList = new InvoiceItemList(items);
+ Invoice invoice = generator.generateInvoice(accountId, billingEvents, invoiceItemList, targetDate, targetCurrency);
+
+ if (invoice == null) {
+ log.info("Generated null invoice.");
+ outputDebugData(events, invoiceItemList);
+ } else {
+ log.info("Generated invoice {} with {} items.", invoice.getId().toString(), invoice.getNumberOfItems());
+
+ if (VERBOSE_OUTPUT) {
+ log.info("New items");
+ for (InvoiceItem item : invoice.getInvoiceItems()) {
+ log.info(item.toString());
+ }
+ }
+ outputDebugData(events, invoiceItemList);
+
+ if (invoice.getNumberOfItems() > 0) {
+ invoiceDao.create(invoice);
+ }
+ }
+ }
+
+ private void outputDebugData(Collection<BillingEvent> events, Collection<InvoiceItem> invoiceItemList) {
+ if (VERBOSE_OUTPUT) {
+ log.info("Events");
+ for (BillingEvent event : events) {
+ log.info(event.toString());
+ }
+
+ log.info("Existing items");
+ for (InvoiceItem item : invoiceItemList) {
+ log.info(item.toString());
+ }
+ }
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
new file mode 100644
index 0000000..d31e98b
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceListener.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import com.ning.billing.invoice.api.InvoiceApiException;
+
+public class InvoiceListener {
+ private final static Logger log = LoggerFactory.getLogger(InvoiceListener.class);
+ private final InvoiceDispatcher dispatcher;
+
+ @Inject
+ public InvoiceListener(InvoiceDispatcher dispatcher) {
+ this.dispatcher = dispatcher;
+ }
+
+ @Subscribe
+ public void handleSubscriptionTransition(final SubscriptionTransition transition) {
+ try {
+ dispatcher.processSubscription(transition);
+ } catch (InvoiceApiException e) {
+ log.error(e.getMessage());
+ }
+ }
+
+ public void handleNextBillingDateEvent(final UUID subscriptionId, final DateTime eventDateTime) {
+ try {
+ dispatcher.processSubscription(subscriptionId, eventDateTime);
+ } catch (InvoiceApiException e) {
+ log.error(e.getMessage());
+ }
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
index 71c4765..4920b88 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingMode.java
@@ -19,14 +19,9 @@ package com.ning.billing.invoice.model;
import com.ning.billing.catalog.api.BillingPeriod;
import org.joda.time.DateTime;
-import java.math.BigDecimal;
+import java.util.List;
public interface BillingMode {
- BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
-
- BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
-
- DateTime calculateEffectiveEndDate(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
-
- DateTime calculateEffectiveEndDate(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod);
+ List<RecurringInvoiceItemData> calculateInvoiceItemData(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
+ List<RecurringInvoiceItemData> calculateInvoiceItemData(DateTime startDate, DateTime targetDate, int billingCycleDay, BillingPeriod billingPeriod) throws InvalidDateSequenceException;
}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
index 949f711..55c646c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/BillingModeBase.java
@@ -21,64 +21,62 @@ import org.joda.time.DateTime;
import java.math.BigDecimal;
-public abstract class BillingModeBase implements BillingMode {
- @Override
- public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
- if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
- if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-
- BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
-
- DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- DateTime endBillCycleDate;
- BigDecimal trailingProRation;
- BigDecimal numberOfBillingPeriods;
-
- DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
- endBillCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillCycleDate, billingCycleDay, billingPeriod);
- numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
-
- trailingProRation = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, endBillCycleDate, billingPeriod);
-
- return precedingProRation.add(numberOfBillingPeriods).add(trailingProRation);
- }
-
- @Override
- public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
- if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
-
- BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
-
- DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- DateTime endBillCycleDate = calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
- BigDecimal numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
-
- return precedingProRation.add(numberOfBillingPeriods);
- }
-
- DateTime buildDate(final int year, final int month, final int day) {
- return new DateTime(year, month, day, 0, 0, 0, 0);
- }
-
- boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
- return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
- }
-
- public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- protected abstract BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod);
-
- protected abstract DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay);
-
- protected abstract DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- protected abstract DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- protected abstract BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod);
-
- protected abstract BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod);
-
- protected abstract DateTime calculateEffectiveEndDate(final DateTime billCycleDate, final DateTime targetDate, final DateTime endDate, final BillingPeriod billingPeriod);
+public abstract class BillingModeBase {
+// public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+// if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+// if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//
+// if (billingPeriod == BillingPeriod.NO_BILLING_PERIOD) {
+// return BigDecimal.ZERO;
+// }
+//
+// BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
+//
+// DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+// DateTime endBillCycleDate;
+// BigDecimal trailingProRation;
+// BigDecimal numberOfBillingPeriods;
+//
+// DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
+// endBillCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillCycleDate, billingCycleDay, billingPeriod);
+// numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
+//
+// trailingProRation = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, endBillCycleDate, billingPeriod);
+//
+// return precedingProRation.add(numberOfBillingPeriods).add(trailingProRation);
+// }
+//
+// public BigDecimal calculateNumberOfBillingCycles(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+// if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+//
+// BigDecimal precedingProRation = calculateProRationBeforeFirstBillingPeriod(startDate, billingCycleDay, billingPeriod);
+//
+// DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+// DateTime endBillCycleDate = calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
+// BigDecimal numberOfBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillCycleDate, endBillCycleDate, billingPeriod);
+//
+// return precedingProRation.add(numberOfBillingPeriods);
+// }
+//
+// boolean isNotBetween(DateTime targetDate, DateTime startDate, DateTime endDate) {
+// return (targetDate.isBefore(startDate) || !targetDate.isBefore(endDate));
+// }
+//
+// public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// public abstract DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// protected abstract BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod);
+//
+// protected abstract DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay);
+//
+// protected abstract DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// protected abstract DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// protected abstract BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod);
+//
+// protected abstract BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod);
+//
+// protected abstract DateTime calculateEffectiveEndDate(final DateTime billCycleDate, final DateTime targetDate, final DateTime endDate, final BillingPeriod billingPeriod);
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
index 9f21ca6..b1e2fa9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DateRange.java
@@ -33,7 +33,11 @@ public class DateRange {
* @return whether the DateRange contains (inclusively) the DateTime in question
*/
public boolean contains(DateTime date) {
- return (!date.isBefore(startDate)) && (!date.isAfter(endDate));
+ if (endDate == null) {
+ return date.compareTo(startDate) >= 0;
+ }
+
+ return !date.isBefore(startDate) && !date.isAfter(endDate);
}
public boolean overlaps(DateRange range) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
index 97f82e0..6d419de 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
@@ -19,6 +19,8 @@ package com.ning.billing.invoice.model;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
import org.joda.time.DateTime;
@@ -27,56 +29,84 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.util.clock.DefaultClock;
+
public class DefaultInvoice implements Invoice {
- private final InvoiceItemList items = new InvoiceItemList();
+ private final InvoiceItemList invoiceItems = new InvoiceItemList();
+ private final List<InvoicePayment> payments = new ArrayList<InvoicePayment>();
private final UUID id;
- private UUID accountId;
+ private final UUID accountId;
private final DateTime invoiceDate;
private final DateTime targetDate;
- private Currency currency;
- private BigDecimal amountPaid;
- private DateTime lastPaymentAttempt;
-
- public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency) {
- this(UUID.randomUUID(), accountId, new DefaultClock().getUTCNow(), targetDate, currency, null, BigDecimal.ZERO, new ArrayList<InvoiceItem>());
- }
+ private final Currency currency;
- public DefaultInvoice(UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime targetDate,
- Currency currency, DateTime lastPaymentAttempt, BigDecimal amountPaid) {
- this(invoiceId, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
+ public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency, Clock clock) {
+ this(UUID.randomUUID(), accountId, clock.getUTCNow(), targetDate, currency);
}
public DefaultInvoice(UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime targetDate,
- Currency currency, DateTime lastPaymentAttempt, BigDecimal amountPaid,
- List<InvoiceItem> invoiceItems) {
+ Currency currency) {
this.id = invoiceId;
this.accountId = accountId;
this.invoiceDate = invoiceDate;
this.targetDate = targetDate;
this.currency = currency;
- this.lastPaymentAttempt= lastPaymentAttempt;
- this.amountPaid = amountPaid;
- this.items.addAll(invoiceItems);
}
@Override
- public boolean add(InvoiceItem item) {
- return items.add(item);
+ public boolean addInvoiceItem(final InvoiceItem item) {
+ return invoiceItems.add(item);
}
@Override
- public boolean add(List<InvoiceItem> items) {
- return this.items.addAll(items);
+ public boolean addInvoiceItems(final List<InvoiceItem> items) {
+ return this.invoiceItems.addAll(items);
}
@Override
- public List<InvoiceItem> getItems() {
- return items;
+ public List<InvoiceItem> getInvoiceItems() {
+ return invoiceItems;
+ }
+
+ @Override
+ public List<InvoiceItem> getInvoiceItems(Class clazz) {
+ List<InvoiceItem> results = new ArrayList<InvoiceItem>();
+ for (InvoiceItem item : invoiceItems) {
+ if (item.getClass() == clazz) {
+ results.add(item);
+ }
+ }
+ return results;
}
@Override
public int getNumberOfItems() {
- return items.size();
+ return invoiceItems.size();
+ }
+
+ @Override
+ public boolean addPayment(final InvoicePayment payment) {
+ return payments.add(payment);
+ }
+
+ @Override
+ public boolean addPayments(final List<InvoicePayment> payments) {
+ return this.payments.addAll(payments);
+ }
+
+ @Override
+ public List<InvoicePayment> getPayments() {
+ return payments;
+ }
+
+ @Override
+ public int getNumberOfPayments() {
+ return payments.size();
}
@Override
@@ -106,21 +136,40 @@ public class DefaultInvoice implements Invoice {
@Override
public DateTime getLastPaymentAttempt() {
+ DateTime lastPaymentAttempt = null;
+
+ for (final InvoicePayment paymentAttempt : payments) {
+ DateTime paymentAttemptDate = paymentAttempt.getPaymentAttemptDate();
+ if (lastPaymentAttempt == null) {
+ lastPaymentAttempt = paymentAttemptDate;
+ }
+
+ if (lastPaymentAttempt.isBefore(paymentAttemptDate)) {
+ lastPaymentAttempt = paymentAttemptDate;
+ }
+ }
+
return lastPaymentAttempt;
}
@Override
public BigDecimal getAmountPaid() {
+ BigDecimal amountPaid = BigDecimal.ZERO;
+ for (final InvoicePayment payment : payments) {
+ if (payment.getAmount() != null) {
+ amountPaid = amountPaid.add(payment.getAmount());
+ }
+ }
return amountPaid;
}
@Override
public BigDecimal getTotalAmount() {
- return items.getTotalAmount();
+ return invoiceItems.getTotalAmount();
}
@Override
- public BigDecimal getAmountOutstanding() {
+ public BigDecimal getBalance() {
return getTotalAmount().subtract(getAmountPaid());
}
@@ -130,11 +179,18 @@ public class DefaultInvoice implements Invoice {
return false;
}
+ DateTime lastPaymentAttempt = getLastPaymentAttempt();
if (lastPaymentAttempt == null) {
return true;
}
return lastPaymentAttempt.plusDays(numberOfDays).isBefore(targetDate);
}
+
+ @Override
+ public String toString() {
+ return "DefaultInvoice [items=" + invoiceItems + ", payments=" + payments + ", id=" + id + ", accountId=" + accountId + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + getAmountPaid() + ", lastPaymentAttempt=" + getLastPaymentAttempt() + "]";
+ }
+
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
index 3b9311e..039f305 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoiceGenerator.java
@@ -16,187 +16,251 @@
package com.ning.billing.invoice.model;
-
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
import java.util.UUID;
-import org.joda.time.DateTime;
-import org.joda.time.format.ISODateTimeFormat;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.InternationalPrice;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import org.joda.time.DateTime;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.entitlement.api.billing.BillingEvent;
-import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.invoice.api.BillingEventSet;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.util.clock.Clock;
+
+import javax.annotation.Nullable;
public class DefaultInvoiceGenerator implements InvoiceGenerator {
- private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
- @Override
- public Invoice generateInvoice(final UUID accountId, final BillingEventSet events, final InvoiceItemList existingItems, final DateTime targetDate, final Currency targetCurrency) {
- if (events == null) {return new DefaultInvoice(accountId, targetDate, targetCurrency);}
- if (events.size() == 0) {return new DefaultInvoice(accountId, targetDate, targetCurrency);}
+ private static final int ROUNDING_MODE = InvoicingConfiguration.getRoundingMode();
+ private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+ //private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceGenerator.class);
- DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency);
- InvoiceItemList currentItems = generateInvoiceItems(events, invoice.getId(), targetDate, targetCurrency);
- InvoiceItemList itemsToPost = reconcileInvoiceItems(invoice.getId(), currentItems, existingItems);
- invoice.add(itemsToPost);
+ private final Clock clock;
- return invoice;
+ @Inject
+ public DefaultInvoiceGenerator(Clock clock) {
+ this.clock = clock;
}
- private InvoiceItemList reconcileInvoiceItems(final UUID invoiceId, final InvoiceItemList currentInvoiceItems, final InvoiceItemList existingInvoiceItems) {
- InvoiceItemList currentItems = new InvoiceItemList();
- for (InvoiceItem item : currentInvoiceItems) {
- currentItems.add(new DefaultInvoiceItem(item, invoiceId));
+ @Override
+ public Invoice generateInvoice(final UUID accountId, final BillingEventSet events,
+ @Nullable final List<InvoiceItem> items, final DateTime targetDate,
+ final Currency targetCurrency) throws InvoiceApiException {
+ if ((events == null) || (events.size() == 0)) {
+ return null;
}
- InvoiceItemList existingItems = (InvoiceItemList) existingInvoiceItems.clone();
+ Collections.sort(events);
+
+ List<InvoiceItem> existingItems = new ArrayList<InvoiceItem>();
+ if (items != null) {
+ existingItems = new ArrayList<InvoiceItem>(items);
+ Collections.sort(existingItems);
+ }
- Collections.sort(currentItems);
- Collections.sort(existingItems);
+ DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, targetCurrency, clock);
+ UUID invoiceId = invoice.getId();
+ List<InvoiceItem> proposedItems = generateInvoiceItems(invoiceId, events, targetDate, targetCurrency);
- List<InvoiceItem> existingItemsToRemove = new ArrayList<InvoiceItem>();
+ if (existingItems != null) {
+ removeCancellingInvoiceItems(existingItems);
+ removeDuplicatedInvoiceItems(proposedItems, existingItems);
- for (InvoiceItem currentItem : currentItems) {
- // see if there are any existing items that are covered by the current item
for (InvoiceItem existingItem : existingItems) {
- if (currentItem.duplicates(existingItem)) {
- currentItem.subtract(existingItem);
- existingItemsToRemove.add(existingItem);
+ if (existingItem instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringItem = (RecurringInvoiceItem) existingItem;
+ proposedItems.add(recurringItem.asCredit());
}
}
}
- existingItems.removeAll(existingItemsToRemove);
+ if (proposedItems == null || proposedItems.size() == 0) {
+ return null;
+ } else {
+ invoice.addInvoiceItems(proposedItems);
+ return invoice;
+ }
+ }
- // remove cancelling pairs of invoice items
- existingItems.removeCancellingPairs();
+ /*
+ * removes all matching items from both submitted collections
+ */
+ private void removeDuplicatedInvoiceItems(final List<InvoiceItem> proposedItems,
+ final List<InvoiceItem> existingInvoiceItems) {
+ Iterator<InvoiceItem> proposedItemIterator = proposedItems.iterator();
+ while (proposedItemIterator.hasNext()) {
+ InvoiceItem proposedItem = proposedItemIterator.next();
+
+ Iterator<InvoiceItem> existingItemIterator = existingInvoiceItems.iterator();
+ while (existingItemIterator.hasNext()) {
+ InvoiceItem existingItem = existingItemIterator.next();
+ if (existingItem.equals(proposedItem)) {
+ existingItemIterator.remove();
+ proposedItemIterator.remove();
+ }
+ }
+ }
+ }
- // remove zero-dollar invoice items
- currentItems.removeZeroDollarItems();
+ private void removeCancellingInvoiceItems(final List<InvoiceItem> items) {
+ List<UUID> itemsToRemove = new ArrayList<UUID>();
- // add existing items that aren't covered by current items as credit items
- for (InvoiceItem existingItem : existingItems) {
- currentItems.add(existingItem.asCredit(invoiceId));
+ for (InvoiceItem item1 : items) {
+ if (item1 instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item1;
+ if (recurringInvoiceItem.reversesItem()) {
+ itemsToRemove.add(recurringInvoiceItem.getId());
+ itemsToRemove.add(recurringInvoiceItem.getReversedItemId());
+ }
+ }
}
- return currentItems;
+ Iterator<InvoiceItem> iterator = items.iterator();
+ while (iterator.hasNext()) {
+ InvoiceItem item = iterator.next();
+ if (itemsToRemove.contains(item.getId())) {
+ iterator.remove();
+ }
+ }
}
- private InvoiceItemList generateInvoiceItems(BillingEventSet events, UUID invoiceId, DateTime targetDate, Currency targetCurrency) {
- InvoiceItemList items = new InvoiceItemList();
+ private List<InvoiceItem> generateInvoiceItems(final UUID invoiceId, final BillingEventSet events,
+ final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+ List<InvoiceItem> items = new ArrayList<InvoiceItem>();
- // sort events; this relies on the sort order being by subscription id then start date
- Collections.sort(events);
-
- // for each event, process it either as a terminated event (if there's a subsequent event)
- // ...or as a non-terminated event (if no subsequent event exists)
- for (int i = 0; i < (events.size() - 1); i++) {
+ for (int i = 0; i < events.size(); i++) {
BillingEvent thisEvent = events.get(i);
- BillingEvent nextEvent = events.get(i + 1);
-
- if (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) {
- processEvents(invoiceId, thisEvent, nextEvent, items, targetDate, targetCurrency);
- } else {
- processEvent(invoiceId, thisEvent, items, targetDate, targetCurrency);
+ BillingEvent nextEvent = events.isLast(thisEvent) ? null : events.get(i + 1);
+ if (nextEvent != null) {
+ nextEvent = (thisEvent.getSubscription().getId() == nextEvent.getSubscription().getId()) ? nextEvent : null;
}
- }
- // process the last item in the event set
- if (events.size() > 0) {
- processEvent(invoiceId, events.getLast(), items, targetDate, targetCurrency);
+ items.addAll(processEvents(invoiceId, thisEvent, nextEvent, targetDate, currency));
}
return items;
}
- private void processEvent(UUID invoiceId, BillingEvent event, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
- try {
- //TODO: Jeff getPrice() -> getRecurringPrice()
- BigDecimal rate = event.getRecurringPrice(targetCurrency);
- BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(event, targetDate, rate);
- BillingMode billingMode = getBillingMode(event.getBillingMode());
- DateTime billThroughDate = billingMode.calculateEffectiveEndDate(event.getEffectiveDate(), targetDate, event.getBillCycleDay(), event.getBillingPeriod());
-
- addInvoiceItem(invoiceId, items, event, billThroughDate, invoiceItemAmount, rate, targetCurrency);
- } catch (CatalogApiException e) {
- log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
- invoiceId.toString(),
- ISODateTimeFormat.basicDateTime().print(event.getEffectiveDate())), e);
+ private List<InvoiceItem> processEvents(final UUID invoiceId, final BillingEvent thisEvent, final BillingEvent nextEvent,
+ final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+ List<InvoiceItem> items = new ArrayList<InvoiceItem>();
+ InvoiceItem fixedPriceInvoiceItem = generateFixedPriceItem(invoiceId, thisEvent, targetDate, currency);
+ if (fixedPriceInvoiceItem != null) {
+ items.add(fixedPriceInvoiceItem);
}
- }
- private void processEvents(UUID invoiceId, BillingEvent firstEvent, BillingEvent secondEvent, List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) {
- //TODO: Jeff getPrice() -> getRecurringPrice()
- try {
- BigDecimal rate = firstEvent.getRecurringPrice(targetCurrency);
- BigDecimal invoiceItemAmount = calculateInvoiceItemAmount(firstEvent, secondEvent, targetDate, rate);
- BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
- DateTime billThroughDate = billingMode.calculateEffectiveEndDate(firstEvent.getEffectiveDate(), secondEvent.getEffectiveDate(), targetDate, firstEvent.getBillCycleDay(), firstEvent.getBillingPeriod());
-
- addInvoiceItem(invoiceId, items, firstEvent, billThroughDate, invoiceItemAmount, rate, targetCurrency);
- } catch (CatalogApiException e) {
- log.error(String.format("Encountered a catalog error processing invoice %s for billing event on date %s",
- invoiceId.toString(),
- ISODateTimeFormat.basicDateTime().print(firstEvent.getEffectiveDate())), e);
- }
- }
-
- private void addInvoiceItem(UUID invoiceId, List<InvoiceItem> items, BillingEvent event, DateTime billThroughDate, BigDecimal amount, BigDecimal rate, Currency currency) {
- if (!(amount.compareTo(BigDecimal.ZERO) == 0)) {
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, event.getSubscription().getId(), event.getEffectiveDate(), billThroughDate, event.getDescription(), amount, rate, currency);
- items.add(item);
- }
- }
+ BillingPeriod billingPeriod = thisEvent.getBillingPeriod();
+ if (billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
+ BillingMode billingMode = instantiateBillingMode(thisEvent.getBillingMode());
+ DateTime startDate = thisEvent.getEffectiveDate();
+ if (!startDate.isAfter(targetDate)) {
+ DateTime endDate = (nextEvent == null) ? null : nextEvent.getEffectiveDate();
+ int billCycleDay = thisEvent.getBillCycleDay();
+
+ List<RecurringInvoiceItemData> itemData;
+ try {
+ itemData = billingMode.calculateInvoiceItemData(startDate, endDate, targetDate, billCycleDay, billingPeriod);
+ } catch (InvalidDateSequenceException e) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_DATE_SEQUENCE, startDate, endDate, targetDate);
+ }
- private BigDecimal calculateInvoiceItemAmount(BillingEvent event, DateTime targetDate, BigDecimal rate){
- BillingMode billingMode = getBillingMode(event.getBillingMode());
- DateTime startDate = event.getEffectiveDate();
- int billingCycleDay = event.getBillCycleDay();
- BillingPeriod billingPeriod = event.getBillingPeriod();
-
- try {
- BigDecimal numberOfBillingCycles;
- numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, billingPeriod);
- return numberOfBillingCycles.multiply(rate);
- } catch (InvalidDateSequenceException e) {
- // TODO: Jeff -- log issue
- return BigDecimal.ZERO;
+ for (RecurringInvoiceItemData itemDatum : itemData) {
+ InternationalPrice price = thisEvent.getRecurringPrice();
+ if (price != null) {
+ BigDecimal rate;
+
+ try {
+ rate = thisEvent.getRecurringPrice().getPrice(currency);
+ } catch (CatalogApiException e) {
+ throw new InvoiceApiException(e, ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency.toString());
+ }
+
+ BigDecimal amount = itemDatum.getNumberOfCycles().multiply(rate).setScale(NUMBER_OF_DECIMALS, ROUNDING_MODE);
+
+ RecurringInvoiceItem recurringItem = new RecurringInvoiceItem(invoiceId, thisEvent.getSubscription().getId(),
+ thisEvent.getPlan().getName(),
+ thisEvent.getPlanPhase().getName(),
+ itemDatum.getStartDate(), itemDatum.getEndDate(),
+ amount, rate, currency);
+ items.add(recurringItem);
+ }
+ }
+ }
}
- }
- private BigDecimal calculateInvoiceItemAmount(BillingEvent firstEvent, BillingEvent secondEvent, DateTime targetDate, BigDecimal rate) {
- BillingMode billingMode = getBillingMode(firstEvent.getBillingMode());
- DateTime startDate = firstEvent.getEffectiveDate();
- int billingCycleDay = firstEvent.getBillCycleDay();
- BillingPeriod billingPeriod = firstEvent.getBillingPeriod();
-
- DateTime endDate = secondEvent.getEffectiveDate();
-
- try {
- BigDecimal numberOfBillingCycles;
- numberOfBillingCycles = billingMode.calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, billingPeriod);
- return numberOfBillingCycles.multiply(rate);
- } catch (InvalidDateSequenceException e) {
- // TODO: Jeff -- log issue
- return BigDecimal.ZERO;
- }
+ return items;
}
- private BillingMode getBillingMode(BillingModeType billingModeType) {
- switch (billingModeType) {
+ private BillingMode instantiateBillingMode(BillingModeType billingMode) {
+ switch (billingMode) {
case IN_ADVANCE:
return new InAdvanceBillingMode();
default:
- return null;
+ throw new UnsupportedOperationException();
}
}
+
+ private InvoiceItem generateFixedPriceItem(final UUID invoiceId, final BillingEvent thisEvent,
+ final DateTime targetDate, final Currency currency) throws InvoiceApiException {
+ if (thisEvent.getEffectiveDate().isAfter(targetDate)) {
+ return null;
+ } else {
+ FixedPriceInvoiceItem fixedPriceInvoiceItem = null;
+
+ if (thisEvent.getFixedPrice() != null) {
+ try {
+ Duration duration = thisEvent.getPlanPhase().getDuration();
+ DateTime endDate = duration.addToDateTime(thisEvent.getEffectiveDate());
+ BigDecimal fixedPrice = thisEvent.getFixedPrice().getPrice(currency);
+ fixedPriceInvoiceItem = new FixedPriceInvoiceItem(invoiceId, thisEvent.getSubscription().getId(),
+ thisEvent.getPlan().getName(), thisEvent.getPlanPhase().getName(),
+ thisEvent.getEffectiveDate(), endDate, fixedPrice, currency);
+ } catch (CatalogApiException e) {
+ throw new InvoiceApiException(e, ErrorCode.CAT_NO_PRICE_FOR_CURRENCY, currency.toString());
+ }
+ }
+
+ return fixedPriceInvoiceItem;
+ }
+ }
+
+// // assumption: startDate is in the user's time zone
+// private DateTime calculateSegmentEndDate(final DateTime startDate, final DateTime nextEndDate,
+// final int billCycleDay, final BillingPeriod billingPeriod) {
+// int dayOfMonth = startDate.getDayOfMonth();
+// int maxDayOfMonth = startDate.dayOfMonth().getMaximumValue();
+//
+// DateTime nextBillingDate;
+//
+// // if the start date is not on the bill cycle day, move it to the nearest following date that works
+// if ((billCycleDay > maxDayOfMonth) || (dayOfMonth == billCycleDay)) {
+// nextBillingDate = startDate.plusMonths(billingPeriod.getNumberOfMonths());
+// } else {
+// MutableDateTime proposedDate = startDate.toMutableDateTime();
+//
+// if (dayOfMonth < billCycleDay) {
+// // move the end date forward to the bill cycle date (same month)
+// int effectiveBillCycleDay = (billCycleDay > maxDayOfMonth) ? maxDayOfMonth : billCycleDay;
+// nextBillingDate = proposedDate.dayOfMonth().set(effectiveBillCycleDay).toDateTime();
+// } else {
+// // go to the next month
+// proposedDate = proposedDate.monthOfYear().add(1);
+// maxDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
+// int effectiveBillCycleDay = (billCycleDay > maxDayOfMonth) ? maxDayOfMonth : billCycleDay;
+// nextBillingDate = proposedDate.dayOfMonth().set(effectiveBillCycleDay).toDateTime();
+// }
+// }
+//
+// return nextBillingDate.isAfter(nextEndDate) ? nextEndDate : nextBillingDate;
+// }
}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
new file mode 100644
index 0000000..6760481
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoicePayment.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.model;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoicePayment;
+import org.joda.time.DateTime;
+
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class DefaultInvoicePayment implements InvoicePayment {
+ private final UUID paymentAttemptId;
+ private final UUID invoiceId;
+ private final DateTime paymentDate;
+ private final BigDecimal amount;
+ private final Currency currency;
+ private final DateTime createdDate;
+ private final DateTime updatedDate;
+
+ public DefaultInvoicePayment(final UUID invoiceId, final DateTime paymentDate) {
+ this(UUID.randomUUID(), invoiceId, paymentDate, null, null, null, null);
+ }
+
+ public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate) {
+ this(paymentAttemptId, invoiceId, paymentDate, null, null, null, null);
+ }
+
+ public DefaultInvoicePayment(final UUID invoiceId, final DateTime paymentDate,
+ final BigDecimal amount, final Currency currency) {
+ this(UUID.randomUUID(), invoiceId, paymentDate, amount, currency, null, null);
+ }
+
+ public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
+ final BigDecimal amount, final Currency currency) {
+ this(paymentAttemptId, invoiceId, paymentDate, amount, currency, null, null);
+ }
+
+ public DefaultInvoicePayment(final UUID paymentAttemptId, final UUID invoiceId, final DateTime paymentDate,
+ @Nullable final BigDecimal amount, @Nullable final Currency currency,
+ @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate) {
+ this.paymentAttemptId = paymentAttemptId;
+ this.amount = amount;
+ this.invoiceId = invoiceId;
+ this.paymentDate = paymentDate;
+ this.currency = currency;
+ this.createdDate = (createdDate == null) ? new DateTime() : createdDate;
+ this.updatedDate = (updatedDate == null) ? new DateTime() : updatedDate;
+ }
+
+ @Override
+ public UUID getPaymentAttemptId() {
+ return paymentAttemptId;
+ }
+
+ @Override
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ @Override
+ public DateTime getPaymentAttemptDate() {
+ return paymentDate;
+ }
+
+ @Override
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
new file mode 100644
index 0000000..2265abd
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -0,0 +1,121 @@
+/*
+* Copyright 2010-2011 Ning, Inc.
+*
+* Ning licenses this file to you under the Apache License, version 2.0
+* (the "License"); you may not use this file except in compliance with the
+* License. You may obtain a copy of the License at:
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+* License for the specific language governing permissions and limitations
+* under the License.
+*/
+
+package com.ning.billing.invoice.model;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class FixedPriceInvoiceItem extends InvoiceItemBase {
+ public FixedPriceInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName, DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+ super(invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+ }
+
+ public FixedPriceInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName, DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency) {
+ super(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+ }
+
+ @Override
+ public InvoiceItem asCredit() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format("%s (fixed price) on %s", getPhaseName(), getStartDate().toString());
+ }
+
+ @Override
+ public int hashCode() {
+ int result = subscriptionId != null ? subscriptionId.hashCode() : 0;
+ result = 31 * result + (planName != null ? planName.hashCode() : 0);
+ result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
+ result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
+ result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+ result = 31 * result + (amount != null ? amount.hashCode() : 0);
+ result = 31 * result + (currency != null ? currency.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public int compareTo(InvoiceItem item) {
+ if (!(item instanceof FixedPriceInvoiceItem)) {
+ return 1;
+ }
+
+ FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) item;
+ int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+
+ if (compareSubscriptions == 0) {
+ return getStartDate().compareTo(that.getStartDate());
+ } else {
+ return compareSubscriptions;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(phaseName).append(", ");
+ sb.append(startDate.toString()).append(", ");
+ sb.append(endDate.toString()).append(", ");
+ sb.append(amount.toString()).append(", ");
+
+ return sb.toString();
+// StringBuilder sb = new StringBuilder();
+// sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+// sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+// sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+// sb.append("planName = ").append(planName).append(", ");
+// sb.append("phaseName = ").append(phaseName).append(", ");
+// sb.append("startDate = ").append(startDate.toString()).append(", ");
+// sb.append("endDate = ").append(endDate.toString()).append(", ");
+//
+// sb.append("amount = ");
+// if (amount == null) {
+// sb.append("null");
+// } else {
+// sb.append(amount.toString());
+// }
+//
+// sb.append("}");
+// return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FixedPriceInvoiceItem that = (FixedPriceInvoiceItem) o;
+
+ if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) return false;
+ if (currency != that.currency) return false;
+ if (startDate != null ? startDate.compareTo(that.startDate) != 0 : that.startDate != null) return false;
+ if (endDate != null ? endDate.compareTo(that.endDate) != 0 : that.endDate != null) return false;
+ if (phaseName != null ? !phaseName.equals(that.phaseName) : that.phaseName != null) return false;
+ if (planName != null ? !planName.equals(that.planName) : that.planName != null) return false;
+ if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
+ return false;
+
+ return true;
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
index 37b5820..4d3dff9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
@@ -20,142 +20,218 @@ import com.ning.billing.catalog.api.BillingPeriod;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Months;
+import org.joda.time.MutableDateTime;
import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
-public class InAdvanceBillingMode extends BillingModeBase {
- private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+public class InAdvanceBillingMode implements BillingMode {
+ private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
@Override
- public DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- return calculateBillingCycleDateAfter(targetDate, firstBillCycleDate, billingCycleDay, billingPeriod);
- }
-
- @Override
- public DateTime calculateEffectiveEndDate(final DateTime startDate, final DateTime endDate, final DateTime targetDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime firstBillCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- return calculateEffectiveEndDate(firstBillCycleDate, targetDate, endDate, billingPeriod);
- }
+ public List<RecurringInvoiceItemData> calculateInvoiceItemData(final DateTime startDate, final DateTime endDate,
+ final DateTime targetDate, final int billingCycleDay,
+ final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+ if (endDate == null) {
+ return calculateInvoiceItemData(startDate, targetDate, billingCycleDay, billingPeriod);
+ }
- @Override
- protected BigDecimal calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod) {
- int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
- BigDecimal numberOfMonthsInPeriod = new BigDecimal(billingPeriod.getNumberOfMonths());
- return new BigDecimal(numberOfMonths).divide(numberOfMonthsInPeriod, 0, ROUNDING_METHOD);
- }
+ if (endDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
+ if (targetDate.isBefore(startDate)) {throw new InvalidDateSequenceException();}
- @Override
- protected DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
- int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+ List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
- DateTime proposedDate;
- if (billingCycleDay > lastDayOfMonth) {
- proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), lastDayOfMonth);
- } else {
- proposedDate = buildDate(date.getYear(), date.getMonthOfYear(), billingCycleDay);
- }
+ // beginning from the start date, find the first billing date
+ DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- while (proposedDate.isBefore(date)) {
- proposedDate = proposedDate.plusMonths(1);
+ // add pro-ration item if needed
+ if (firstBillingCycleDate.isAfter(startDate)) {
+ BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
+ if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+ results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+ }
}
- return proposedDate;
- }
-
- protected DateTime calculateBillingCycleDateAfter(final DateTime date, final DateTime billingCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime proposedDate = billingCycleDate;
-
- while (!proposedDate.isAfter(date)) {
- proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+ // add one item per billing period
+ DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, endDate, billingPeriod);
+ DateTime lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
+ int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
+ int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
- if (proposedDate.dayOfMonth().get() != billingCycleDay) {
- int lastDayOfMonth = proposedDate.dayOfMonth().getMaximumValue();
+ for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
+ results.add(new RecurringInvoiceItemData(firstBillingCycleDate.plusMonths(i * numberOfMonthsPerBillingPeriod),
+ firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod), BigDecimal.ONE));
+ }
- if (lastDayOfMonth < billingCycleDay) {
- proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfMonth);
- } else {
- proposedDate = buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
- }
+ // check to see if a trailing pro-ration amount is needed
+ if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
+ BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
+ if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+ results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
}
}
- return proposedDate;
+ return results;
}
@Override
- protected DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime proposedDate = previousBillCycleDate;
- proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+ public List<RecurringInvoiceItemData> calculateInvoiceItemData(final DateTime startDate,
+ final DateTime targetDate, final int billingCycleDay,
+ final BillingPeriod billingPeriod) throws InvalidDateSequenceException {
+ List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
+
+ if (targetDate.isBefore(startDate)) {
+ // since the target date is before the start date of the event, this should result in no items being generated
+ throw new InvalidDateSequenceException();
+ }
- if (!proposedDate.isBefore(date)) {return previousBillCycleDate;}
+ // beginning from the start date, find the first billing date
+ DateTime firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
- while (proposedDate.isBefore(date)) {
- proposedDate = proposedDate.plusMonths(billingPeriod.getNumberOfMonths());
+ // add pro-ration item if needed
+ if (firstBillingCycleDate.isAfter(startDate)) {
+ BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
+ if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+ results.add(new RecurringInvoiceItemData(startDate, firstBillingCycleDate, leadingProRationPeriods));
+ }
}
- proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+ // add one item per billing period
+ DateTime effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, billingPeriod);
+ DateTime lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDay, billingPeriod);
+ int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
+ int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
- if (proposedDate.dayOfMonth().get() < billingCycleDay) {
- int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
- if (lastDayOfTheMonth < billingCycleDay) {
- return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
- } else {
- return buildDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
+ for (int i = 0; i < numberOfWholeBillingPeriods; i++) {
+ results.add(new RecurringInvoiceItemData(firstBillingCycleDate.plusMonths(i * numberOfMonthsPerBillingPeriod),
+ firstBillingCycleDate.plusMonths((i + 1) * numberOfMonthsPerBillingPeriod), BigDecimal.ONE));
+ }
+
+ // check to see if a trailing pro-ration amount is needed
+ if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
+ BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
+ if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
+ results.add(new RecurringInvoiceItemData(lastBillingCycleDate, effectiveEndDate, trailingProRationPeriods));
}
+ }
+
+ return results;
+ }
+
+ private DateTime calculateBillingCycleDateOnOrAfter(final DateTime date, final int billingCycleDay) {
+ int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
+
+ MutableDateTime tmp = date.toMutableDateTime();
+ if (billingCycleDay > lastDayOfMonth) {
+ tmp.setDayOfMonth(lastDayOfMonth);
} else {
- return proposedDate;
+ tmp.setDayOfMonth(billingCycleDay);
}
+ DateTime proposedDate = tmp.toDateTime();
+
+ while (proposedDate.isBefore(date)) {
+ // STEPH could be an annual ?
+ proposedDate = proposedDate.plusMonths(1);
+ }
+ return proposedDate;
}
- @Override
- protected BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
- DateTime nextBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, billingCycleDay);
+ private BigDecimal calculateProRationBeforeFirstBillingPeriod(final DateTime startDate, DateTime nextBillingCycleDate, final BillingPeriod billingPeriod) {
DateTime previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
- BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays());
+ int daysBetween = Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays();
+ if (daysBetween <= 0) {
+ return BigDecimal.ZERO;
+ }
+
+ BigDecimal daysInPeriod = new BigDecimal(daysBetween);
BigDecimal days = new BigDecimal(Days.daysBetween(startDate, nextBillingCycleDate).getDays());
return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
}
- @Override
- protected BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod) {
- // note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
- DateTime nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
- BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillThroughDate, nextBillThroughDate).getDays());
-
- BigDecimal days = new BigDecimal(Days.daysBetween(previousBillThroughDate, endDate).getDays());
-
- return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+ private int calculateNumberOfWholeBillingPeriods(final DateTime startDate, final DateTime endDate, final BillingPeriod billingPeriod) {
+ int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
+ int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+ return numberOfMonths / numberOfMonthsInPeriod;
}
- @Override
- protected DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod) {
+ private DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, DateTime endDate, BillingPeriod billingPeriod) {
if (targetDate.isBefore(endDate)) {
if (targetDate.isBefore(billCycleDate)) {
return billCycleDate;
}
int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
- DateTime startOfPeriod = billCycleDate;
- DateTime startOfNextPeriod = billCycleDate.plusMonths(numberOfMonthsInPeriod);
+ int numberOfPeriods = 0;
+ DateTime proposedDate = billCycleDate;
- while (isNotBetween(targetDate, startOfPeriod, startOfNextPeriod)) {
- startOfPeriod = startOfNextPeriod;
- startOfNextPeriod = startOfPeriod.plusMonths(numberOfMonthsInPeriod);
+ while (!proposedDate.isAfter(targetDate)) {
+ proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+ numberOfPeriods += 1;
}
// the current period includes the target date
// check to see whether the end date truncates the period
- if (endDate.isBefore(startOfNextPeriod)) {
+ if (endDate.isBefore(proposedDate)) {
return endDate;
} else {
- return startOfNextPeriod;
+ return proposedDate;
}
} else {
return endDate;
}
}
+
+ private DateTime calculateEffectiveEndDate(DateTime billCycleDate, DateTime targetDate, BillingPeriod billingPeriod) {
+ if (targetDate.isBefore(billCycleDate)) {
+ return billCycleDate;
+ }
+
+ int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
+ int numberOfPeriods = 0;
+ DateTime proposedDate = billCycleDate;
+
+ while (!proposedDate.isAfter(targetDate)) {
+ proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+ numberOfPeriods += 1;
+ }
+
+ return proposedDate;
+ }
+
+ private DateTime calculateLastBillingCycleDateBefore(final DateTime date, final DateTime previousBillCycleDate, final int billingCycleDay, final BillingPeriod billingPeriod) {
+ DateTime proposedDate = previousBillCycleDate;
+
+ int numberOfPeriods = 0;
+ while (!proposedDate.isAfter(date)) {
+ proposedDate = previousBillCycleDate.plusMonths(numberOfPeriods * billingPeriod.getNumberOfMonths());
+ numberOfPeriods += 1;
+ }
+
+ proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+
+ if (proposedDate.dayOfMonth().get() < billingCycleDay) {
+ int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
+ if (lastDayOfTheMonth < billingCycleDay) {
+ return new MutableDateTime(proposedDate).dayOfMonth().set(lastDayOfTheMonth).toDateTime();
+ } else {
+ return new MutableDateTime(proposedDate).dayOfMonth().set(billingCycleDay).toDateTime();
+ }
+ } else {
+ return proposedDate;
+ }
+ }
+
+ private BigDecimal calculateProRationAfterLastBillingCycleDate(final DateTime endDate, final DateTime previousBillThroughDate, final BillingPeriod billingPeriod) {
+ // note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
+ DateTime nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
+ BigDecimal daysInPeriod = new BigDecimal(Days.daysBetween(previousBillThroughDate, nextBillThroughDate).getDays());
+
+ BigDecimal days = new BigDecimal(Days.daysBetween(previousBillThroughDate, endDate).getDays());
+
+ return days.divide(daysInPeriod, NUMBER_OF_DECIMALS, ROUNDING_METHOD);
+ }
}
\ No newline at end of file
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
index 6066a75..6bc6c9d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceGenerator.java
@@ -17,12 +17,15 @@
package com.ning.billing.invoice.model;
import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.BillingEventSet;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
import org.joda.time.DateTime;
+import javax.annotation.Nullable;
+import java.util.List;
import java.util.UUID;
public interface InvoiceGenerator {
- public Invoice generateInvoice(UUID accountId, BillingEventSet events, InvoiceItemList items, DateTime targetDate, Currency targetCurrency);
+ public Invoice generateInvoice(UUID accountId, BillingEventSet events, @Nullable List<InvoiceItem> items, DateTime targetDate, Currency targetCurrency) throws InvoiceApiException;
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
index fb82f06..683bbaf 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
@@ -16,52 +16,73 @@
package com.ning.billing.invoice.model;
-import com.ning.billing.invoice.api.InvoiceItem;
-
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
+import com.ning.billing.invoice.api.InvoiceItem;
public class InvoiceItemList extends ArrayList<InvoiceItem> {
private static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
+ private static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
- public BigDecimal getTotalAmount() {
- // TODO: Jeff -- naive implementation, assumes all invoice items share the same currency
- BigDecimal total = new BigDecimal("0");
-
- for (InvoiceItem item : this) {
- total = total.add(item.getAmount());
- }
+ public InvoiceItemList() {
+ super();
+ }
- return total.setScale(NUMBER_OF_DECIMALS);
+ public InvoiceItemList(final List<InvoiceItem> invoiceItems) {
+ super();
+ this.addAll(invoiceItems);
}
- public void removeZeroDollarItems() {
- List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
+ public BigDecimal getTotalAmount() {
+ // naive implementation, assumes all invoice items share the same currency
+ BigDecimal total = BigDecimal.ZERO.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
- for (InvoiceItem item : this) {
- if (item.getAmount().compareTo(BigDecimal.ZERO) == 0) {
- itemsToRemove.add(item);
+ for (final InvoiceItem item : this) {
+ if (item.getAmount() != null) {
+ total = total.add(item.getAmount());
}
}
- this.removeAll(itemsToRemove);
+ return total.setScale(NUMBER_OF_DECIMALS, ROUNDING_METHOD);
}
- public void removeCancellingPairs() {
- List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
-
- for (int firstItemIndex = 0; firstItemIndex < this.size(); firstItemIndex++) {
- for (int secondItemIndex = firstItemIndex + 1; secondItemIndex < this.size(); secondItemIndex++) {
- InvoiceItem firstItem = this.get(firstItemIndex);
- InvoiceItem secondItem = this.get(secondItemIndex);
- if (firstItem.cancels(secondItem)) {
- itemsToRemove.add(firstItem);
- itemsToRemove.add(secondItem);
- }
- }
- }
+// public void removeCancellingPairs() {
+// List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
+//
+// for (int firstItemIndex = 0; firstItemIndex < this.size(); firstItemIndex++) {
+// for (int secondItemIndex = firstItemIndex + 1; secondItemIndex < this.size(); secondItemIndex++) {
+// InvoiceItem firstItem = this.get(firstItemIndex);
+// InvoiceItem secondItem = this.get(secondItemIndex);
+// if (firstItem.cancels(secondItem)) {
+// itemsToRemove.add(firstItem);
+// itemsToRemove.add(secondItem);
+// }
+// }
+// }
+//
+// this.removeAll(itemsToRemove);
+// }
- this.removeAll(itemsToRemove);
- }
+// /*
+// * removes recurring items from the list that have a recurring amount of zero, but a recurring rate that is not zero
+// */
+// public void cleanupDuplicatedItems() {
+// Iterator<InvoiceItem> iterator = this.iterator();
+// while (iterator.hasNext()) {
+// InvoiceItem item = iterator.next();
+//
+// if (item instanceof RecurringInvoiceItem) {
+// RecurringInvoiceItem that = (RecurringInvoiceItem) item;
+// boolean recurringRateNull = (that.getRate() == null);
+// boolean recurringAmountZero = (that.getAmount() !=null) && (that.getAmount().compareTo(BigDecimal.ZERO) == 0);
+//
+// if (recurringRateNull || recurringAmountZero) {
+// iterator.remove();
+// } else if (that.getEndDate() != null && that.getStartDate().compareTo(that.getEndDate()) == 0) {
+// iterator.remove();
+// }
+// }
+// }
+// }
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
index 7482465..554dc62 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoicingConfiguration.java
@@ -22,7 +22,7 @@ public class InvoicingConfiguration {
private final static int roundingMethod = BigDecimal.ROUND_HALF_UP;
private final static int numberOfDecimals = 4;
- public static int getRoundingMethod() {
+ public static int getRoundingMode() {
return roundingMethod;
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
new file mode 100644
index 0000000..93ef474
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItem.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.model;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+public class RecurringInvoiceItem extends InvoiceItemBase {
+ private final BigDecimal rate;
+ private final UUID reversedItemId;
+
+ public RecurringInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, BigDecimal rate,
+ Currency currency) {
+ this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+ amount, rate, currency);
+ }
+
+ public RecurringInvoiceItem(UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, BigDecimal rate,
+ Currency currency, UUID reversedItemId) {
+ this(UUID.randomUUID(), invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+ amount, rate, currency, reversedItemId);
+ }
+
+ public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, BigDecimal rate,
+ Currency currency) {
+ super(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+
+ this.rate = rate;
+ this.reversedItemId = null;
+ }
+
+ public RecurringInvoiceItem(UUID id, UUID invoiceId, UUID subscriptionId, String planName, String phaseName,
+ DateTime startDate, DateTime endDate,
+ BigDecimal amount, BigDecimal rate,
+ Currency currency, UUID reversedItemId) {
+ super(id, invoiceId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency);
+
+ this.rate = rate;
+ this.reversedItemId = reversedItemId;
+ }
+
+ @Override
+ public InvoiceItem asCredit() {
+ BigDecimal amountNegated = amount == null ? null : amount.negate();
+ return new RecurringInvoiceItem(invoiceId, subscriptionId, planName, phaseName, startDate, endDate,
+ amountNegated, rate, currency, id);
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format("%s from %s to %s", phaseName, startDate.toString(), endDate.toString());
+ }
+
+ public UUID getReversedItemId() {
+ return reversedItemId;
+ }
+
+ public boolean reversesItem() {
+ return (reversedItemId != null);
+ }
+
+ public BigDecimal getRate() {
+ return rate;
+ }
+
+ @Override
+ public int compareTo(InvoiceItem item) {
+ if (item == null) {return -1;}
+ if (!(item instanceof RecurringInvoiceItem)) {return -1;}
+
+ RecurringInvoiceItem that = (RecurringInvoiceItem) item;
+
+ int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+ if (compareSubscriptions == 0) {
+ int compareStartDates = getStartDate().compareTo(that.getStartDate());
+ if (compareStartDates == 0) {
+ return getEndDate().compareTo(that.getEndDate());
+ } else {
+ return compareStartDates;
+ }
+ } else {
+ return compareSubscriptions;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RecurringInvoiceItem that = (RecurringInvoiceItem) o;
+
+ if (amount.compareTo(that.amount) != 0) return false;
+ if (currency != that.currency) return false;
+ if (startDate.compareTo(that.startDate) != 0) return false;
+ if (endDate.compareTo(that.endDate) != 0) return false;
+ if (!phaseName.equals(that.phaseName)) return false;
+ if (!planName.equals(that.planName)) return false;
+ if (rate.compareTo(that.rate) != 0) return false;
+ if (reversedItemId != null ? !reversedItemId.equals(that.reversedItemId) : that.reversedItemId != null)
+ return false;
+ if (!subscriptionId.equals(that.subscriptionId)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = invoiceId.hashCode();
+ result = 31 * result + subscriptionId.hashCode();
+ result = 31 * result + planName.hashCode();
+ result = 31 * result + phaseName.hashCode();
+ result = 31 * result + startDate.hashCode();
+ result = 31 * result + endDate.hashCode();
+ result = 31 * result + amount.hashCode();
+ result = 31 * result + rate.hashCode();
+ result = 31 * result + currency.hashCode();
+ result = 31 * result + (reversedItemId != null ? reversedItemId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(phaseName).append(", ");
+ sb.append(startDate.toString()).append(", ");
+ sb.append(endDate.toString()).append(", ");
+ sb.append(amount.toString()).append(", ");
+
+ return sb.toString();
+
+// StringBuilder sb = new StringBuilder();
+// sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+// sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+// sb.append("subscriptionId = ").append(subscriptionId.toString()).append(", ");
+// sb.append("planName = ").append(planName).append(", ");
+// sb.append("phaseName = ").append(phaseName).append(", ");
+// sb.append("startDate = ").append(startDate.toString()).append(", ");
+// if (endDate != null) {
+// sb.append("endDate = ").append(endDate.toString()).append(", ");
+// } else {
+// sb.append("endDate = null");
+// }
+// sb.append("recurringAmount = ");
+// if (amount == null) {
+// sb.append("null");
+// } else {
+// sb.append(amount.toString());
+// }
+// sb.append(", ");
+//
+// sb.append("recurringRate = ");
+// if (rate == null) {
+// sb.append("null");
+// } else {
+// sb.append(rate.toString());
+// }
+// sb.append(", ");
+//
+// sb.append("}");
+// return sb.toString();
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
new file mode 100644
index 0000000..d42c533
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/RecurringInvoiceItemData.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.model;
+
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+
+public class RecurringInvoiceItemData {
+ private final DateTime startDate;
+ private final DateTime endDate;
+ private final BigDecimal numberOfCycles;
+
+ public RecurringInvoiceItemData(DateTime startDate, DateTime endDate, BigDecimal numberOfCycles) {
+ this.startDate = startDate;
+ this.endDate = endDate;
+ this.numberOfCycles = numberOfCycles;
+ }
+
+ public DateTime getStartDate() {
+ return startDate;
+ }
+
+ public DateTime getEndDate() {
+ return endDate;
+ }
+
+ public BigDecimal getNumberOfCycles() {
+ return numberOfCycles;
+ }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
new file mode 100644
index 0000000..0dd5e3a
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.notification;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.api.DefaultInvoiceService;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.notificationq.NotificationConfig;
+import com.ning.billing.util.notificationq.NotificationKey;
+import com.ning.billing.util.notificationq.NotificationQueue;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists;
+import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+
+public class DefaultNextBillingDateNotifier implements NextBillingDateNotifier {
+
+ private final static Logger log = LoggerFactory.getLogger(DefaultNextBillingDateNotifier.class);
+
+ public static final String NEXT_BILLING_DATE_NOTIFIER_QUEUE = "next-billing-date-queue";
+
+ private final NotificationQueueService notificationQueueService;
+ private final InvoiceConfig config;
+ private final EntitlementDao entitlementDao;
+
+ private NotificationQueue nextBillingQueue;
+ private InvoiceListener listener;
+
+ @Inject
+ public DefaultNextBillingDateNotifier(NotificationQueueService notificationQueueService,
+ InvoiceConfig config, EntitlementDao entitlementDao, InvoiceListener listener){
+ this.notificationQueueService = notificationQueueService;
+ this.config = config;
+ this.entitlementDao = entitlementDao;
+ this.listener = listener;
+ }
+
+ @Override
+ public void initialize() {
+ try {
+ nextBillingQueue = notificationQueueService.createNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+ NEXT_BILLING_DATE_NOTIFIER_QUEUE,
+ new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey, DateTime eventDate) {
+ try {
+ UUID key = UUID.fromString(notificationKey);
+ Subscription subscription = entitlementDao.getSubscriptionFromId(key);
+ if (subscription == null) {
+ log.warn("Next Billing Date Notification Queue handled spurious notification (key: " + key + ")" );
+ } else {
+ processEvent(key , eventDate);
+ }
+ } catch (IllegalArgumentException e) {
+ log.error("The key returned from the NextBillingNotificationQueue is not a valid UUID", e);
+ return;
+ }
+
+ }
+ },
+ new NotificationConfig() {
+ @Override
+ public boolean isNotificationProcessingOff() {
+ return config.isEventProcessingOff();
+ }
+ @Override
+ public long getNotificationSleepTimeMs() {
+ return config.getNotificationSleepTimeMs();
+ }
+ @Override
+ public int getDaoMaxReadyEvents() {
+ return config.getDaoMaxReadyEvents();
+ }
+ @Override
+ public long getDaoClaimTimeMs() {
+ return config.getDaoMaxReadyEvents();
+ }
+ });
+ } catch (NotificationQueueAlreadyExists e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void start() {
+ nextBillingQueue.startQueue();
+ }
+
+ @Override
+ public void stop() {
+ if (nextBillingQueue != null) {
+ nextBillingQueue.stopQueue();
+ }
+ }
+
+ private void processEvent(UUID subscriptionId, DateTime eventDateTime) {
+ listener.handleNextBillingDateEvent(subscriptionId, eventDateTime);
+ }
+
+
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java
new file mode 100644
index 0000000..ea630aa
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDateNotifier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.notification;
+
+
+public interface NextBillingDateNotifier {
+
+ public void initialize();
+
+ public void start();
+
+ public void stop();
+
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.java b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.java
new file mode 100644
index 0000000..67598d1
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/NextBillingDatePoster.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.notification;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+public interface NextBillingDatePoster {
+
+ void insertNextBillingNotification(Transmogrifier transactionalDao,
+ UUID subscriptionId, DateTime futureNotificationTime);
+
+}
\ No newline at end of file
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
new file mode 100644
index 0000000..90310d5
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/FixedPriceInvoiceItemSqlDao.sql.stg
@@ -0,0 +1,63 @@
+group FixedPriceInvoiceItemSqlDao;
+
+fields(prefix) ::= <<
+ <prefix>id,
+ <prefix>invoice_id,
+ <prefix>subscription_id,
+ <prefix>plan_name,
+ <prefix>phase_name,
+ <prefix>start_date,
+ <prefix>end_date,
+ <prefix>amount,
+ <prefix>currency
+>>
+
+getById() ::= <<
+ SELECT <fields()>
+ FROM fixed_invoice_items
+ WHERE id = :id;
+>>
+
+getInvoiceItemsByInvoice() ::= <<
+ SELECT <fields()>
+ FROM fixed_invoice_items
+ WHERE invoice_id = :invoiceId;
+>>
+
+getInvoiceItemsByAccount() ::= <<
+ SELECT <fields("rii.")>
+ FROM fixed_invoice_items rii
+ INNER JOIN invoices i ON i.id = rii.invoice_id
+ WHERE i.account_id = :accountId;
+>>
+
+getInvoiceItemsBySubscription() ::= <<
+ SELECT <fields()>
+ FROM fixed_invoice_items
+ WHERE subscription_id = :subscriptionId;
+>>
+
+create() ::= <<
+ INSERT INTO fixed_invoice_items(<fields()>)
+ VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName,
+ :startDate, :endDate, :amount, :currency);
+>>
+
+batchCreateFromTransaction() ::= <<
+ INSERT INTO fixed_invoice_items(<fields()>)
+ VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName,
+ :startDate, :endDate, :amount, :currency);
+>>
+
+update() ::= <<
+ UPDATE fixed_invoice_items
+ SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+ start_date = :startDate, end_date = :endDate, amount = :amount, currency = :currency
+ WHERE id = :id;
+>>
+
+test() ::= <<
+ SELECT 1
+ FROM fixed_invoice_items;
+>>
+;
\ No newline at end of file
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
new file mode 100644
index 0000000..2172573
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -0,0 +1,61 @@
+group InvoicePayment;
+
+invoicePaymentFields(prefix) ::= <<
+ <prefix>invoice_id,
+ <prefix>payment_attempt_id,
+ <prefix>payment_attempt_date,
+ <prefix>amount,
+ <prefix>currency,
+ <prefix>created_date,
+ <prefix>updated_date
+>>
+
+create() ::= <<
+ INSERT INTO invoice_payments(<invoicePaymentFields()>)
+ VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, :createdDate, :updatedDate);
+>>
+
+batchCreateFromTransaction() ::= <<
+ INSERT INTO invoice_payments(<invoicePaymentFields()>)
+ VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, :createdDate, :updatedDate);
+>>
+
+
+update() ::= <<
+ UPDATE invoice_payments
+ SET payment_date = :paymentAttemptDate, amount = :amount, currency = :currency, created_date = :createdDate, updated_date = :updatedDate
+ WHERE invoice_id = :invoiceId, payment_attempt_id = :paymentAttemptId;
+>>
+
+getByPaymentAttemptId() ::= <<
+ SELECT <invoicePaymentFields()>
+ FROM invoice_payments
+ WHERE payment_id = :paymentAttemptId;
+>>
+
+get() ::= <<
+ SELECT <invoicePaymentFields()>
+ FROM invoice_payments;
+>>
+
+getPaymentsForInvoice() ::= <<
+ SELECT <invoicePaymentFields()>
+ FROM invoice_payments
+ WHERE invoice_id = :invoiceId;
+>>
+
+notifyOfPaymentAttempt() ::= <<
+ INSERT INTO invoice_payments(<invoicePaymentFields()>)
+ VALUES(:invoiceId, :paymentAttemptId, :paymentAttemptDate, :amount, :currency, NOW(), NOW());
+>>
+
+getInvoicePayment() ::= <<
+ SELECT <invoicePaymentFields()>
+ FROM invoice_payments
+ WHERE payment_id = :payment_id;
+>>
+
+test() ::= <<
+ SELECT 1 FROM invoice_payments;
+>>
+;
\ No newline at end of file
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
index 8766e18..35c45bf 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -1,34 +1,39 @@
group InvoiceDao;
+invoiceFields(prefix) ::= <<
+ <prefix>id,
+ <prefix>account_id,
+ <prefix>invoice_date,
+ <prefix>target_date,
+ <prefix>currency
+>>
+
get() ::= <<
- SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
- SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
- FROM invoices i
- LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
- LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
- ORDER BY i.invoice_date ASC;
+ SELECT <invoiceFields()>
+ FROM invoices
+ ORDER BY target_date ASC;
>>
getInvoicesByAccount() ::= <<
- SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
- SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
- FROM invoices i
- LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
- LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
- WHERE i.account_id = :accountId
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
- ORDER BY i.invoice_date ASC;
+ SELECT <invoiceFields()>
+ FROM invoices
+ WHERE account_id = :accountId
+ ORDER BY target_date ASC;
+>>
+
+getInvoicesByAccountAfterDate() ::= <<
+ SELECT <invoiceFields()>
+ FROM invoices
+ WHERE account_id = :accountId AND target_date >= :fromDate
+ ORDER BY target_date ASC;
>>
getInvoicesBySubscription() ::= <<
- SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
- SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+ SELECT <invoiceFields("i.")>
FROM invoices i
- LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
- LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
- WHERE ii.subscription_id = :subscriptionId
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+ LEFT JOIN recurring_invoice_items rii ON i.id = rii.invoice_id
+ WHERE rii.subscription_id = :subscriptionId
+ GROUP BY <invoiceFields("i.")>;
>>
getInvoicesForPayment() ::= <<
@@ -37,41 +42,56 @@ getInvoicesForPayment() ::= <<
LEFT JOIN invoice_payment_summary ips ON ips.invoice_id = i.id
LEFT JOIN invoice_item_summary iis ON iis.invoice_id = i.id
WHERE ((ips.last_payment_date IS NULL) OR (DATEDIFF(:targetDate, ips.last_payment_date) >= :numberOfDays))
- AND ((ips.total_paid IS NULL) OR (iis.total_amount >= ips.total_paid))
- AND ((iis.total_amount IS NOT NULL) AND (iis.total_amount > 0))
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+ AND ((ips.total_paid IS NULL) OR (iis.amount_invoiced >= ips.total_paid))
+ AND ((iis.amount_invoiced IS NOT NULL) AND (iis.amount_invoiced > 0))
+ GROUP BY <invoiceFields("i.")>;
>>
getById() ::= <<
- SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
- SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+ SELECT <invoiceFields()>
+ FROM invoices
+ WHERE id = :id;
+>>
+
+getAccountBalance() ::= <<
+ SELECT SUM(iis.amount_invoiced) AS amount_invoiced,
+ SUM(ips.total_paid) AS amount_paid
FROM invoices i
- LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
- LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
- WHERE i.id = :id
- GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency;
+ LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
+ LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
+ WHERE i.account_id = :accountId
+ GROUP BY i.account_id;
>>
create() ::= <<
- INSERT INTO invoices(id, account_id, invoice_date, target_date, currency)
+ INSERT INTO invoices(<invoiceFields()>)
VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency);
>>
+getInvoiceIdByPaymentAttemptId() ::= <<
+ SELECT i.id
+ FROM invoices i, invoice_payments ip
+ WHERE ip.invoice_id = i.id
+ AND ip.payment_attempt_id = :paymentAttemptId
+>>
+
update() ::= <<
UPDATE invoices
SET account_id = :accountId, invoice_date = :invoiceDate, target_date = :targetDate, currency = :currency
WHERE id = :id;
>>
-notifySuccessfulPayment() ::= <<
- INSERT INTO invoice_payments(invoice_id, payment_id, payment_date, amount, currency)
- VALUES(:invoiceId, :paymentId, :paymentDate, :amount, :currency);
+getUnpaidInvoicesByAccountId() ::= <<
+ SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency
+ FROM invoices i
+ LEFT JOIN invoice_payment_summary ips ON i.id = ips.invoice_id
+ LEFT JOIN invoice_item_summary iis ON i.id = iis.invoice_id
+ WHERE i.account_id = :accountId AND NOT (i.target_date > :upToDate)
+ GROUP BY i.id, i.account_id, i.invoice_date, i.target_date, i.currency
+ HAVING (SUM(iis.amount_invoiced) > SUM(ips.total_paid)) OR (SUM(ips.total_paid) IS NULL)
+ ORDER BY i.target_date ASC;
>>
-notifyFailedPayment() ::= <<
- INSERT INTO invoice_payments(invoice_id, payment_id, payment_date)
- VALUES(:invoiceId, :paymentId, :paymentAttemptDate);
->>
test() ::= <<
SELECT 1
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg
new file mode 100644
index 0000000..d50a1c5
--- /dev/null
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/RecurringInvoiceItemSqlDao.sql.stg
@@ -0,0 +1,66 @@
+group RecurringInvoiceItemSqlDao;
+
+fields(prefix) ::= <<
+ <prefix>id,
+ <prefix>invoice_id,
+ <prefix>subscription_id,
+ <prefix>plan_name,
+ <prefix>phase_name,
+ <prefix>start_date,
+ <prefix>end_date,
+ <prefix>amount,
+ <prefix>rate,
+ <prefix>currency,
+ <prefix>reversed_item_id
+>>
+
+getById() ::= <<
+ SELECT <fields()>
+ FROM recurring_invoice_items
+ WHERE id = :id;
+>>
+
+getInvoiceItemsByInvoice() ::= <<
+ SELECT <fields()>
+ FROM recurring_invoice_items
+ WHERE invoice_id = :invoiceId;
+>>
+
+getInvoiceItemsByAccount() ::= <<
+ SELECT <fields("rii.")>
+ FROM recurring_invoice_items rii
+ INNER JOIN invoices i ON i.id = rii.invoice_id
+ WHERE i.account_id = :accountId;
+>>
+
+getInvoiceItemsBySubscription() ::= <<
+ SELECT <fields()>
+ FROM recurring_invoice_items
+ WHERE subscription_id = :subscriptionId;
+>>
+
+create() ::= <<
+ INSERT INTO recurring_invoice_items(<fields()>)
+ VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+ :amount, :rate, :currency, :reversedItemId);
+>>
+
+batchCreateFromTransaction() ::= <<
+ INSERT INTO recurring_invoice_items(<fields()>)
+ VALUES(:id, :invoiceId, :subscriptionId, :planName, :phaseName, :startDate, :endDate,
+ :amount, :rate, :currency, :reversedItemId);
+>>
+
+update() ::= <<
+ UPDATE recurring_invoice_items
+ SET invoice_id = :invoiceId, subscription_id = :subscriptionId, plan_name = :planName, phase_name = :phaseName,
+ start_date = :startDate, end_date = :endDate, amount = :amount, rate = :rate, currency = :currency,
+ reversed_item_id = :reversedItemId
+ WHERE id = :id;
+>>
+
+test() ::= <<
+ SELECT 1
+ FROM recurring_invoice_items;
+>>
+;
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
index a3ff37d..c7712a8 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -1,18 +1,43 @@
DROP TABLE IF EXISTS invoice_items;
-CREATE TABLE invoice_items (
+DROP TABLE IF EXISTS recurring_invoice_items;
+CREATE TABLE recurring_invoice_items (
id char(36) NOT NULL,
invoice_id char(36) NOT NULL,
subscription_id char(36) NOT NULL,
+ plan_name varchar(50) NOT NULL,
+ phase_name varchar(50) NOT NULL,
start_date datetime NOT NULL,
end_date datetime NOT NULL,
- description varchar(100) NOT NULL,
- amount numeric(10,4) NOT NULL,
- rate numeric(10,4) NOT NULL,
+ amount numeric(10,4) NULL,
+ rate numeric(10,4) NULL,
currency char(3) NOT NULL,
+ reversed_item_id char(36),
PRIMARY KEY(id)
) ENGINE=innodb;
+CREATE INDEX recurring_invoice_items_subscription_id ON recurring_invoice_items(subscription_id ASC);
+CREATE INDEX recurring_invoice_items_invoice_id ON recurring_invoice_items(invoice_id ASC);
-CREATE INDEX invoice_items_subscription_id ON invoice_items(subscription_id ASC);
+DROP TABLE IF EXISTS fixed_invoice_items;
+CREATE TABLE fixed_invoice_items (
+ id char(36) NOT NULL,
+ invoice_id char(36) NOT NULL,
+ subscription_id char(36) NOT NULL,
+ plan_name varchar(50) NOT NULL,
+ phase_name varchar(50) NOT NULL,
+ start_date datetime NOT NULL,
+ end_date datetime NOT NULL,
+ amount numeric(10,4) NULL,
+ currency char(3) NOT NULL,
+ PRIMARY KEY(id)
+) ENGINE=innodb;
+CREATE INDEX fixed_invoice_items_subscription_id ON fixed_invoice_items(subscription_id ASC);
+CREATE INDEX fixed_invoice_items_invoice_id ON fixed_invoice_items(invoice_id ASC);
+
+DROP TABLE IF EXISTS invoice_locking;
+CREATE TABLE invoice_locking (
+ account_id char(36) NOT NULL,
+ PRIMARY KEY(account_id)
+) ENGINE = innodb;
DROP TABLE IF EXISTS invoices;
CREATE TABLE invoices (
@@ -23,28 +48,32 @@ CREATE TABLE invoices (
currency char(3) NOT NULL,
PRIMARY KEY(id)
) ENGINE=innodb;
-
CREATE INDEX invoices_account_id ON invoices(account_id ASC);
DROP TABLE IF EXISTS invoice_payments;
CREATE TABLE invoice_payments (
invoice_id char(36) NOT NULL,
- payment_id char(36) NOT NULL,
- payment_date datetime NOT NULL,
+ payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
+ payment_attempt_date datetime,
amount numeric(10,4),
currency char(3),
- PRIMARY KEY(invoice_id, payment_id)
+ created_date datetime NOT NULL,
+ updated_date datetime NOT NULL,
+ PRIMARY KEY(invoice_id, payment_attempt_id)
) ENGINE=innodb;
-CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_id);
+CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_attempt_id);
DROP VIEW IF EXISTS invoice_payment_summary;
CREATE VIEW invoice_payment_summary AS
-SELECT invoice_id, SUM(amount) AS total_paid, MAX(payment_date) AS last_payment_date
+SELECT invoice_id,
+ CASE WHEN SUM(amount) IS NULL THEN 0 ELSE SUM(amount) END AS total_paid,
+ MAX(payment_attempt_date) AS last_payment_date
FROM invoice_payments
GROUP BY invoice_id;
DROP VIEW IF EXISTS invoice_item_summary;
CREATE VIEW invoice_item_summary AS
-SELECT invoice_id, SUM(amount) AS total_amount
-FROM invoice_items
-GROUP BY invoice_id;
\ No newline at end of file
+SELECT invoice_id,
+ CASE WHEN SUM(amount) IS NULL THEN 0 ELSE SUM(amount) END AS amount_invoiced
+FROM recurring_invoice_items
+GROUP BY invoice_id;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
new file mode 100644
index 0000000..11e6ead
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+
+public class MockInvoicePaymentApi implements InvoicePaymentApi
+{
+ private final CopyOnWriteArrayList<Invoice> invoices = new CopyOnWriteArrayList<Invoice>();
+ private final CopyOnWriteArrayList<InvoicePayment> invoicePayments = new CopyOnWriteArrayList<InvoicePayment>();
+
+ public void add(Invoice invoice) {
+ invoices.add(invoice);
+ }
+
+ @Override
+ public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+ for (InvoicePayment existingInvoicePayment : invoicePayments) {
+ if (existingInvoicePayment.getInvoiceId().equals(invoicePayment.getInvoiceId()) && existingInvoicePayment.getPaymentAttemptId().equals(invoicePayment.getPaymentAttemptId())) {
+ invoicePayments.remove(existingInvoicePayment);
+ }
+ }
+ invoicePayments.add(invoicePayment);
+ }
+
+ @Override
+ public List<Invoice> getInvoicesByAccount(UUID accountId) {
+ ArrayList<Invoice> result = new ArrayList<Invoice>();
+
+ for (Invoice invoice : invoices) {
+ if (accountId.equals(invoice.getAccountId())) {
+ result.add(invoice);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Invoice getInvoice(UUID invoiceId) {
+ for (Invoice invoice : invoices) {
+ if (invoiceId.equals(invoice.getId())) {
+ return invoice;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId) {
+ for (InvoicePayment invoicePayment : invoicePayments) {
+ if (invoicePayment.getPaymentAttemptId().equals(paymentAttemptId)) {
+ return getInvoice(invoicePayment.getInvoiceId());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+ for (InvoicePayment invoicePayment : invoicePayments) {
+ if (paymentAttemptId.equals(invoicePayment.getPaymentAttemptId())) {
+ return invoicePayment;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+ InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate, amountOutstanding, currency);
+ notifyOfPaymentAttempt(invoicePayment);
+ }
+
+ @Override
+ public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+ InvoicePayment invoicePayment = new DefaultInvoicePayment(paymentAttemptId, invoiceId, paymentAttemptDate);
+ notifyOfPaymentAttempt(invoicePayment);
+ }
+
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
index 268aaf5..6957ba0 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
@@ -16,42 +16,63 @@
package com.ning.billing.invoice.dao;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
import java.io.IOException;
+
+import com.ning.billing.invoice.tests.InvoicingTestBase;
import org.apache.commons.io.IOUtils;
+import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
+
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
-import com.ning.billing.invoice.glue.InvoiceModuleMock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
-
-import static org.testng.Assert.fail;
+import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.DefaultBusService;
-public abstract class InvoiceDaoTestBase {
+public abstract class InvoiceDaoTestBase extends InvoicingTestBase {
protected InvoiceDao invoiceDao;
- protected InvoiceItemSqlDao invoiceItemDao;
+ protected RecurringInvoiceItemSqlDao recurringInvoiceItemDao;
+ protected InvoicePaymentSqlDao invoicePaymentDao;
+ protected InvoiceModuleWithEmbeddedDb module;
- @BeforeClass()
+ @BeforeClass(alwaysRun = true)
protected void setup() throws IOException {
// Health check test to make sure MySQL is setup properly
try {
- InvoiceModuleMock module = new InvoiceModuleMock();
- final String ddl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
- module.createDb(ddl);
+ module = new InvoiceModuleWithEmbeddedDb();
+ final String invoiceDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+ final String entitlementDdl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+
+ module.startDb();
+ module.initDb(invoiceDdl);
+ module.initDb(entitlementDdl);
final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
invoiceDao = injector.getInstance(InvoiceDao.class);
invoiceDao.test();
- invoiceItemDao = module.getInvoiceItemDao();
+ recurringInvoiceItemDao = module.getInvoiceItemSqlDao();
+
+ invoicePaymentDao = module.getInvoicePaymentSqlDao();
+
+ BusService busService = injector.getInstance(BusService.class);
+ ((DefaultBusService) busService).startBus();
- EventBusService busService = injector.getInstance(EventBusService.class);
- ((DefaultEventBusService) busService).startBus();
+ assertTrue(true);
}
catch (Throwable t) {
fail(t.toString());
}
}
+
+ @AfterClass(alwaysRun = true)
+ protected void tearDown() {
+ module.stopDb();
+ assertTrue(true);
+ }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index 3fe72ed..4e07a39 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -16,38 +16,57 @@
package com.ning.billing.invoice.dao;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import org.joda.time.DateTime;
-import org.joda.time.Days;
-import org.testng.annotations.Test;
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
+import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.entitlement.api.billing.BillingEvent;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.model.BillingEventSet;
import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.invoice.model.InvoiceGenerator;
+import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import static org.testng.Assert.*;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
@Test(groups = {"invoicing", "invoicing-invoiceDao"})
public class InvoiceDaoTests extends InvoiceDaoTestBase {
private final int NUMBER_OF_DAY_BETWEEN_RETRIES = 8;
+ private final Clock clock = new DefaultClock();
@Test
public void testCreationAndRetrievalByAccount() {
UUID accountId = UUID.randomUUID();
- Invoice invoice = new DefaultInvoice(accountId, new DefaultClock().getUTCNow(), Currency.USD);
+ Invoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), Currency.USD, clock);
DateTime invoiceDate = invoice.getInvoiceDate();
invoiceDao.create(invoice);
- List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId.toString());
+ List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
assertNotNull(invoices);
assertEquals(invoices.size(), 1);
Invoice thisInvoice = invoices.get(0);
@@ -61,37 +80,38 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
@Test
public void testInvoicePayment() {
UUID accountId = UUID.randomUUID();
- Invoice invoice = new DefaultInvoice(accountId, new DefaultClock().getUTCNow(), Currency.USD);
+ Invoice invoice = new DefaultInvoice(accountId, clock.getUTCNow(), Currency.USD, clock);
UUID invoiceId = invoice.getId();
UUID subscriptionId = UUID.randomUUID();
DateTime startDate = new DateTime(2010, 1, 1, 0, 0, 0, 0);
DateTime endDate = new DateTime(2010, 4, 1, 0, 0, 0, 0);
- InvoiceItem invoiceItem = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
- invoice.add(invoiceItem);
+ InvoiceItem invoiceItem = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
+ invoice.addInvoiceItem(invoiceItem);
invoiceDao.create(invoice);
- Invoice savedInvoice = invoiceDao.getById(invoiceId.toString());
+ Invoice savedInvoice = invoiceDao.getById(invoiceId);
assertNotNull(savedInvoice);
assertEquals(savedInvoice.getTotalAmount().compareTo(new BigDecimal("21.00")), 0);
- assertEquals(savedInvoice.getAmountOutstanding().compareTo(new BigDecimal("21.00")), 0);
+ assertEquals(savedInvoice.getBalance().compareTo(new BigDecimal("21.00")), 0);
assertEquals(savedInvoice.getAmountPaid(), BigDecimal.ZERO);
- assertEquals(savedInvoice.getItems().size(), 1);
+ assertEquals(savedInvoice.getInvoiceItems().size(), 1);
BigDecimal paymentAmount = new BigDecimal("11.00");
- String paymentId = UUID.randomUUID().toString();
- invoiceDao.notifySuccessfulPayment(invoiceId.toString(), paymentAmount, Currency.USD.toString(), paymentId, new DefaultClock().getUTCNow().plusDays(12).toDate());
+ UUID paymentAttemptId = UUID.randomUUID();
+
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoiceId, clock.getUTCNow().plusDays(12), paymentAmount, Currency.USD));
- Invoice retrievedInvoice = invoiceDao.getById(invoiceId.toString());
+ Invoice retrievedInvoice = invoiceDao.getById(invoiceId);
assertNotNull(retrievedInvoice);
- assertEquals(retrievedInvoice.getItems().size(), 1);
+ assertEquals(retrievedInvoice.getInvoiceItems().size(), 1);
assertEquals(retrievedInvoice.getTotalAmount().compareTo(new BigDecimal("21.00")), 0);
- assertEquals(retrievedInvoice.getAmountOutstanding().compareTo(new BigDecimal("10.00")), 0);
+ assertEquals(retrievedInvoice.getBalance().compareTo(new BigDecimal("10.00")), 0);
assertEquals(retrievedInvoice.getAmountPaid().compareTo(new BigDecimal("11.00")), 0);
}
@Test
public void testRetrievalForNonExistentInvoiceId() {
- Invoice invoice = invoiceDao.getById(UUID.randomUUID().toString());
+ Invoice invoice = invoiceDao.getById(UUID.randomUUID());
assertNull(invoice);
}
@@ -99,49 +119,50 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
public void testAddPayment() {
UUID accountId = UUID.randomUUID();
DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
- Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+ Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
- String paymentId = UUID.randomUUID().toString();
+ UUID paymentAttemptId = UUID.randomUUID();
DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
BigDecimal paymentAmount = new BigDecimal("14.0");
invoiceDao.create(invoice);
- invoiceDao.notifySuccessfulPayment(invoice.getId().toString(), paymentAmount, Currency.USD.toString(), paymentId, paymentAttemptDate.toDate());
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttemptId, invoice.getId(), paymentAttemptDate, paymentAmount, Currency.USD));
- invoice = invoiceDao.getById(invoice.getId().toString());
+ invoice = invoiceDao.getById(invoice.getId());
assertEquals(invoice.getAmountPaid().compareTo(paymentAmount), 0);
- assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+ assertEquals(invoice.getLastPaymentAttempt().compareTo(paymentAttemptDate), 0);
+ assertEquals(invoice.getNumberOfPayments(), 1);
}
@Test
public void testAddPaymentAttempt() {
UUID accountId = UUID.randomUUID();
DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
- Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+ Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
invoiceDao.create(invoice);
- invoiceDao.notifyFailedPayment(invoice.getId().toString(), UUID.randomUUID().toString(), paymentAttemptDate.toDate());
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(invoice.getId(), paymentAttemptDate));
- invoice = invoiceDao.getById(invoice.getId().toString());
- assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
+ invoice = invoiceDao.getById(invoice.getId());
+ assertEquals(invoice.getLastPaymentAttempt().compareTo(paymentAttemptDate), 0);
}
@Test
public void testGetInvoicesForPaymentWithNoResults() {
DateTime notionalDate = new DateTime();
DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-
+
// determine the number of existing invoices available for payment (to avoid side effects from other tests)
- List<UUID> invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ List<UUID> invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
int existingInvoiceCount = invoices.size();
-
+
UUID accountId = UUID.randomUUID();
- Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+ Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
invoiceDao.create(invoice);
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
assertEquals(invoices.size(), existingInvoiceCount);
}
@@ -153,7 +174,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
// create a new invoice with one item
UUID accountId = UUID.randomUUID();
DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
- Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+ Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
UUID invoiceId = invoice.getId();
UUID subscriptionId = UUID.randomUUID();
@@ -161,71 +182,72 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
BigDecimal rate = new BigDecimal("9.0");
BigDecimal amount = rate.multiply(new BigDecimal("3.0"));
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, targetDate, endDate, "test", amount, rate, Currency.USD);
- invoice.add(item);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", targetDate, endDate, amount, rate, Currency.USD);
+ invoice.addInvoiceItem(item);
invoiceDao.create(invoice);
// ensure that the number of invoices for payment has increased by 1
int count;
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
List<Invoice> invoicesDue = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate);
count = invoicesDue.size();
assertEquals(invoices.size(), count);
// attempt a payment; ensure that the number of invoices for payment has decreased by 1
// (no retries for NUMBER_OF_DAYS_BETWEEN_RETRIES days)
- invoiceDao.notifyFailedPayment(invoice.getId().toString(), UUID.randomUUID().toString(), notionalDate.toDate());
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(invoice.getId(), notionalDate));
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// advance clock by NUMBER_OF_DAYS_BETWEEN_RETRIES days
// ensure that number of invoices for payment has increased by 1 (retry)
notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// post successful partial payment; ensure that number of invoices for payment has decreased by 1
- invoiceDao.notifySuccessfulPayment(invoiceId.toString(), new BigDecimal("22.0000"), Currency.USD.toString(), UUID.randomUUID().toString(), notionalDate.toDate());
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(UUID.randomUUID(), invoice.getId(), notionalDate, new BigDecimal("22.0000"), Currency.USD));
+
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// get invoice; verify amount paid is correct
- invoice = invoiceDao.getById(invoiceId.toString());
+ invoice = invoiceDao.getById(invoiceId);
assertEquals(invoice.getAmountPaid().compareTo(new BigDecimal("22.0")), 0);
// advance clock NUMBER_OF_DAYS_BETWEEN_RETRIES days
// ensure that number of invoices for payment has increased by 1 (retry)
notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// post completed payment; ensure that the number of invoices for payment has decreased by 1
- invoiceDao.notifySuccessfulPayment(invoiceId.toString(), new BigDecimal("5.0000"), Currency.USD.toString(), UUID.randomUUID().toString(), notionalDate.toDate());
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoiceDao.notifyOfPaymentAttempt(new DefaultInvoicePayment(UUID.randomUUID(), invoice.getId(), notionalDate, new BigDecimal("5.0000"), Currency.USD));
+
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
// get invoice; verify amount paid is correct
- invoice = invoiceDao.getById(invoiceId.toString());
- count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
+ invoice = invoiceDao.getById(invoiceId);
assertEquals(invoice.getAmountPaid().compareTo(new BigDecimal("27.0")), 0);
// advance clock by NUMBER_OF_DAYS_BETWEEN_RETRIES days
// ensure that the number of invoices for payment hasn't changed
notionalDate = notionalDate.plusDays(NUMBER_OF_DAY_BETWEEN_RETRIES);
- invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
+ invoices = invoiceDao.getInvoicesForPayment(notionalDate, NUMBER_OF_DAY_BETWEEN_RETRIES);
count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
assertEquals(invoices.size(), count);
}
- private List<Invoice> getInvoicesDueForPaymentAttempt(List<Invoice> invoices, DateTime date) {
+ private List<Invoice> getInvoicesDueForPaymentAttempt(final List<Invoice> invoices, final DateTime date) {
List<Invoice> invoicesDue= new ArrayList<Invoice>();
- for (Invoice invoice : invoices) {
+ for (final Invoice invoice : invoices) {
if (invoice.isDueForPayment(date, NUMBER_OF_DAY_BETWEEN_RETRIES)) {
invoicesDue.add(invoice);
}
@@ -247,7 +269,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
// create invoice 1 (subscriptions 1-4)
- Invoice invoice1 = new DefaultInvoice(accountId, targetDate, Currency.USD);
+ Invoice invoice1 = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
invoiceDao.create(invoice1);
UUID invoiceId1 = invoice1.getId();
@@ -255,20 +277,20 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
DateTime endDate = startDate.plusMonths(1);
- DefaultInvoiceItem item1 = new DefaultInvoiceItem(invoiceId1, subscriptionId1, startDate, endDate, "test A", rate1, rate1, Currency.USD);
- invoiceItemDao.create(item1);
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoiceId1, subscriptionId1, "test plan", "test A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item1);
- DefaultInvoiceItem item2 = new DefaultInvoiceItem(invoiceId1, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
- invoiceItemDao.create(item2);
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoiceId1, subscriptionId2, "test plan", "test B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item2);
- DefaultInvoiceItem item3 = new DefaultInvoiceItem(invoiceId1, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
- invoiceItemDao.create(item3);
+ RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoiceId1, subscriptionId3, "test plan", "test C", startDate, endDate, rate3, rate3, Currency.USD);
+ recurringInvoiceItemDao.create(item3);
- DefaultInvoiceItem item4 = new DefaultInvoiceItem(invoiceId1, subscriptionId4, startDate, endDate, "test D", rate4, rate4, Currency.USD);
- invoiceItemDao.create(item4);
+ RecurringInvoiceItem item4 = new RecurringInvoiceItem(invoiceId1, subscriptionId4, "test plan", "test D", startDate, endDate, rate4, rate4, Currency.USD);
+ recurringInvoiceItemDao.create(item4);
// create invoice 2 (subscriptions 1-3)
- DefaultInvoice invoice2 = new DefaultInvoice(accountId, targetDate, Currency.USD);
+ DefaultInvoice invoice2 = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
invoiceDao.create(invoice2);
UUID invoiceId2 = invoice2.getId();
@@ -276,27 +298,431 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
startDate = endDate;
endDate = startDate.plusMonths(1);
- DefaultInvoiceItem item5 = new DefaultInvoiceItem(invoiceId2, subscriptionId1, startDate, endDate, "test A", rate1, rate1, Currency.USD);
- invoiceItemDao.create(item5);
+ RecurringInvoiceItem item5 = new RecurringInvoiceItem(invoiceId2, subscriptionId1, "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item5);
- DefaultInvoiceItem item6 = new DefaultInvoiceItem(invoiceId2, subscriptionId2, startDate, endDate, "test B", rate2, rate2, Currency.USD);
- invoiceItemDao.create(item6);
+ RecurringInvoiceItem item6 = new RecurringInvoiceItem(invoiceId2, subscriptionId2, "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item6);
- DefaultInvoiceItem item7 = new DefaultInvoiceItem(invoiceId2, subscriptionId3, startDate, endDate, "test C", rate3, rate3, Currency.USD);
- invoiceItemDao.create(item7);
+ RecurringInvoiceItem item7 = new RecurringInvoiceItem(invoiceId2, subscriptionId3, "test plan", "test phase C", startDate, endDate, rate3, rate3, Currency.USD);
+ recurringInvoiceItemDao.create(item7);
// check that each subscription returns the correct number of invoices
- List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1.toString());
+ List<Invoice> items1 = invoiceDao.getInvoicesBySubscription(subscriptionId1);
assertEquals(items1.size(), 2);
- List<Invoice> items2 = invoiceDao.getInvoicesBySubscription(subscriptionId2.toString());
+ List<Invoice> items2 = invoiceDao.getInvoicesBySubscription(subscriptionId2);
assertEquals(items2.size(), 2);
- List<Invoice> items3 = invoiceDao.getInvoicesBySubscription(subscriptionId3.toString());
+ List<Invoice> items3 = invoiceDao.getInvoicesBySubscription(subscriptionId3);
assertEquals(items3.size(), 2);
- List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4.toString());
+ List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4);
assertEquals(items4.size(), 1);
}
-
+
+ @Test
+ public void testGetInvoicesForAccountAfterDate() {
+ UUID accountId = UUID.randomUUID();
+ DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+ Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+ invoiceDao.create(invoice1);
+
+ DateTime targetDate2 = new DateTime(2011, 12, 6, 0, 0, 0, 0);
+ Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD, clock);
+ invoiceDao.create(invoice2);
+
+
+ List<Invoice> invoices;
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 1, 1, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 2);
+
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 6, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 2);
+
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 10, 11, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 1);
+
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2011, 12, 6, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 1);
+
+ invoices = invoiceDao.getInvoicesByAccount(accountId, new DateTime(2012, 1, 1, 0, 0, 0, 0));
+ assertEquals(invoices.size(), 0);
+ }
+
+ @Test
+ public void testAccountBalance() {
+ UUID accountId = UUID.randomUUID();
+ DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+ Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+ invoiceDao.create(invoice1);
+
+ DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
+ DateTime endDate = startDate.plusMonths(1);
+
+ BigDecimal rate1 = new BigDecimal("17.0");
+ BigDecimal rate2 = new BigDecimal("42.0");
+
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item1);
+
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item2);
+
+ BigDecimal payment1 = new BigDecimal("48.0");
+ InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
+ invoicePaymentDao.create(payment);
+
+ BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+ assertEquals(balance.compareTo(rate1.add(rate2).subtract(payment1)), 0);
+ }
+
+ @Test
+ public void testAccountBalanceWithNoPayments() {
+ UUID accountId = UUID.randomUUID();
+ DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+ Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+ invoiceDao.create(invoice1);
+
+ DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
+ DateTime endDate = startDate.plusMonths(1);
+
+ BigDecimal rate1 = new BigDecimal("17.0");
+ BigDecimal rate2 = new BigDecimal("42.0");
+
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item1);
+
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item2);
+
+ BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+ assertEquals(balance.compareTo(rate1.add(rate2)), 0);
+ }
+
+ @Test
+ public void testAccountBalanceWithNoInvoiceItems() {
+ UUID accountId = UUID.randomUUID();
+ DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+ Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+ invoiceDao.create(invoice1);
+
+ BigDecimal payment1 = new BigDecimal("48.0");
+ InvoicePayment payment = new DefaultInvoicePayment(invoice1.getId(), new DateTime(), payment1, Currency.USD);
+ invoicePaymentDao.create(payment);
+
+ BigDecimal balance = invoiceDao.getAccountBalance(accountId);
+ assertEquals(balance.compareTo(BigDecimal.ZERO.subtract(payment1)), 0);
+ }
+
+ @Test
+ public void testGetUnpaidInvoicesByAccountId() {
+ UUID accountId = UUID.randomUUID();
+ DateTime targetDate1 = new DateTime(2011, 10, 6, 0, 0, 0, 0);
+ Invoice invoice1 = new DefaultInvoice(accountId, targetDate1, Currency.USD, clock);
+ invoiceDao.create(invoice1);
+
+ DateTime startDate = new DateTime(2011, 3, 1, 0, 0, 0, 0);
+ DateTime endDate = startDate.plusMonths(1);
+
+ BigDecimal rate1 = new BigDecimal("17.0");
+ BigDecimal rate2 = new BigDecimal("42.0");
+
+ RecurringInvoiceItem item1 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase A", startDate, endDate, rate1, rate1, Currency.USD);
+ recurringInvoiceItemDao.create(item1);
+
+ RecurringInvoiceItem item2 = new RecurringInvoiceItem(invoice1.getId(), UUID.randomUUID(), "test plan", "test phase B", startDate, endDate, rate2, rate2, Currency.USD);
+ recurringInvoiceItemDao.create(item2);
+
+ DateTime upToDate;
+ Collection<Invoice> invoices;
+
+ upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
+ invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+ assertEquals(invoices.size(), 0);
+
+ upToDate = new DateTime(2012, 1, 1, 0, 0, 0, 0);
+ invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+ assertEquals(invoices.size(), 1);
+
+ DateTime targetDate2 = new DateTime(2011, 7, 1, 0, 0, 0, 0);
+ Invoice invoice2 = new DefaultInvoice(accountId, targetDate2, Currency.USD, clock);
+ invoiceDao.create(invoice2);
+
+ DateTime startDate2 = new DateTime(2011, 6, 1, 0, 0, 0, 0);
+ DateTime endDate2 = startDate2.plusMonths(3);
+
+ BigDecimal rate3 = new BigDecimal("21.0");
+
+ RecurringInvoiceItem item3 = new RecurringInvoiceItem(invoice2.getId(), UUID.randomUUID(), "test plan", "test phase C", startDate2, endDate2, rate3, rate3, Currency.USD);
+ recurringInvoiceItemDao.create(item3);
+
+ upToDate = new DateTime(2011, 1, 1, 0, 0, 0, 0);
+ invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+ assertEquals(invoices.size(), 0);
+
+ upToDate = new DateTime(2012, 1, 1, 0, 0, 0, 0);
+ invoices = invoiceDao.getUnpaidInvoicesByAccountId(accountId, upToDate);
+ assertEquals(invoices.size(), 2);
+ }
+
+ /*
+ *
+ * this test verifies that immediate changes give the correct results
+ *
+ */
+ @Test
+ public void testInvoiceGenerationForImmediateChanges() throws InvoiceApiException {
+
+ InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+
+ UUID accountId = UUID.randomUUID();
+ InvoiceItemList invoiceItemList = new InvoiceItemList();
+ DateTime targetDate = new DateTime(2011, 2, 16, 0, 0, 0, 0);
+
+ // generate first invoice
+ DefaultPrice price1 = new DefaultPrice(TEN, Currency.USD);
+ MockInternationalPrice recurringPrice = new MockInternationalPrice(price1);
+ MockPlanPhase phase1 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+ MockPlan plan1 = new MockPlan(phase1);
+
+ Subscription subscription = new MockSubscription();
+ DateTime effectiveDate1 = new DateTime(2011, 2, 1, 0, 0, 0, 0);
+ BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan1, phase1, null,
+ recurringPrice, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+ "testEvent1", SubscriptionTransitionType.CREATE);
+
+ BillingEventSet events = new BillingEventSet();
+ events.add(event1);
+
+ Invoice invoice1 = generator.generateInvoice(accountId, events, invoiceItemList, targetDate, Currency.USD);
+ assertEquals(invoice1.getBalance(), TEN);
+ invoiceItemList.addAll(invoice1.getInvoiceItems());
+
+ // generate second invoice
+ DefaultPrice price2 = new DefaultPrice(TWENTY, Currency.USD);
+ MockInternationalPrice recurringPrice2 = new MockInternationalPrice(price2);
+ MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null, BillingPeriod.MONTHLY, PhaseType.TRIAL);
+ MockPlan plan2 = new MockPlan(phase2);
+
+ DateTime effectiveDate2 = new DateTime(2011, 2, 15, 0, 0, 0, 0);
+ BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan2, phase2, null,
+ recurringPrice2, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+ "testEvent2", SubscriptionTransitionType.CREATE);
+ events.add(event2);
+
+ // 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
+ Invoice invoice2 = generator.generateInvoice(accountId, events, invoiceItemList, targetDate, Currency.USD);
+ assertEquals(invoice2.getBalance(), FIVE);
+ invoiceItemList.addAll(invoice2.getInvoiceItems());
+
+ invoiceDao.create(invoice1);
+ invoiceDao.create(invoice2);
+
+ Invoice savedInvoice1 = invoiceDao.getById(invoice1.getId());
+ assertEquals(savedInvoice1.getTotalAmount(), ZERO);
+
+ Invoice savedInvoice2 = invoiceDao.getById(invoice2.getId());
+ assertEquals(savedInvoice2.getTotalAmount(), FIFTEEN);
+ }
+
+ @Test
+ public void testInvoiceForFreeTrial() throws InvoiceApiException {
+ DefaultPrice price = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+ MockInternationalPrice recurringPrice = new MockInternationalPrice(price);
+ MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
+ MockPlan plan = new MockPlan(phase);
+
+ Subscription subscription = new MockSubscription();
+ DateTime effectiveDate = buildDateTime(2011, 1, 1);
+
+ BillingEvent event = new DefaultBillingEvent(subscription, effectiveDate, plan, phase, null,
+ recurringPrice, BillingPeriod.MONTHLY, 15, BillingModeType.IN_ADVANCE,
+ "testEvent", SubscriptionTransitionType.CREATE);
+ BillingEventSet events = new BillingEventSet();
+ events.add(event);
+
+ DateTime targetDate = buildDateTime(2011, 1, 15);
+ InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+ Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+
+ // expect one pro-ration item and one full-period item
+ assertEquals(invoice.getNumberOfItems(), 2);
+ assertEquals(invoice.getTotalAmount().compareTo(ZERO), 0);
+ }
+
+ @Test
+ public void testInvoiceForFreeTrialWithRecurringDiscount() throws InvoiceApiException {
+ DefaultPrice zeroPrice = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+ MockInternationalPrice fixedPrice = new MockInternationalPrice(zeroPrice);
+ MockPlanPhase phase1 = new MockPlanPhase(null, fixedPrice);
+
+ BigDecimal cheapAmount = new BigDecimal("24.95");
+ DefaultPrice cheapPrice = new DefaultPrice(cheapAmount, Currency.USD);
+ MockInternationalPrice recurringPrice = new MockInternationalPrice(cheapPrice);
+ MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null);
+
+ MockPlan plan = new MockPlan();
+ Subscription subscription = new MockSubscription();
+ DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
+
+ BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice,
+ null, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+ "testEvent1", SubscriptionTransitionType.CREATE);
+ BillingEventSet events = new BillingEventSet();
+ events.add(event1);
+
+ InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+ Invoice invoice1 = generator.generateInvoice(UUID.randomUUID(), events, null, effectiveDate1, Currency.USD);
+ assertNotNull(invoice1);
+ assertEquals(invoice1.getNumberOfItems(), 1);
+ assertEquals(invoice1.getTotalAmount().compareTo(ZERO), 0);
+
+ List<InvoiceItem> existingItems = invoice1.getInvoiceItems();
+
+ DateTime effectiveDate2 = effectiveDate1.plusDays(30);
+ BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+ recurringPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+ "testEvent2", SubscriptionTransitionType.CHANGE);
+ events.add(event2);
+
+ Invoice invoice2 = generator.generateInvoice(UUID.randomUUID(), events, existingItems, effectiveDate2, Currency.USD);
+ assertNotNull(invoice2);
+ assertEquals(invoice2.getNumberOfItems(), 1);
+ assertEquals(invoice2.getTotalAmount().compareTo(cheapAmount), 0);
+
+ existingItems.addAll(invoice2.getInvoiceItems());
+
+ DateTime effectiveDate3 = effectiveDate2.plusMonths(1);
+ Invoice invoice3 = generator.generateInvoice(UUID.randomUUID(), events, existingItems, effectiveDate3, Currency.USD);
+ assertNotNull(invoice3);
+ assertEquals(invoice3.getNumberOfItems(), 1);
+ assertEquals(invoice3.getTotalAmount().compareTo(cheapAmount), 0);
+ }
+
+ @Test
+ public void testInvoiceForEmptyEventSet() throws InvoiceApiException {
+ InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+ BillingEventSet events = new BillingEventSet();
+ Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new DateTime(), Currency.USD);
+ assertNull(invoice);
+ }
+
+ @Test
+ public void testMixedModeInvoicePersistence() throws InvoiceApiException {
+ DefaultPrice zeroPrice = new DefaultPrice(BigDecimal.ZERO, Currency.USD);
+ MockInternationalPrice fixedPrice = new MockInternationalPrice(zeroPrice);
+ MockPlanPhase phase1 = new MockPlanPhase(null, fixedPrice);
+
+ BigDecimal cheapAmount = new BigDecimal("24.95");
+ DefaultPrice cheapPrice = new DefaultPrice(cheapAmount, Currency.USD);
+ MockInternationalPrice recurringPrice = new MockInternationalPrice(cheapPrice);
+ MockPlanPhase phase2 = new MockPlanPhase(recurringPrice, null);
+
+ MockPlan plan = new MockPlan();
+ Subscription subscription = new MockSubscription();
+ DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
+
+ BillingEvent event1 = new DefaultBillingEvent(subscription, effectiveDate1, plan, phase1, fixedPrice,
+ null, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+ "testEvent1", SubscriptionTransitionType.CREATE);
+ BillingEventSet events = new BillingEventSet();
+ events.add(event1);
+
+ DateTime effectiveDate2 = effectiveDate1.plusDays(30);
+ BillingEvent event2 = new DefaultBillingEvent(subscription, effectiveDate2, plan, phase2, null,
+ recurringPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+ "testEvent2", SubscriptionTransitionType.CHANGE);
+ events.add(event2);
+
+ InvoiceGenerator generator = new DefaultInvoiceGenerator(clock);
+ Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, effectiveDate2, Currency.USD);
+ assertNotNull(invoice);
+ assertEquals(invoice.getNumberOfItems(), 2);
+ assertEquals(invoice.getTotalAmount().compareTo(cheapAmount), 0);
+
+ invoiceDao.create(invoice);
+ Invoice savedInvoice = invoiceDao.getById(invoice.getId());
+
+ assertNotNull(savedInvoice);
+ assertEquals(savedInvoice.getNumberOfItems(), 2);
+ assertEquals(savedInvoice.getTotalAmount().compareTo(cheapAmount), 0);
+ }
+
+// @Test
+// public void testCancellationWithMultipleBillingPeriodsFollowing() throws InvoiceApiException {
+// UUID accountId = UUID.randomUUID();
+//
+// BigDecimal fixedValue = FIVE;
+// DefaultPrice fixedAmount = new DefaultPrice(fixedValue, Currency.USD);
+// MockInternationalPrice fixedPrice = new MockInternationalPrice(fixedAmount);
+// MockPlanPhase plan1phase1 = new MockPlanPhase(null, fixedPrice);
+//
+// BigDecimal trialValue = new BigDecimal("9.95");
+// DefaultPrice trialAmount = new DefaultPrice(trialValue, Currency.USD);
+// MockInternationalPrice trialPrice = new MockInternationalPrice(trialAmount);
+// MockPlanPhase plan2phase1 = new MockPlanPhase(trialPrice, null);
+//
+// BigDecimal discountValue = new BigDecimal("24.95");
+// DefaultPrice discountAmount = new DefaultPrice(discountValue, Currency.USD);
+// MockInternationalPrice discountPrice = new MockInternationalPrice(discountAmount);
+// MockPlanPhase plan2phase2 = new MockPlanPhase(discountPrice, null);
+//
+// MockPlan plan1 = new MockPlan();
+// MockPlan plan2 = new MockPlan();
+// Subscription subscription = new MockSubscription();
+// DateTime effectiveDate1 = buildDateTime(2011, 1, 1);
+//
+// BillingEvent creationEvent = new DefaultBillingEvent(subscription, effectiveDate1, plan1, plan1phase1, fixedPrice,
+// null, BillingPeriod.MONTHLY, 1, BillingModeType.IN_ADVANCE,
+// "trial", SubscriptionTransitionType.CREATE);
+// BillingEventSet events = new BillingEventSet();
+// events.add(creationEvent);
+//
+// InvoiceGenerator generator = new DefaultInvoiceGenerator();
+// InvoiceItemList existingItems;
+//
+// existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+// Invoice invoice1 = generator.generateInvoice(accountId, events, existingItems, effectiveDate1, Currency.USD);
+//
+// assertNotNull(invoice1);
+// assertEquals(invoice1.getNumberOfItems(), 1);
+// assertEquals(invoice1.getTotalAmount().compareTo(fixedValue), 0);
+// invoiceDao.create(invoice1);
+//
+// DateTime effectiveDate2 = effectiveDate1.plusSeconds(1);
+// BillingEvent changeEvent = new DefaultBillingEvent(subscription, effectiveDate2, plan2, plan2phase1, null,
+// trialPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+// "discount", SubscriptionTransitionType.CHANGE);
+// events.add(changeEvent);
+//
+// existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+// Invoice invoice2 = generator.generateInvoice(accountId, events, existingItems, effectiveDate2, Currency.USD);
+// assertNotNull(invoice2);
+// assertEquals(invoice2.getNumberOfItems(), 2);
+// assertEquals(invoice2.getTotalAmount().compareTo(trialValue), 0);
+// invoiceDao.create(invoice2);
+//
+// DateTime effectiveDate3 = effectiveDate2.plusMonths(1);
+// BillingEvent phaseEvent = new DefaultBillingEvent(subscription, effectiveDate3, plan2, plan2phase2, null,
+// discountPrice, BillingPeriod.MONTHLY, 31, BillingModeType.IN_ADVANCE,
+// "discount", SubscriptionTransitionType.PHASE);
+// events.add(phaseEvent);
+//
+// existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+// Invoice invoice3 = generator.generateInvoice(accountId, events, existingItems, effectiveDate3, Currency.USD);
+// assertNotNull(invoice3);
+// assertEquals(invoice3.getNumberOfItems(), 1);
+// assertEquals(invoice3.getTotalAmount().compareTo(discountValue), 0);
+// invoiceDao.create(invoice3);
+//
+// DateTime effectiveDate4 = effectiveDate3.plusMonths(1);
+// existingItems = new InvoiceItemList(invoiceDao.getInvoiceItemsByAccount(accountId));
+// Invoice invoice4 = generator.generateInvoice(accountId, events, existingItems, effectiveDate4, Currency.USD);
+// assertNotNull(invoice4);
+// assertEquals(invoice4.getNumberOfItems(), 1);
+// assertEquals(invoice4.getTotalAmount().compareTo(discountValue), 0);
+// invoiceDao.create(invoice4);
+// }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
index fa743ff..16e95fb 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
@@ -16,27 +16,28 @@
package com.ning.billing.invoice.dao;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.glue.InvoiceModuleMock;
import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
-import org.apache.commons.io.IOUtils;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+
import org.joda.time.DateTime;
-import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
-import static org.testng.Assert.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
+
+ private final Clock clock = new DefaultClock();
+
@Test
public void testInvoiceItemCreation() {
UUID invoiceId = UUID.randomUUID();
@@ -45,18 +46,17 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
DateTime endDate = new DateTime(2011, 11, 1, 0, 0, 0, 0);
BigDecimal rate = new BigDecimal("20.00");
- InvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, endDate, "test", rate, rate, Currency.USD);
- invoiceItemDao.create(item);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, endDate, rate, rate, Currency.USD);
+ recurringInvoiceItemDao.create(item);
- InvoiceItem thisItem = invoiceItemDao.getById(item.getId().toString());
+ RecurringInvoiceItem thisItem = (RecurringInvoiceItem) recurringInvoiceItemDao.getById(item.getId().toString());
assertNotNull(thisItem);
assertEquals(thisItem.getId(), item.getId());
assertEquals(thisItem.getInvoiceId(), item.getInvoiceId());
assertEquals(thisItem.getSubscriptionId(), item.getSubscriptionId());
assertEquals(thisItem.getStartDate(), item.getStartDate());
assertEquals(thisItem.getEndDate(), item.getEndDate());
- assertEquals(thisItem.getDescription(), item.getDescription());
- assertEquals(thisItem.getAmount().compareTo(item.getAmount()), 0);
+ assertEquals(thisItem.getAmount().compareTo(item.getRate()), 0);
assertEquals(thisItem.getRate().compareTo(item.getRate()), 0);
assertEquals(thisItem.getCurrency(), item.getCurrency());
}
@@ -69,11 +69,11 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
for (int i = 0; i < 3; i++) {
UUID invoiceId = UUID.randomUUID();
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate.plusMonths(i), startDate.plusMonths(i + 1), "test", rate, rate, Currency.USD);
- invoiceItemDao.create(item);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate.plusMonths(i), startDate.plusMonths(i + 1), rate, rate, Currency.USD);
+ recurringInvoiceItemDao.create(item);
}
- List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsBySubscription(subscriptionId.toString());
+ List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsBySubscription(subscriptionId.toString());
assertEquals(items.size(), 3);
}
@@ -86,11 +86,11 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
for (int i = 0; i < 5; i++) {
UUID subscriptionId = UUID.randomUUID();
BigDecimal amount = rate.multiply(new BigDecimal(i + 1));
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", amount, amount, Currency.USD);
- invoiceItemDao.create(item);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), amount, amount, Currency.USD);
+ recurringInvoiceItemDao.create(item);
}
- List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
+ List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsByInvoice(invoiceId.toString());
assertEquals(items.size(), 5);
}
@@ -98,7 +98,7 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
public void testGetInvoiceItemsByAccountId() {
UUID accountId = UUID.randomUUID();
DateTime targetDate = new DateTime(2011, 5, 23, 0, 0, 0, 0);
- DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
+ DefaultInvoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD, clock);
invoiceDao.create(invoice);
@@ -107,10 +107,10 @@ public class InvoiceItemDaoTests extends InvoiceDaoTestBase {
BigDecimal rate = new BigDecimal("20.00");
UUID subscriptionId = UUID.randomUUID();
- DefaultInvoiceItem item = new DefaultInvoiceItem(invoiceId, subscriptionId, startDate, startDate.plusMonths(1), "test", rate, rate, Currency.USD);
- invoiceItemDao.create(item);
+ RecurringInvoiceItem item = new RecurringInvoiceItem(invoiceId, subscriptionId, "test plan", "test phase", startDate, startDate.plusMonths(1), rate, rate, Currency.USD);
+ recurringInvoiceItemDao.create(item);
- List<InvoiceItem> items = invoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
+ List<InvoiceItem> items = recurringInvoiceItemDao.getInvoiceItemsByAccount(accountId.toString());
assertEquals(items.size(), 1);
}
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
new file mode 100644
index 0000000..cf6c112
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.dao;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.bus.Bus;
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
+
+public class MockInvoiceDao implements InvoiceDao {
+ private final Bus eventBus;
+ private final Object monitor = new Object();
+ private final Map<UUID, Invoice> invoices = new LinkedHashMap<UUID, Invoice>();
+
+ @Inject
+ public MockInvoiceDao(Bus eventBus) {
+ this.eventBus = eventBus;
+ }
+
+ @Override
+ public void create(Invoice invoice) {
+ synchronized (monitor) {
+ invoices.put(invoice.getId(), invoice);
+ }
+ try {
+ eventBus.post(new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
+ invoice.getBalance(), invoice.getCurrency(),
+ invoice.getInvoiceDate()));
+ }
+ catch (Bus.EventBusException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public Invoice getById(UUID id) {
+ synchronized (monitor) {
+ return invoices.get(id);
+ }
+ }
+
+ @Override
+ public List<Invoice> get() {
+ synchronized (monitor) {
+ return new ArrayList<Invoice>(invoices.values());
+ }
+ }
+
+ @Override
+ public List<Invoice> getInvoicesByAccount(UUID accountId) {
+ List<Invoice> result = new ArrayList<Invoice>();
+
+ synchronized (monitor) {
+ for (Invoice invoice : invoices.values()) {
+ if (accountId.equals(invoice.getAccountId())) {
+ result.add(invoice);
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public List<Invoice> getInvoicesByAccount(UUID accountId, DateTime fromDate) {
+ List<Invoice> invoicesForAccount = new ArrayList<Invoice>();
+
+ synchronized (monitor) {
+ for (Invoice invoice : get()) {
+ if (accountId.equals(invoice.getAccountId()) && !invoice.getTargetDate().isBefore(fromDate)) {
+ invoicesForAccount.add(invoice);
+ }
+ }
+ }
+
+ return invoicesForAccount;
+ }
+
+ @Override
+ public List<InvoiceItem> getInvoiceItemsByAccount(UUID accountId) {
+ List<InvoiceItem> invoiceItemsForAccount = new ArrayList<InvoiceItem>();
+
+ synchronized (monitor) {
+ for (Invoice invoice : get()) {
+ if (accountId.equals(invoice.getAccountId())) {
+ invoiceItemsForAccount.addAll(invoice.getInvoiceItems());
+ }
+ }
+ }
+
+ return invoiceItemsForAccount;
+ }
+
+ @Override
+ public List<Invoice> getInvoicesBySubscription(UUID subscriptionId) {
+ List<Invoice> result = new ArrayList<Invoice>();
+
+ synchronized (monitor) {
+ for (Invoice invoice : invoices.values()) {
+ for (InvoiceItem item : invoice.getInvoiceItems()) {
+ if (subscriptionId.equals(item.getSubscriptionId())) {
+ result.add(invoice);
+ break;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays) {
+ List<UUID> result = new ArrayList<UUID>();
+
+ synchronized (monitor) {
+ for (Invoice invoice : invoices.values()) {
+ if (invoice.isDueForPayment(targetDate, numberOfDays)) {
+ result.add(invoice.getId());
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public void test() {
+ }
+
+ @Override
+ public UUID getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+ synchronized(monitor) {
+ for (Invoice invoice : invoices.values()) {
+ for (InvoicePayment payment : invoice.getPayments()) {
+ if (paymentAttemptId.equals(payment.getPaymentAttemptId())) {
+ return invoice.getId();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+ synchronized(monitor) {
+ for (Invoice invoice : invoices.values()) {
+ for (InvoicePayment payment : invoice.getPayments()) {
+ if (paymentAttemptId.equals(payment.getPaymentAttemptId())) {
+ return payment;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+ synchronized (monitor) {
+ Invoice invoice = invoices.get(invoicePayment.getInvoiceId());
+ if (invoice != null) {
+ invoice.addPayment(invoicePayment);
+ }
+ }
+ }
+
+ @Override
+ public BigDecimal getAccountBalance(UUID accountId) {
+ BigDecimal balance = BigDecimal.ZERO;
+
+ for (Invoice invoice : get()) {
+ if (accountId.equals(invoice.getAccountId())) {
+ balance = balance.add(invoice.getBalance());
+ }
+ }
+
+ return balance;
+ }
+
+ @Override
+ public List<Invoice> getUnpaidInvoicesByAccountId(UUID accountId, DateTime upToDate) {
+ List<Invoice> unpaidInvoices = new ArrayList<Invoice>();
+
+ for (Invoice invoice : get()) {
+ if (accountId.equals(invoice.getAccountId()) && (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0)) {
+ unpaidInvoices.add(invoice);
+ }
+ }
+
+ return unpaidInvoices;
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
new file mode 100644
index 0000000..7a9253c
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockSubscription.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.dao;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+import org.joda.time.DateTime;
+
+import java.util.List;
+import java.util.UUID;
+
+public class MockSubscription implements Subscription {
+ private final UUID subscriptionId = UUID.randomUUID();
+
+ @Override
+ public void cancel(DateTime requestedDate, boolean eot) throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void uncancel() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void changePlan(String productName, BillingPeriod term, String planSet, DateTime requestedDate) throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void pause() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void resume() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public UUID getId() {
+ return subscriptionId;
+ }
+
+ @Override
+ public UUID getBundleId() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionState getState() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DateTime getStartDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DateTime getEndDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Plan getCurrentPlan() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getCurrentPriceList() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PlanPhase getCurrentPhase() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DateTime getChargedThroughDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DateTime getPaidThroughDate() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<SubscriptionTransition> getActiveTransitions() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<SubscriptionTransition> getAllTransitions() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionTransition getPendingTransition() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionTransition getPreviousTransition() {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
new file mode 100644
index 0000000..4ab3495
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithEmbeddedDb.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.glue;
+
+import java.io.IOException;
+
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import com.ning.billing.invoice.api.test.DefaultInvoiceTestApi;
+import com.ning.billing.invoice.dao.InvoicePaymentSqlDao;
+import com.ning.billing.invoice.dao.RecurringInvoiceItemSqlDao;
+import com.ning.billing.util.glue.GlobalLockerModule;
+import org.skife.jdbi.v2.IDBI;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.catalog.glue.CatalogModule;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.glue.EntitlementModule;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.glue.BusModule;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
+public class InvoiceModuleWithEmbeddedDb extends InvoiceModule {
+ private final MysqlTestingHelper helper = new MysqlTestingHelper();
+ private IDBI dbi;
+
+ public void startDb() throws IOException {
+ helper.startMysql();
+ }
+
+ public void initDb(final String ddl) throws IOException {
+ helper.initDb(ddl);
+ }
+
+ public void stopDb() {
+ helper.stopMysql();
+ }
+
+ public RecurringInvoiceItemSqlDao getInvoiceItemSqlDao() {
+ return dbi.onDemand(RecurringInvoiceItemSqlDao.class);
+ }
+
+ public InvoicePaymentSqlDao getInvoicePaymentSqlDao() {
+ return dbi.onDemand(InvoicePaymentSqlDao.class);
+ }
+
+ private void installNotificationQueue() {
+ bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+ }
+
+ @Override
+ public void configure() {
+ dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+
+ bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
+ installNotificationQueue();
+ install(new AccountModule());
+ install(new CatalogModule());
+ install(new EntitlementModule());
+ install(new GlobalLockerModule());
+
+ super.configure();
+
+ bind(InvoiceTestApi.class).to(DefaultInvoiceTestApi.class).asEagerSingleton();
+
+ install(new BusModule());
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
new file mode 100644
index 0000000..b180a03
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.glue;
+
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.MockGlobalLocker;
+
+
+public class InvoiceModuleWithMocks extends InvoiceModule {
+ @Override
+ protected void installInvoiceDao() {
+ bind(MockInvoiceDao.class).asEagerSingleton();
+ bind(InvoiceDao.class).to(MockInvoiceDao.class);
+ bind(GlobalLocker.class).to(MockGlobalLocker.class).asEagerSingleton();
+ }
+
+ @Override
+ protected void installInvoiceListener() {
+
+ }
+
+ @Override
+ protected void installNotifier() {
+
+ }
+
+ @Override
+ protected void installInvoiceService() {
+
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java b/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java
new file mode 100644
index 0000000..587144b
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/BrainDeadSubscription.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.notification;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
+
+public class BrainDeadSubscription implements Subscription {
+
+ @Override
+ public void cancel(DateTime requestedDate, boolean eot)
+ throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void uncancel() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void changePlan(String productName, BillingPeriod term,
+ String planSet, DateTime requestedDate)
+ throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+
+
+ }
+
+ @Override
+ public void pause() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+
+
+ }
+
+ @Override
+ public void resume() throws EntitlementUserApiException {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public UUID getId() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public UUID getBundleId() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public SubscriptionState getState() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getStartDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getEndDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public Plan getCurrentPlan() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public String getCurrentPriceList() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public PlanPhase getCurrentPhase() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getChargedThroughDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public DateTime getPaidThroughDate() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<SubscriptionTransition> getActiveTransitions() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<SubscriptionTransition> getAllTransitions() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public SubscriptionTransition getPendingTransition() {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public SubscriptionTransition getPreviousTransition() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
new file mode 100644
index 0000000..67c27fa
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/notification/TestNextBillingDateNotifier.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.notification;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.catalog.DefaultCatalogService;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.config.CatalogConfig;
+import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.entitlement.api.migration.AccountMigrationData;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+import com.ning.billing.entitlement.api.user.SubscriptionBundleData;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.engine.dao.EntitlementDao;
+import com.ning.billing.entitlement.engine.dao.EntitlementSqlDao;
+import com.ning.billing.entitlement.events.EntitlementEvent;
+import com.ning.billing.invoice.InvoiceListener;
+import com.ning.billing.invoice.dao.DefaultInvoiceDao;
+import com.ning.billing.lifecycle.KillbillService.ServiceException;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.notificationq.DefaultNotificationQueueService;
+import com.ning.billing.util.notificationq.DummySqlTest;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
+
+public class TestNextBillingDateNotifier {
+ private static Logger log = LoggerFactory.getLogger(TestNextBillingDateNotifier.class);
+ private Clock clock;
+ private DefaultNextBillingDateNotifier notifier;
+ private DummySqlTest dao;
+ private Bus eventBus;
+ private MysqlTestingHelper helper;
+ private InvoiceListenerMock listener = new InvoiceListenerMock();
+ private NotificationQueueService notificationQueueService;
+
+ private static final class InvoiceListenerMock extends InvoiceListener {
+ int eventCount = 0;
+ UUID latestSubscriptionId = null;
+
+ public InvoiceListenerMock() {
+ super(null);
+ }
+
+
+ @Override
+ public void handleNextBillingDateEvent(UUID subscriptionId,
+ DateTime eventDateTime) {
+ eventCount++;
+ latestSubscriptionId=subscriptionId;
+ }
+
+ public int getEventCount() {
+ return eventCount;
+ }
+
+ public UUID getLatestSubscriptionId(){
+ return latestSubscriptionId;
+ }
+
+ }
+
+ private class MockEntitlementDao implements EntitlementDao {
+
+ @Override
+ public List<SubscriptionBundle> getSubscriptionBundleForAccount(
+ UUID accountId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionBundle getSubscriptionBundleFromKey(String bundleKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionBundle getSubscriptionBundleFromId(UUID bundleId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SubscriptionBundle createSubscriptionBundle(
+ SubscriptionBundleData bundle) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public Subscription getSubscriptionFromId(UUID subscriptionId) {
+ return new BrainDeadSubscription();
+
+ }
+
+ @Override
+ public UUID getAccountIdFromSubscriptionId(UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public Subscription getBaseSubscription(UUID bundleId) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<Subscription> getSubscriptions(UUID bundleId) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<Subscription> getSubscriptionsForKey(String bundleKey) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void updateSubscription(SubscriptionData subscription) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void createNextPhaseEvent(UUID subscriptionId,
+ EntitlementEvent nextPhase) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public EntitlementEvent getEventById(UUID eventId) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<EntitlementEvent> getEventsForSubscription(
+ UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public List<EntitlementEvent> getPendingEventsForSubscription(
+ UUID subscriptionId) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void createSubscription(SubscriptionData subscription,
+ List<EntitlementEvent> initialEvents) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void cancelSubscription(UUID subscriptionId,
+ EntitlementEvent cancelEvent) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void uncancelSubscription(UUID subscriptionId,
+ List<EntitlementEvent> uncancelEvents) {
+ throw new UnsupportedOperationException();
+
+ }
+
+ @Override
+ public void changePlan(UUID subscriptionId,
+ List<EntitlementEvent> changeEvents) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void migrate(UUID acountId, AccountMigrationData data) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void undoMigration(UUID accountId) {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+ @BeforeClass(groups={"setup"})
+ public void setup() throws ServiceException, IOException, ClassNotFoundException, SQLException {
+ //TestApiBase.loadSystemPropertiesFromClasspath("/entitlement.properties");
+ final Injector g = Guice.createInjector(Stage.PRODUCTION, new AbstractModule() {
+ protected void configure() {
+ bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+ bind(NotificationQueueService.class).to(DefaultNotificationQueueService.class).asEagerSingleton();
+ final InvoiceConfig invoiceConfig = new ConfigurationObjectFactory(System.getProperties()).build(InvoiceConfig.class);
+ bind(InvoiceConfig.class).toInstance(invoiceConfig);
+ final CatalogConfig catalogConfig = new ConfigurationObjectFactory(System.getProperties()).build(CatalogConfig.class);
+ bind(CatalogConfig.class).toInstance(catalogConfig);
+ bind(CatalogService.class).to(DefaultCatalogService.class).asEagerSingleton();
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ bind(EntitlementDao.class).to(EntitlementSqlDao.class).asEagerSingleton();
+ }
+ });
+
+ clock = g.getInstance(Clock.class);
+ IDBI dbi = g.getInstance(IDBI.class);
+ dao = dbi.onDemand(DummySqlTest.class);
+ eventBus = g.getInstance(Bus.class);
+ helper = g.getInstance(MysqlTestingHelper.class);
+ notificationQueueService = g.getInstance(NotificationQueueService.class);
+ notifier = new DefaultNextBillingDateNotifier(notificationQueueService,g.getInstance(InvoiceConfig.class), new MockEntitlementDao(), listener);
+ startMysql();
+ }
+
+ private void startMysql() throws IOException, ClassNotFoundException, SQLException {
+ final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
+ final String entitlementDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
+ helper.startMysql();
+ helper.initDb(ddl);
+ helper.initDb(testDdl);
+ helper.initDb(entitlementDdl);
+ }
+
+
+ @Test(enabled=true, groups="slow")
+ public void test() throws Exception {
+ final UUID subscriptionId = new UUID(0L,1L);
+ final DateTime now = new DateTime();
+ final DateTime readyTime = now.plusMillis(2000);
+ final NextBillingDatePoster poster = new DefaultNextBillingDatePoster(notificationQueueService);
+
+ eventBus.start();
+ notifier.initialize();
+ notifier.start();
+
+
+ dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+ @Override
+ public Void inTransaction(DummySqlTest transactional,
+ TransactionStatus status) throws Exception {
+
+ poster.insertNextBillingNotification(transactional, subscriptionId, readyTime);
+ return null;
+ }
+ });
+
+
+ // Move time in the future after the notification effectiveDate
+ ((ClockMock) clock).setDeltaFromReality(3000);
+
+
+ await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return listener.getEventCount() == 1;
+ }
+ });
+
+ Assert.assertEquals(listener.getEventCount(), 1);
+ Assert.assertEquals(listener.getLatestSubscriptionId(), subscriptionId);
+ }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
index 5d36d55..f0de1ce 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/DefaultInvoiceGeneratorTests.java
@@ -16,18 +16,13 @@
package com.ning.billing.invoice.tests;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-
-import java.math.BigDecimal;
-import java.util.UUID;
-
-import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
-import com.ning.billing.catalog.MockCatalog;
+import com.ning.billing.catalog.DefaultPrice;
+import com.ning.billing.catalog.MockInternationalPrice;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PhaseType;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.catalog.api.PlanPhase;
import com.ning.billing.entitlement.api.billing.BillingEvent;
@@ -36,90 +31,104 @@ import com.ning.billing.entitlement.api.billing.DefaultBillingEvent;
import com.ning.billing.entitlement.api.user.Subscription;
import com.ning.billing.entitlement.api.user.SubscriptionData;
import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
-import com.ning.billing.invoice.api.BillingEventSet;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.dao.MockSubscription;
+import com.ning.billing.invoice.model.BillingEventSet;
import com.ning.billing.invoice.model.DefaultInvoiceGenerator;
-import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
import com.ning.billing.invoice.model.InvoiceGenerator;
import com.ning.billing.invoice.model.InvoiceItemList;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.Test;
+
+import javax.annotation.Nullable;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
-@Test(groups = {"invoicing", "invoiceGenerator"})
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+@Test(groups = {"fast", "invoicing", "invoiceGenerator"})
public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
- private final InvoiceGenerator generator = new DefaultInvoiceGenerator();
+
+
+ private final InvoiceGenerator generator = new DefaultInvoiceGenerator(new DefaultClock());
@Test
- public void testWithNullEventSetAndNullInvoiceSet() {
+ public void testWithNullEventSetAndNullInvoiceSet() throws InvoiceApiException {
UUID accountId = UUID.randomUUID();
- Invoice invoice = generator.generateInvoice(accountId, null, null, new DateTime(), Currency.USD);
+ Invoice invoice = generator.generateInvoice(accountId, new BillingEventSet(), new InvoiceItemList(), new DateTime(), Currency.USD);
- assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 0);
- assertEquals(invoice.getTotalAmount(), ZERO);
+ assertNull(invoice);
}
@Test
- public void testWithEmptyEventSet() {
+ public void testWithEmptyEventSet() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
UUID accountId = UUID.randomUUID();
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, new DateTime(), Currency.USD);
- assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 0);
- assertEquals(invoice.getTotalAmount(), ZERO);
+ assertNull(invoice);
}
@Test
- public void testWithSingleMonthlyEvent() {
+ public void testWithSingleMonthlyEvent() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
DateTime startDate = buildDateTime(2011, 9, 1);
- MockCatalog catalog = new MockCatalog();
- Plan plan = catalog.getCurrentPlans()[0];
- PlanPhase phase = plan.getAllPhases()[0];
-
- BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE, "Test");
+
+ Plan plan = new MockPlan();
+ BigDecimal rate1 = TEN;
+ PlanPhase phase = createMockMonthlyPlanPhase(rate1);
+
+ BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 1);
events.add(event);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-
+
DateTime targetDate = buildDateTime(2011, 10, 3);
UUID accountId = UUID.randomUUID();
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 1);
+ assertEquals(invoice.getNumberOfItems(), 2);
assertEquals(invoice.getTotalAmount(), TWENTY);
+ assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
}
@Test
- public void testWithSingleMonthlyEventWithLeadingProRation() {
+ public void testWithSingleMonthlyEventWithLeadingProRation() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
DateTime startDate = buildDateTime(2011, 9, 1);
- MockCatalog catalog = new MockCatalog();
- Plan plan = catalog.getCurrentPlans()[0];
- PlanPhase phase = plan.getAllPhases()[0];
+
+ Plan plan = new MockPlan();
BigDecimal rate = TEN;
- BillingEvent event = new DefaultBillingEvent(sub, startDate, plan, phase,
- new InternationalPriceMock(ZERO), new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
- 15, BillingModeType.IN_ADVANCE,"Test");
+ PlanPhase phase = createMockMonthlyPlanPhase(rate);
+ BillingEvent event = createBillingEvent(sub.getId(), startDate, plan, phase, 15);
events.add(event);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
-
- DateTime targetDate = buildDateTime(2011, 10, 3);
+
+ DateTime targetDate = buildDateTime(2011, 10, 3);
UUID accountId = UUID.randomUUID();
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 1);
+ assertEquals(invoice.getNumberOfItems(), 2);
BigDecimal expectedNumberOfBillingCycles;
expectedNumberOfBillingCycles = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
@@ -128,27 +137,23 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
}
@Test
- public void testTwoMonthlySubscriptionsWithAlignedBillingDates() {
+ public void testTwoMonthlySubscriptionsWithAlignedBillingDates() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
- MockCatalog catalog = new MockCatalog();
- Plan plan1 = catalog.getCurrentPlans()[0];
- PlanPhase phase1 = plan1.getAllPhases()[0];
- Plan plan2 = catalog.getCurrentPlans()[1];
- PlanPhase phase2 = plan2.getAllPhases()[0];
-
+ Plan plan1 = new MockPlan();
+ BigDecimal rate1 = FIVE;
+ PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
+
+ Plan plan2 = new MockPlan();
+ BigDecimal rate2 = TEN;
+ PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
- plan1,phase1,
- new InternationalPriceMock(ZERO), new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE, "Test");
+ BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
events.add(event1);
- BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 1),
- plan2,phase2,
- new InternationalPriceMock(ZERO), new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 1), plan2, phase2, 1);
events.add(event2);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -158,29 +163,24 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), 2);
- assertEquals(invoice.getTotalAmount(), FIVE.multiply(TWO).add(TEN).setScale(NUMBER_OF_DECIMALS));
+ assertEquals(invoice.getTotalAmount(), rate1.add(rate2).setScale(NUMBER_OF_DECIMALS));
}
@Test
- public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() {
+ public void testOnePlan_TwoMonthlyPhases_ChangeImmediate() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
- MockCatalog catalog = new MockCatalog();
- Plan plan1 = catalog.getCurrentPlans()[0];
- PlanPhase phase1 = plan1.getAllPhases()[0];
+ Plan plan1 = new MockPlan();
+ BigDecimal rate1 = FIVE;
+ PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
-
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
- plan1,phase1,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1,phase1, 1);
events.add(event1);
- BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 15),
- plan1,phase1, //technically should be a different phase but it doesn't impact the test
- new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 15, BillingModeType.IN_ADVANCE,"Test");
+ BigDecimal rate2 = TEN;
+ PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+ BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 15), plan1, phase2, 15);
events.add(event2);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -189,7 +189,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 2);
+ assertEquals(invoice.getNumberOfItems(), 4);
BigDecimal numberOfCyclesEvent1;
numberOfCyclesEvent1 = ONE.add(FOURTEEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD));
@@ -197,39 +197,33 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
BigDecimal numberOfCyclesEvent2 = TWO;
BigDecimal expectedValue;
- expectedValue = numberOfCyclesEvent1.multiply(FIVE);
- expectedValue = expectedValue.add(numberOfCyclesEvent2.multiply(TEN));
+ expectedValue = numberOfCyclesEvent1.multiply(rate1);
+ expectedValue = expectedValue.add(numberOfCyclesEvent2.multiply(rate2));
expectedValue = expectedValue.setScale(NUMBER_OF_DECIMALS);
assertEquals(invoice.getTotalAmount(), expectedValue);
}
@Test
- public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() {
+ public void testOnePlan_ThreeMonthlyPhases_ChangeEOT() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
- MockCatalog catalog = new MockCatalog();
- Plan plan1 = catalog.getCurrentPlans()[0];
- PlanPhase phase1 = plan1.getAllPhases()[0];
-
+ Plan plan1 = new MockPlan();
+ BigDecimal rate1 = FIVE;
+ PlanPhase phase1 = createMockMonthlyPlanPhase(rate1);
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- BillingEvent event1 = new DefaultBillingEvent(sub, buildDateTime(2011, 9, 1),
- plan1,phase1,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(FIVE), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BillingEvent event1 = createBillingEvent(sub.getId(), buildDateTime(2011, 9, 1), plan1, phase1, 1);
events.add(event1);
- BillingEvent event2 = new DefaultBillingEvent(sub, buildDateTime(2011, 10, 1),
- plan1,phase1, //technically should be a different phase but it doesn't impact the test
- new InternationalPriceMock(ZERO),new InternationalPriceMock(TEN), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BigDecimal rate2 = TEN;
+ PlanPhase phase2 = createMockMonthlyPlanPhase(rate2);
+ BillingEvent event2 = createBillingEvent(sub.getId(), buildDateTime(2011, 10, 1), plan1, phase2, 1);
events.add(event2);
- BillingEvent event3 = new DefaultBillingEvent(sub, buildDateTime(2011, 11, 1),
- plan1,phase1, //technically should be a different phase but it doesn't impact the test
- new InternationalPriceMock(ZERO),new InternationalPriceMock(THIRTY), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ BigDecimal rate3 = THIRTY;
+ PlanPhase phase3 = createMockMonthlyPlanPhase(rate3);
+ BillingEvent event3 = createBillingEvent(sub.getId(), buildDateTime(2011, 11, 1), plan1, phase3, 1);
events.add(event3);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
@@ -238,44 +232,38 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 3);
- assertEquals(invoice.getTotalAmount(), FIVE.add(TEN).add(TWO.multiply(THIRTY)).setScale(NUMBER_OF_DECIMALS));
+ assertEquals(invoice.getNumberOfItems(), 4);
+ assertEquals(invoice.getTotalAmount(), rate1.add(rate2).add(TWO.multiply(rate3)).setScale(NUMBER_OF_DECIMALS));
}
@Test
- public void testSingleEventWithExistingInvoice() {
+ public void testSingleEventWithExistingInvoice() throws InvoiceApiException {
BillingEventSet events = new BillingEventSet();
Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
DateTime startDate = buildDateTime(2011, 9, 1);
-
- MockCatalog catalog = new MockCatalog();
- Plan plan1 = catalog.getCurrentPlans()[0];
- PlanPhase phase1 = plan1.getAllPhases()[0];
-
+
+ Plan plan1 = new MockPlan();
BigDecimal rate = FIVE;
- BillingEvent event1 = new DefaultBillingEvent(sub, startDate,
- plan1,phase1,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
- 1, BillingModeType.IN_ADVANCE,"Test");
+ PlanPhase phase1 = createMockMonthlyPlanPhase(rate);
+
+ BillingEvent event1 = createBillingEvent(sub.getId(), startDate, plan1, phase1, 1);
events.add(event1);
+ DateTime targetDate = buildDateTime(2011, 12, 1);
+ UUID accountId = UUID.randomUUID();
+ Invoice invoice1 = generator.generateInvoice(accountId, events, null, targetDate, Currency.USD);
InvoiceItemList existingInvoiceItems = new InvoiceItemList();
- InvoiceItem invoiceItem = new DefaultInvoiceItem(UUID.randomUUID(), sub.getId(), startDate, buildDateTime(2012, 1, 1), "",
- rate.multiply(FOUR), rate, Currency.USD);
- existingInvoiceItems.add(invoiceItem);
+ existingInvoiceItems.addAll(invoice1.getInvoiceItems());
- DateTime targetDate = buildDateTime(2011, 12, 3);
- UUID accountId = UUID.randomUUID();
- Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
+ targetDate = buildDateTime(2011, 12, 3);
+ Invoice invoice2 = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, Currency.USD);
- assertNotNull(invoice);
- assertEquals(invoice.getNumberOfItems(), 0);
- assertEquals(invoice.getTotalAmount(), ZERO);
+ assertNull(invoice2);
}
@Test
- public void testMultiplePlansWithUtterChaos() {
+ public void testMultiplePlansWithUtterChaos() throws InvoiceApiException {
// plan 1: change of phase from trial to discount followed by immediate cancellation; (covers phase change, cancel, pro-ration)
// plan 2: single plan that moves from trial to discount to evergreen; BCD = 10 (covers phase change)
// plan 3: change of term from monthly (BCD = 20) to annual (BCD = 31; immediate)
@@ -283,35 +271,44 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
// plan 5: addon to plan 2, with bill cycle alignment to plan; immediate cancellation
UUID subscriptionId1 = UUID.randomUUID();
- String planName1 = "Change from trial to discount with immediate cancellation";
- String plan1PhaseName1 = "Trial"; String plan1PhaseName2 = "Discount"; String plan1phase3 = "Cancel";
+ UUID subscriptionId2 = UUID.randomUUID();
+ UUID subscriptionId3 = UUID.randomUUID();
+ UUID subscriptionId4 = UUID.randomUUID();
+ UUID subscriptionId5 = UUID.randomUUID();
+
+ Plan plan1 = new MockPlan("Change from trial to discount with immediate cancellation");
+ PlanPhase plan1Phase1 = createMockMonthlyPlanPhase(EIGHT, PhaseType.TRIAL);
+ PlanPhase plan1Phase2 = createMockMonthlyPlanPhase(TWELVE, PhaseType.DISCOUNT);
+ PlanPhase plan1Phase3 = createMockMonthlyPlanPhase();
DateTime plan1StartDate = buildDateTime(2011, 1, 5);
DateTime plan1PhaseChangeDate = buildDateTime(2011, 4, 5);
DateTime plan1CancelDate = buildDateTime(2011, 4, 29);
- UUID subscriptionId2 = UUID.randomUUID();
- String planName2 = "Change phase from trial to discount to evergreen";
- String plan2PhaseName1 = "Trial"; String plan2PhaseName2 = "Discount"; String plan2PhaseName3 = "Evergreen";
+ Plan plan2 = new MockPlan("Change phase from trial to discount to evergreen");
+ PlanPhase plan2Phase1 = createMockMonthlyPlanPhase(TWENTY, PhaseType.TRIAL);
+ PlanPhase plan2Phase2 = createMockMonthlyPlanPhase(THIRTY, PhaseType.DISCOUNT);
+ PlanPhase plan2Phase3 = createMockMonthlyPlanPhase(FORTY, PhaseType.EVERGREEN);
DateTime plan2StartDate = buildDateTime(2011, 3, 10);
DateTime plan2PhaseChangeToDiscountDate = buildDateTime(2011, 6, 10);
DateTime plan2PhaseChangeToEvergreenDate = buildDateTime(2011, 9, 10);
- UUID subscriptionId3 = UUID.randomUUID();
- String planName3 = "Upgrade with immediate change, BCD = 31";
- String plan3PhaseName1 = "Evergreen monthly"; String plan3PhaseName2 = "Evergreen annual";
+ Plan plan3 = new MockPlan("Upgrade with immediate change, BCD = 31");
+ PlanPhase plan3Phase1 = createMockMonthlyPlanPhase(TEN, PhaseType.EVERGREEN);
+ PlanPhase plan3Phase2 = createMockAnnualPlanPhase(ONE_HUNDRED, PhaseType.EVERGREEN);
DateTime plan3StartDate = buildDateTime(2011, 5, 20);
DateTime plan3UpgradeToAnnualDate = buildDateTime(2011, 7, 31);
- UUID subscriptionId4 = UUID.randomUUID();
- String planName4a = "Plan change effective EOT; plan 1";
- String planName4b = "Plan change effective EOT; plan 2";
- String plan4PhaseName = "Evergreen";
+ Plan plan4a = new MockPlan("Plan change effective EOT; plan 1");
+ Plan plan4b = new MockPlan("Plan change effective EOT; plan 2");
+ PlanPhase plan4aPhase1 = createMockMonthlyPlanPhase(FIFTEEN);
+ PlanPhase plan4bPhase1 = createMockMonthlyPlanPhase(TWENTY_FOUR);
+
DateTime plan4StartDate = buildDateTime(2011, 6, 7);
DateTime plan4ChangeOfPlanDate = buildDateTime(2011, 8, 7);
- UUID subscriptionId5 = UUID.randomUUID();
- String planName5 = "Add-on";
- String plan5PhaseName1 = "Evergreen"; String plan5PhaseName2 = "Cancel";
+ Plan plan5 = new MockPlan("Add-on");
+ PlanPhase plan5Phase1 = createMockMonthlyPlanPhase(TWENTY);
+ PlanPhase plan5Phase2 = createMockMonthlyPlanPhase();
DateTime plan5StartDate = buildDateTime(2011, 6, 21);
DateTime plan5CancelDate = buildDateTime(2011, 10, 7);
@@ -320,7 +317,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
BillingEventSet events = new BillingEventSet();
// on 1/5/2011, create subscription 1 (trial)
- events.add(createBillingEvent(subscriptionId1, plan1StartDate, planName1, plan1PhaseName1, EIGHT, 5));
+ events.add(createBillingEvent(subscriptionId1, plan1StartDate, plan1, plan1Phase1, 5));
expectedAmount = EIGHT;
testInvoiceGeneration(events, invoiceItems, plan1StartDate, 1, expectedAmount);
@@ -333,12 +330,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 3, 5), 1, expectedAmount);
// on 3/10/2011, create subscription 2 (trial)
- events.add(createBillingEvent(subscriptionId2, plan2StartDate, planName2, plan2PhaseName1, TWENTY, 10));
+ events.add(createBillingEvent(subscriptionId2, plan2StartDate, plan2, plan2Phase1, 10));
expectedAmount = TWENTY;
testInvoiceGeneration(events, invoiceItems, plan2StartDate, 1, expectedAmount);
// on 4/5/2011, invoice subscription 1 (discount)
- events.add(createBillingEvent(subscriptionId1, plan1PhaseChangeDate, planName1, plan1PhaseName2, TWELVE, 5));
+ events.add(createBillingEvent(subscriptionId1, plan1PhaseChangeDate, plan1, plan1Phase2, 5));
expectedAmount = TWELVE;
testInvoiceGeneration(events, invoiceItems, plan1PhaseChangeDate, 1, expectedAmount);
@@ -347,7 +344,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 4, 10), 1, expectedAmount);
// on 4/29/2011, cancel subscription 1
- events.add(createBillingEvent(subscriptionId1, plan1CancelDate, planName1, plan1phase3, ZERO, 5));
+ events.add(createBillingEvent(subscriptionId1, plan1CancelDate, plan1, plan1Phase3, 5));
expectedAmount = TWELVE.multiply(SIX.divide(THIRTY, NUMBER_OF_DECIMALS, ROUNDING_METHOD)).negate().setScale(NUMBER_OF_DECIMALS);
testInvoiceGeneration(events, invoiceItems, plan1CancelDate, 2, expectedAmount);
@@ -356,17 +353,17 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 5, 10), 1, expectedAmount);
// on 5/20/2011, create subscription 3 (monthly)
- events.add(createBillingEvent(subscriptionId3, plan3StartDate, planName3, plan3PhaseName1, TEN, 20));
+ events.add(createBillingEvent(subscriptionId3, plan3StartDate, plan3, plan3Phase1, 20));
expectedAmount = TEN;
testInvoiceGeneration(events, invoiceItems, plan3StartDate, 1, expectedAmount);
// on 6/7/2011, create subscription 4
- events.add(createBillingEvent(subscriptionId4, plan4StartDate, planName4a, plan4PhaseName, FIFTEEN, 7));
+ events.add(createBillingEvent(subscriptionId4, plan4StartDate, plan4a, plan4aPhase1, 7));
expectedAmount = FIFTEEN;
testInvoiceGeneration(events, invoiceItems, plan4StartDate, 1, expectedAmount);
// on 6/10/2011, invoice subscription 2 (discount)
- events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToDiscountDate, planName2, plan2PhaseName2, THIRTY, 10));
+ events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToDiscountDate, plan2, plan2Phase2, 10));
expectedAmount = THIRTY;
testInvoiceGeneration(events, invoiceItems, plan2PhaseChangeToDiscountDate, 1, expectedAmount);
@@ -375,7 +372,7 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 6, 20), 1, expectedAmount);
// on 6/21/2011, create add-on (subscription 5)
- events.add(createBillingEvent(subscriptionId5, plan5StartDate, planName5, plan5PhaseName1, TWENTY, 10));
+ events.add(createBillingEvent(subscriptionId5, plan5StartDate, plan5, plan5Phase1, 10));
expectedAmount = TWENTY.multiply(NINETEEN.divide(THIRTY, NUMBER_OF_DECIMALS, ROUNDING_METHOD)).setScale(NUMBER_OF_DECIMALS);
testInvoiceGeneration(events, invoiceItems, plan5StartDate, 1, expectedAmount);
@@ -392,14 +389,14 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 7, 20), 1, expectedAmount);
// on 7/31/2011, convert subscription 3 to annual
- events.add(createAnnualBillingEvent(subscriptionId3, plan3UpgradeToAnnualDate, planName3, plan3PhaseName2, ONE_HUNDRED, 31));
+ events.add(createBillingEvent(subscriptionId3, plan3UpgradeToAnnualDate, plan3, plan3Phase2, 31));
expectedAmount = ONE_HUNDRED.subtract(TEN);
expectedAmount = expectedAmount.add(TEN.multiply(ELEVEN.divide(THIRTY_ONE, NUMBER_OF_DECIMALS, ROUNDING_METHOD)));
expectedAmount = expectedAmount.setScale(NUMBER_OF_DECIMALS);
testInvoiceGeneration(events, invoiceItems, plan3UpgradeToAnnualDate, 3, expectedAmount);
// on 8/7/2011, invoice subscription 4 (plan 2)
- events.add(createBillingEvent(subscriptionId4, plan4ChangeOfPlanDate, planName4b, plan4PhaseName, TWENTY_FOUR, 7));
+ events.add(createBillingEvent(subscriptionId4, plan4ChangeOfPlanDate, plan4b, plan4bPhase1, 7));
expectedAmount = TWENTY_FOUR;
testInvoiceGeneration(events, invoiceItems, plan4ChangeOfPlanDate, 1, expectedAmount);
@@ -412,12 +409,12 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 9, 7), 1, expectedAmount);
// on 9/10/2011, invoice plan 2 (evergreen), invoice subscription 5
- events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToEvergreenDate, planName2, plan2PhaseName3, FORTY, 10));
+ events.add(createBillingEvent(subscriptionId2, plan2PhaseChangeToEvergreenDate, plan2, plan2Phase3, 10));
expectedAmount = FORTY.add(TWENTY);
testInvoiceGeneration(events, invoiceItems, plan2PhaseChangeToEvergreenDate, 2, expectedAmount);
// on 10/7/2011, invoice subscription 4 (plan 2), cancel subscription 5
- events.add(createBillingEvent(subscriptionId5, plan5CancelDate, planName5, plan5PhaseName2, ZERO, 10));
+ events.add(createBillingEvent(subscriptionId5, plan5CancelDate, plan5, plan5Phase2, 10));
expectedAmount = TWENTY_FOUR.add(TWENTY.multiply(THREE.divide(THIRTY)).negate().setScale(NUMBER_OF_DECIMALS));
testInvoiceGeneration(events, invoiceItems, plan5CancelDate, 3, expectedAmount);
@@ -426,35 +423,192 @@ public class DefaultInvoiceGeneratorTests extends InvoicingTestBase {
testInvoiceGeneration(events, invoiceItems, buildDateTime(2011, 10, 10), 1, expectedAmount);
}
- private DefaultBillingEvent createBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
- BigDecimal rate, int billCycleDay) {
- MockCatalog catalog = new MockCatalog();
- Plan plan = catalog.getCurrentPlans()[0];
- PlanPhase phase = plan.getAllPhases()[0];
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- return new DefaultBillingEvent(sub, startDate, plan, phase,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.MONTHLY,
- billCycleDay, BillingModeType.IN_ADVANCE,"Test");
+ @Test
+ public void testZeroDollarEvents() throws InvoiceApiException {
+ Plan plan = new MockPlan();
+ PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
+ BillingEventSet events = new BillingEventSet();
+ DateTime targetDate = buildDateTime(2011, 1, 1);
+ events.add(createBillingEvent(UUID.randomUUID(), targetDate, plan, planPhase, 1));
+
+ Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+
+ assertEquals(invoice.getNumberOfItems(), 1);
}
- private DefaultBillingEvent createAnnualBillingEvent(UUID subscriptionId, DateTime startDate, String planName, String planPhaseName,
- BigDecimal rate, int billCycleDay) {
- MockCatalog catalog = new MockCatalog();
- Plan plan = catalog.getCurrentPlans()[0];
- PlanPhase phase = plan.getAllPhases()[0];
- Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(UUID.randomUUID()));
- return new DefaultBillingEvent(sub, startDate, plan, phase,
- new InternationalPriceMock(ZERO),new InternationalPriceMock(rate), BillingPeriod.ANNUAL,
- billCycleDay, BillingModeType.IN_ADVANCE,"Test");
+ @Test
+ public void testEndDateIsCorrect() throws InvoiceApiException {
+ Plan plan = new MockPlan();
+ PlanPhase planPhase = createMockMonthlyPlanPhase(ZERO);
+ BillingEventSet events = new BillingEventSet();
+ DateTime targetDate = new DateTime();
+ events.add(createBillingEvent(UUID.randomUUID(), targetDate, plan, planPhase, targetDate.getDayOfMonth()));
+
+ Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, targetDate, Currency.USD);
+ RecurringInvoiceItem item = (RecurringInvoiceItem) invoice.getInvoiceItems().get(0);
+
+ // end date of the invoice item should be equal to exactly one month later
+ assertEquals(item.getEndDate().compareTo(targetDate.plusMonths(1)), 0);
+ }
+
+ @Test
+ public void testFixedPriceLifeCycle() throws InvoiceApiException {
+ UUID accountId = UUID.randomUUID();
+ Subscription subscription = new MockSubscription();
+ Plan plan = new MockPlan("plan 1");
+ MockInternationalPrice zeroPrice = new MockInternationalPrice(new DefaultPrice(ZERO, Currency.USD));
+ MockInternationalPrice cheapPrice = new MockInternationalPrice(new DefaultPrice(ONE, Currency.USD));
+
+ PlanPhase phase1 = new MockPlanPhase(null, zeroPrice, BillingPeriod.NO_BILLING_PERIOD, PhaseType.TRIAL);
+ PlanPhase phase2 = new MockPlanPhase(cheapPrice, null, BillingPeriod.MONTHLY, PhaseType.DISCOUNT);
+
+ DateTime changeDate = new DateTime("2012-04-1T00:00:00.000-08:00");
+
+ BillingEventSet events = new BillingEventSet();
+
+ BillingEvent event1 = new DefaultBillingEvent(subscription, new DateTime("2012-01-1T00:00:00.000-08:00"),
+ plan, phase1,
+ zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 1",
+ SubscriptionTransitionType.CREATE);
+
+ BillingEvent event2 = new DefaultBillingEvent(subscription, changeDate,
+ plan, phase2,
+ zeroPrice, null, BillingPeriod.NO_BILLING_PERIOD, 1,
+ BillingModeType.IN_ADVANCE, "Test Event 2",
+ SubscriptionTransitionType.PHASE);
+
+ events.add(event2);
+ events.add(event1);
+ Invoice invoice1 = generator.generateInvoice(accountId, events, null, new DateTime("2012-02-01T00:01:00.000-08:00"), Currency.USD);
+ assertNotNull(invoice1);
+ assertEquals(invoice1.getNumberOfItems(), 1);
+
+ Invoice invoice2 = generator.generateInvoice(accountId, events, invoice1.getInvoiceItems(), new DateTime("2012-04-05T00:01:00.000-08:00"), Currency.USD);
+ assertNotNull(invoice2);
+ assertEquals(invoice2.getNumberOfItems(), 1);
+ FixedPriceInvoiceItem item = (FixedPriceInvoiceItem) invoice2.getInvoiceItems().get(0);
+ assertEquals(item.getStartDate().compareTo(changeDate), 0);
+ }
+
+ @Test
+ public void testMixedModeLifeCycle() throws InvoiceApiException {
+ // create a subscription with a fixed price and recurring price
+ Plan plan1 = new MockPlan();
+ BigDecimal monthlyRate = FIVE;
+ BigDecimal fixedCost = TEN;
+ PlanPhase phase1 = createMockMonthlyPlanPhase(monthlyRate, fixedCost, PhaseType.TRIAL);
+
+ BillingEventSet events = new BillingEventSet();
+ UUID subscriptionId = UUID.randomUUID();
+ UUID accountId = UUID.randomUUID();
+
+ DateTime startDate = new DateTime(2011, 1, 1, 3, 40, 27, 0);
+ BillingEvent event1 = createBillingEvent(subscriptionId, startDate, plan1, phase1, 1);
+ events.add(event1);
+
+ // ensure both components are invoiced
+ Invoice invoice1 = generator.generateInvoice(accountId, events, null, startDate, Currency.USD);
+ assertNotNull(invoice1);
+ assertEquals(invoice1.getNumberOfItems(), 2);
+ assertEquals(invoice1.getTotalAmount(), FIFTEEN);
+
+ List<InvoiceItem> existingItems = invoice1.getInvoiceItems();
+
+ // move forward in time one billing period
+ DateTime currentDate = startDate.plusMonths(1);
+
+ // ensure that only the recurring price is invoiced
+ Invoice invoice2 = generator.generateInvoice(accountId, events, existingItems, currentDate, Currency.USD);
+ assertNotNull(invoice2);
+ assertEquals(invoice2.getNumberOfItems(), 1);
+ assertEquals(invoice2.getTotalAmount(), FIVE);
}
- private void testInvoiceGeneration(BillingEventSet events, InvoiceItemList existingInvoiceItems, DateTime targetDate, int expectedNumberOfItems, BigDecimal expectedAmount) {
+ @Test
+ public void testFixedModePlanChange() throws InvoiceApiException {
+ // create a subscription with a fixed price and recurring price
+ Plan plan1 = new MockPlan();
+ BigDecimal fixedCost1 = TEN;
+ BigDecimal fixedCost2 = TWENTY;
+ PlanPhase phase1 = createMockMonthlyPlanPhase(null, fixedCost1, PhaseType.TRIAL);
+ PlanPhase phase2 = createMockMonthlyPlanPhase(null, fixedCost2, PhaseType.EVERGREEN);
+
+ BillingEventSet events = new BillingEventSet();
+ UUID subscriptionId = UUID.randomUUID();
+ UUID accountId = UUID.randomUUID();
+
+ DateTime startDate = new DateTime(2011, 1, 1, 3, 40, 27, 0);
+ BillingEvent event1 = createBillingEvent(subscriptionId, startDate, plan1, phase1, 1);
+ events.add(event1);
+
+ // ensure that a single invoice item is generated for the fixed cost
+ Invoice invoice1 = generator.generateInvoice(accountId, events, null, startDate, Currency.USD);
+ assertNotNull(invoice1);
+ assertEquals(invoice1.getNumberOfItems(), 1);
+ assertEquals(invoice1.getTotalAmount(), fixedCost1);
+
+ List<InvoiceItem> existingItems = invoice1.getInvoiceItems();
+
+ // move forward in time one billing period
+ DateTime phaseChangeDate = startDate.plusMonths(1);
+ BillingEvent event2 = createBillingEvent(subscriptionId, phaseChangeDate, plan1, phase2, 1);
+ events.add(event2);
+
+ // ensure that a single invoice item is generated for the fixed cost
+ Invoice invoice2 = generator.generateInvoice(accountId, events, existingItems, phaseChangeDate, Currency.USD);
+ assertNotNull(invoice2);
+ assertEquals(invoice2.getNumberOfItems(), 1);
+ assertEquals(invoice2.getTotalAmount(), fixedCost2);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase() {
+ return new MockPlanPhase(null, null, BillingPeriod.MONTHLY);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase(@Nullable final BigDecimal recurringRate) {
+ return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+ null, BillingPeriod.MONTHLY);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
+ return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+ null, BillingPeriod.MONTHLY, phaseType);
+ }
+
+ private MockPlanPhase createMockMonthlyPlanPhase(@Nullable BigDecimal recurringRate, final BigDecimal fixedCost,
+ final PhaseType phaseType) {
+ MockInternationalPrice recurringPrice = (recurringRate == null) ? null : new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD));
+ MockInternationalPrice fixedPrice = (fixedCost == null) ? null : new MockInternationalPrice(new DefaultPrice(fixedCost, Currency.USD));
+
+ return new MockPlanPhase(recurringPrice, fixedPrice, BillingPeriod.MONTHLY, phaseType);
+ }
+
+ private MockPlanPhase createMockAnnualPlanPhase(final BigDecimal recurringRate, final PhaseType phaseType) {
+ return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+ null, BillingPeriod.ANNUAL, phaseType);
+ }
+
+ private DefaultBillingEvent createBillingEvent(final UUID subscriptionId, final DateTime startDate,
+ final Plan plan, final PlanPhase planPhase, final int billCycleDay) {
+ Subscription sub = new SubscriptionData(new SubscriptionBuilder().setId(subscriptionId));
+
+ return new DefaultBillingEvent(sub, startDate, plan, planPhase,
+ planPhase.getFixedPrice(),
+ planPhase.getRecurringPrice(), planPhase.getBillingPeriod(),
+ billCycleDay, BillingModeType.IN_ADVANCE,"Test", SubscriptionTransitionType.CREATE);
+ }
+
+ private void testInvoiceGeneration(final BillingEventSet events, final InvoiceItemList existingInvoiceItems,
+ final DateTime targetDate, final int expectedNumberOfItems,
+ final BigDecimal expectedAmount) throws InvoiceApiException {
Currency currency = Currency.USD;
UUID accountId = UUID.randomUUID();
Invoice invoice = generator.generateInvoice(accountId, events, existingInvoiceItems, targetDate, currency);
- existingInvoiceItems.addAll(invoice.getItems());
assertNotNull(invoice);
assertEquals(invoice.getNumberOfItems(), expectedNumberOfItems);
+
+ existingInvoiceItems.addAll(invoice.getInvoiceItems());
assertEquals(invoice.getTotalAmount(), expectedAmount);
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
index 84bf795..64fa95c 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/DoubleProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class DoubleProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
index f612f10..8d0eb06 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/GenericProRationTests.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class GenericProRationTests extends GenericProRationTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
index f44876e..a009fba 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/LeadingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class LeadingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
index 274a8e7..f882005 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/ProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class ProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
index 0689c69..e0216b5 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/annual/TrailingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class TrailingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
index d4e4c24..60892e6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/GenericProRationTestBase.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBase {
/**
* used for testing cancellation in less than a single billing period
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
index 4799e40..240b8aa 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/DoubleProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class DoubleProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
index eb3b1c9..8b40db8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/GenericProRationTests.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class GenericProRationTests extends GenericProRationTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
index 5db6911..998e566 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/LeadingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class LeadingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
index 13c75df..c3748d1 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/ProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class ProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
index 9a8e635..6a5e5ef 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/monthly/TrailingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class TrailingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
index 14392a8..18bd096 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ProRationInAdvanceTestBase.java
@@ -21,7 +21,7 @@ import com.ning.billing.invoice.model.InAdvanceBillingMode;
import com.ning.billing.invoice.tests.ProRationTestBase;
import org.testng.annotations.Test;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public abstract class ProRationInAdvanceTestBase extends ProRationTestBase {
@Override
protected BillingMode getBillingMode() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
index 68e29fd..c1d6085 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/DoubleProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class DoubleProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
index 8306716..c4237a6 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/GenericProRationTests.java
@@ -22,7 +22,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class GenericProRationTests extends GenericProRationTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
index fe614a9..04ec683 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/LeadingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class LeadingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
index c99aa5c..e13db0d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/ProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class ProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
index 89e138e..8f63010 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/quarterly/TrailingProRationTests.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
import java.math.BigDecimal;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class TrailingProRationTests extends ProRationInAdvanceTestBase {
@Override
protected BillingPeriod getBillingPeriod() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
index 9de01bd..bd8a38d 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/inAdvance/ValidationProRationTests.java
@@ -28,7 +28,7 @@ import java.math.BigDecimal;
import static org.testng.Assert.assertEquals;
-@Test(groups = {"invoicing", "proRation"})
+@Test(groups = {"fast", "invoicing", "proRation"})
public class ValidationProRationTests extends ProRationTestBase {
protected BillingPeriod getBillingPeriod() {
return BillingPeriod.MONTHLY;
@@ -39,14 +39,6 @@ public class ValidationProRationTests extends ProRationTestBase {
return new InAdvanceBillingMode();
}
- protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
- return getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod());
- }
-
- protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
- return getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
- }
-
@Test(expectedExceptions = InvalidDateSequenceException.class)
public void testTargetStartEnd() throws InvalidDateSequenceException {
DateTime startDate = buildDateTime(2011, 1, 30);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
index 8577544..ab11024 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InternationalPriceMock.java
@@ -43,4 +43,9 @@ public class InternationalPriceMock implements InternationalPrice {
return rate;
}
+ @Override
+ public boolean isZero() {
+ return rate.compareTo(BigDecimal.ZERO) == 0;
+ }
+
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
index afc9d1c..c90bf69 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoicingTestBase.java
@@ -24,7 +24,7 @@ import java.math.BigDecimal;
public abstract class InvoicingTestBase {
protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
- protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMethod();
+ protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
protected static final BigDecimal ZERO = new BigDecimal("0.0").setScale(NUMBER_OF_DECIMALS);
protected static final BigDecimal ONE_HALF = new BigDecimal("0.5").setScale(NUMBER_OF_DECIMALS);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
index 174017b..a24b0c0 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/ProRationTestBase.java
@@ -19,23 +19,25 @@ package com.ning.billing.invoice.tests;
import com.ning.billing.catalog.api.BillingPeriod;
import com.ning.billing.invoice.model.BillingMode;
import com.ning.billing.invoice.model.InvalidDateSequenceException;
+import com.ning.billing.invoice.model.RecurringInvoiceItemData;
import org.joda.time.DateTime;
import java.math.BigDecimal;
+import java.util.List;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
-public abstract class ProRationTestBase extends InvoicingTestBase{
+public abstract class ProRationTestBase extends InvoicingTestBase {
protected abstract BillingMode getBillingMode();
protected abstract BillingPeriod getBillingPeriod();
protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException {
try {
BigDecimal numberOfBillingCycles;
- numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay, getBillingPeriod());
+ numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, targetDate, billingCycleDay);
- assertEquals(numberOfBillingCycles, expectedValue);
+ assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0, "Actual: " + numberOfBillingCycles.toString() + "; expected: " + expectedValue.toString());
} catch (InvalidDateSequenceException idse) {
throw idse;
} catch (Exception e) {
@@ -46,13 +48,35 @@ public abstract class ProRationTestBase extends InvoicingTestBase{
protected void testCalculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay, BigDecimal expectedValue) throws InvalidDateSequenceException {
try {
BigDecimal numberOfBillingCycles;
- numberOfBillingCycles = getBillingMode().calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
+ numberOfBillingCycles = calculateNumberOfBillingCycles(startDate, endDate, targetDate, billingCycleDay);
- assertEquals(numberOfBillingCycles, expectedValue);
+ assertEquals(numberOfBillingCycles.compareTo(expectedValue), 0);
} catch (InvalidDateSequenceException idse) {
throw idse;
} catch (Exception e) {
fail("Unexpected exception: " + e.getMessage());
}
}
+
+ protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime endDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
+ List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, endDate, targetDate, billingCycleDay, getBillingPeriod());
+
+ BigDecimal numberOfBillingCycles = ZERO;
+ for (RecurringInvoiceItemData item : items) {
+ numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
+ }
+
+ return numberOfBillingCycles;
+ }
+
+ protected BigDecimal calculateNumberOfBillingCycles(DateTime startDate, DateTime targetDate, int billingCycleDay) throws InvalidDateSequenceException {
+ List<RecurringInvoiceItemData> items = getBillingMode().calculateInvoiceItemData(startDate, targetDate, billingCycleDay, getBillingPeriod());
+
+ BigDecimal numberOfBillingCycles = ZERO;
+ for (RecurringInvoiceItemData item : items) {
+ numberOfBillingCycles = numberOfBillingCycles.add(item.getNumberOfCycles());
+ }
+
+ return numberOfBillingCycles;
+ }
}
\ No newline at end of file
invoice/src/test/resources/log4j.xml 36(+36 -0)
diff --git a/invoice/src/test/resources/log4j.xml b/invoice/src/test/resources/log4j.xml
new file mode 100644
index 0000000..33b9662
--- /dev/null
+++ b/invoice/src/test/resources/log4j.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2010-2011 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %d{ISO8601} %X{trace} %t %c %m%n"/>
+ </layout>
+ </appender>
+
+
+ <logger name="com.ning.billing.invoice">
+ <level value="info"/>
+ </logger>
+
+ <root>
+ <priority value="info"/>
+ <appender-ref ref="stdout"/>
+ </root>
+</log4j:configuration>
payment/pom.xml 100(+97 -3)
diff --git a/payment/pom.xml b/payment/pom.xml
index 3d6356e..bdb30c8 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,14 +13,108 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
<name>killbill-payment</name>
<packaging>jar</packaging>
<dependencies>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-invoice</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject.extensions</groupId>
+ <artifactId>guice-multibindings</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>joda-time</groupId>
+ <artifactId>joda-time</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jayway.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-account</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-invoice</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.mysql</groupId>
+ <artifactId>management</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.mysql</groupId>
+ <artifactId>management-dbfiles</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
- <build>
- </build>
</project>
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
new file mode 100644
index 0000000..a8364bf
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.PaymentProviderPlugin;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+
+public class DefaultPaymentApi implements PaymentApi {
+ private final PaymentProviderPluginRegistry pluginRegistry;
+ private final AccountUserApi accountUserApi;
+ private final InvoicePaymentApi invoicePaymentApi;
+ private final PaymentDao paymentDao;
+
+ private static final Logger log = LoggerFactory.getLogger(DefaultPaymentApi.class);
+
+ @Inject
+ public DefaultPaymentApi(PaymentProviderPluginRegistry pluginRegistry,
+ AccountUserApi accountUserApi,
+ InvoicePaymentApi invoicePaymentApi,
+ PaymentDao paymentDao) {
+ this.pluginRegistry = pluginRegistry;
+ this.accountUserApi = accountUserApi;
+ this.invoicePaymentApi = invoicePaymentApi;
+ this.paymentDao = paymentDao;
+ }
+
+ @Override
+ public Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId) {
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+ return plugin.getPaymentMethodInfo(paymentMethodId);
+ }
+
+ private PaymentProviderPlugin getPaymentProviderPlugin(String accountKey) {
+ String paymentProviderName = null;
+
+ if (accountKey != null) {
+ final Account account = accountUserApi.getAccountByKey(accountKey);
+ if (account != null) {
+ return getPaymentProviderPlugin(account);
+ }
+ }
+
+ return pluginRegistry.getPlugin(paymentProviderName);
+ }
+
+ private PaymentProviderPlugin getPaymentProviderPlugin(Account account) {
+ String paymentProviderName = null;
+
+ if (account != null) {
+ paymentProviderName = account.getPaymentProviderName();
+ }
+
+ return pluginRegistry.getPlugin(paymentProviderName);
+ }
+
+ @Override
+ public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey) {
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+ return plugin.getPaymentMethods(accountKey);
+ }
+
+ @Override
+ public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+ return plugin.updatePaymentGateway(accountKey);
+ }
+
+ @Override
+ public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+ return plugin.getPaymentProviderAccount(accountKey);
+ }
+
+ @Override
+ public Either<PaymentError, String> addPaymentMethod(@Nullable String accountKey, PaymentMethodInfo paymentMethod) {
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+ return plugin.addPaymentMethod(accountKey, paymentMethod);
+ }
+
+ @Override
+ public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+ return plugin.deletePaymentMethod(accountKey, paymentMethodId);
+ }
+
+ @Override
+ public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo) {
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+ return plugin.updatePaymentMethod(accountKey, paymentMethodInfo);
+ }
+
+ @Override
+ public List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds) {
+ final Account account = accountUserApi.getAccountByKey(accountKey);
+ return createPayment(account, invoiceIds);
+ }
+
+ @Override
+ public List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds) {
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+
+ List<Either<PaymentError, PaymentInfo>> processedPaymentsOrErrors = new ArrayList<Either<PaymentError, PaymentInfo>>(invoiceIds.size());
+
+ for (String invoiceId : invoiceIds) {
+ Invoice invoice = invoicePaymentApi.getInvoice(UUID.fromString(invoiceId));
+
+ if (invoice.getBalance().compareTo(BigDecimal.ZERO) == 0 ) {
+ // TODO: send a notification that invoice was ignored?
+ log.info("Received invoice for payment with outstanding amount of 0 {} ", invoice);
+ }
+ else if (invoiceId.equals(paymentDao.getPaymentAttemptForInvoiceId(invoiceId))) {
+ //TODO: do equals on invoice instead and only reject when invoice is exactly the same?
+ log.info("Duplicate invoice payment event, already received invoice {} ", invoice);
+ }
+ else {
+ PaymentAttempt paymentAttempt = paymentDao.createPaymentAttempt(invoice);
+ Either<PaymentError, PaymentInfo> paymentOrError = plugin.processInvoice(account, invoice);
+ processedPaymentsOrErrors.add(paymentOrError);
+
+ PaymentInfo paymentInfo = null;
+
+ if (paymentOrError.isRight()) {
+ paymentInfo = paymentOrError.getRight();
+ paymentDao.savePaymentInfo(paymentInfo);
+
+ Either<PaymentError, PaymentMethodInfo> paymentMethodInfoOrError = plugin.getPaymentMethodInfo(paymentInfo.getPaymentMethodId());
+
+ if (paymentMethodInfoOrError.isRight()) {
+ PaymentMethodInfo paymentMethodInfo = paymentMethodInfoOrError.getRight();
+
+ if (paymentMethodInfo instanceof CreditCardPaymentMethodInfo) {
+ CreditCardPaymentMethodInfo ccPaymentMethod = (CreditCardPaymentMethodInfo)paymentMethodInfo;
+ paymentDao.updatePaymentInfo(ccPaymentMethod.getType(), paymentInfo.getPaymentId(), ccPaymentMethod.getCardType(), ccPaymentMethod.getCardCountry());
+ }
+ else if (paymentMethodInfo instanceof PaypalPaymentMethodInfo) {
+ PaypalPaymentMethodInfo paypalPaymentMethodInfo = (PaypalPaymentMethodInfo)paymentMethodInfo;
+ paymentDao.updatePaymentInfo(paypalPaymentMethodInfo.getType(), paymentInfo.getPaymentId(), null, null);
+ }
+ }
+
+
+ if (paymentInfo.getPaymentId() != null) {
+ paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getPaymentAttemptId(), paymentInfo.getPaymentId());
+ }
+ }
+
+ invoicePaymentApi.notifyOfPaymentAttempt(new DefaultInvoicePayment(paymentAttempt.getPaymentAttemptId(),
+ invoice.getId(),
+ paymentAttempt.getPaymentAttemptDate(),
+ paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : paymentInfo.getAmount(),
+// paymentInfo.getRefundAmount(), TODO
+ paymentInfo == null || paymentInfo.getStatus().equalsIgnoreCase("Error") ? null : invoice.getCurrency()));
+
+ }
+ }
+
+ return processedPaymentsOrErrors;
+ }
+
+ @Override
+ public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin((Account)null);
+ return plugin.createPaymentProviderAccount(account);
+ }
+
+ @Override
+ public Either<PaymentError, Void> updatePaymentProviderAccountContact(String externalKey) {
+ Account account = accountUserApi.getAccountByKey(externalKey);
+ final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+ return plugin.updatePaymentProviderAccountExistingContact(account);
+ }
+
+ @Override
+ public PaymentAttempt getPaymentAttemptForPaymentId(String id) {
+ return paymentDao.getPaymentAttemptForPaymentId(id);
+ }
+
+ @Override
+ public List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds) {
+ //TODO
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
new file mode 100644
index 0000000..eacc226
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import java.util.UUID;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public class DefaultPaymentDao implements PaymentDao {
+ private final PaymentSqlDao sqlDao;
+
+ @Inject
+ public DefaultPaymentDao(IDBI dbi) {
+ this.sqlDao = dbi.onDemand(PaymentSqlDao.class);
+ }
+
+ @Override
+ public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
+ return sqlDao.getPaymentAttemptForPaymentId(paymentId);
+ }
+
+ @Override
+ public PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId) {
+ return sqlDao.getPaymentAttemptForInvoiceId(invoiceId);
+ }
+
+ @Override
+ public PaymentAttempt createPaymentAttempt(Invoice invoice) {
+ final PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+
+ sqlDao.insertPaymentAttempt(paymentAttempt);
+ return paymentAttempt;
+ }
+
+ @Override
+ public void savePaymentInfo(PaymentInfo info) {
+ sqlDao.insertPaymentInfo(info);
+ }
+
+ @Override
+ public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId) {
+ sqlDao.updatePaymentAttemptWithPaymentId(paymentAttemptId.toString(), paymentId);
+ }
+
+ @Override
+ public void updatePaymentInfo(String type, String paymentId, String cardType, String cardCountry) {
+ sqlDao.updatePaymentInfo(type, paymentId, cardType, cardCountry);
+ }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
new file mode 100644
index 0000000..5cf065b
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import java.util.UUID;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public interface PaymentDao {
+
+ PaymentAttempt createPaymentAttempt(Invoice invoice);
+
+ void savePaymentInfo(PaymentInfo right);
+
+ PaymentAttempt getPaymentAttemptForPaymentId(String paymentId);
+
+ void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId);
+
+ PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId);
+
+ void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry);
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
new file mode 100644
index 0000000..972ee64
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+@ExternalizedSqlViaStringTemplate3()
+public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Transmogrifier {
+ @SqlUpdate
+ void insertPaymentAttempt(@Bind(binder = PaymentAttemptBinder.class) PaymentAttempt paymentAttempt);
+
+ @SqlQuery
+ @Mapper(PaymentAttemptMapper.class)
+ PaymentAttempt getPaymentAttemptForPaymentId(@Bind("payment_id") String paymentId);
+
+ @SqlQuery
+ @Mapper(PaymentAttemptMapper.class)
+ PaymentAttempt getPaymentAttemptForInvoiceId(@Bind("invoice_id") String invoiceId);
+
+ @SqlUpdate
+ void updatePaymentAttemptWithPaymentId(@Bind("payment_attempt_id") String paymentAttemptId,
+ @Bind("payment_id") String paymentId);
+
+ @SqlUpdate
+ void updatePaymentInfo(@Bind("payment_method") String paymentMethod,
+ @Bind("payment_id") String paymentId,
+ @Bind("card_type") String cardType,
+ @Bind("card_country") String cardCountry);
+
+ @SqlUpdate
+ void insertPaymentInfo(@Bind(binder = PaymentInfoBinder.class) PaymentInfo paymentInfo);
+
+ public static final class PaymentAttemptBinder implements Binder<Bind, PaymentAttempt> {
+
+ private Date getDate(DateTime dateTime) {
+ return dateTime == null ? null : dateTime.toDate();
+ }
+
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentAttempt paymentAttempt) {
+ stmt.bind("payment_attempt_id", paymentAttempt.getPaymentAttemptId().toString());
+ stmt.bind("invoice_id", paymentAttempt.getInvoiceId().toString());
+ stmt.bind("account_id", paymentAttempt.getAccountId().toString());
+ stmt.bind("amount", paymentAttempt.getAmount());
+ stmt.bind("currency", paymentAttempt.getCurrency().toString());
+ stmt.bind("invoice_dt", getDate(paymentAttempt.getInvoiceDate()));
+ stmt.bind("payment_attempt_dt", getDate(paymentAttempt.getPaymentAttemptDate()));
+ stmt.bind("payment_id", paymentAttempt.getPaymentId());
+ stmt.bind("retry_count", paymentAttempt.getRetryCount());
+ stmt.bind("next_retry_dt", getDate(paymentAttempt.getNextRetryDate()));
+ stmt.bind("created_dt", getDate(paymentAttempt.getCreatedDate()));
+ stmt.bind("updated_dt", getDate(paymentAttempt.getUpdatedDate()));
+ }
+ }
+
+ public static class PaymentAttemptMapper implements ResultSetMapper<PaymentAttempt> {
+
+ private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+ final Timestamp resultStamp = rs.getTimestamp(fieldName);
+ return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+ }
+
+ @Override
+ public PaymentAttempt map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+
+ UUID paymentAttemptId = UUID.fromString(rs.getString("payment_attempt_id"));
+ UUID invoiceId = UUID.fromString(rs.getString("invoice_id"));
+ UUID accountId = UUID.fromString(rs.getString("account_id"));
+ BigDecimal amount = rs.getBigDecimal("amount");
+ Currency currency = Currency.valueOf(rs.getString("currency"));
+ DateTime invoiceDate = getDate(rs, "invoice_dt");
+ DateTime paymentAttemptDate = getDate(rs, "payment_attempt_dt");
+ String paymentId = rs.getString("payment_id");
+ Integer retryCount = rs.getInt("retry_count");
+ DateTime nextRetryDate = getDate(rs, "next_retry_dt");
+ DateTime createdDate = getDate(rs, "created_dt");
+ DateTime updatedDate = getDate(rs, "updated_dt");
+
+ return new PaymentAttempt(paymentAttemptId,
+ invoiceId,
+ accountId,
+ amount,
+ currency,
+ invoiceDate,
+ paymentAttemptDate,
+ paymentId,
+ retryCount,
+ nextRetryDate,
+ createdDate,
+ updatedDate);
+ }
+ }
+
+ public static final class PaymentInfoBinder implements Binder<Bind, PaymentInfo> {
+
+ private Date getDate(DateTime dateTime) {
+ return dateTime == null ? null : dateTime.toDate();
+ }
+
+ @Override
+ public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentInfo paymentInfo) {
+ stmt.bind("payment_id", paymentInfo.getPaymentId().toString());
+ stmt.bind("amount", paymentInfo.getAmount());
+ stmt.bind("refund_amount", paymentInfo.getRefundAmount());
+ stmt.bind("payment_number", paymentInfo.getPaymentNumber());
+ stmt.bind("bank_identification_number", paymentInfo.getBankIdentificationNumber());
+ stmt.bind("status", paymentInfo.getStatus());
+ stmt.bind("payment_type", paymentInfo.getType());
+ stmt.bind("reference_id", paymentInfo.getReferenceId());
+ stmt.bind("payment_method_id", paymentInfo.getPaymentMethodId());
+ stmt.bind("payment_method", paymentInfo.getPaymentMethod());
+ stmt.bind("card_type", paymentInfo.getCardType());
+ stmt.bind("card_country", paymentInfo.getCardCountry());
+ stmt.bind("effective_dt", getDate(paymentInfo.getEffectiveDate()));
+ stmt.bind("created_dt", getDate(paymentInfo.getCreatedDate()));
+ stmt.bind("updated_dt", getDate(paymentInfo.getUpdatedDate()));
+ }
+ }
+
+ public static class PaymentInfoMapper implements ResultSetMapper<PaymentInfo> {
+
+ private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+ final Timestamp resultStamp = rs.getTimestamp(fieldName);
+ return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+ }
+
+ @Override
+ public PaymentInfo map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+
+ String paymentId = rs.getString("payment_id");
+ BigDecimal amount = rs.getBigDecimal("amount");
+ BigDecimal refundAmount = rs.getBigDecimal("refund_amount");
+ String paymentNumber = rs.getString("payment_number");
+ String bankIdentificationNumber = rs.getString("bank_identification_number");
+ String status = rs.getString("status");
+ String type = rs.getString("payment_type");
+ String referenceId = rs.getString("reference_id");
+ String paymentMethodId = rs.getString("payment_method_id");
+ String paymentMethod = rs.getString("payment_method");
+ String cardType = rs.getString("card_type");
+ String cardCountry = rs.getString("card_country");
+ DateTime effectiveDate = getDate(rs, "effective_dt");
+ DateTime createdDate = getDate(rs, "created_dt");
+ DateTime updatedDate = getDate(rs, "updated_dt");
+
+ return new PaymentInfo(paymentId,
+ amount,
+ refundAmount,
+ bankIdentificationNumber,
+ paymentNumber,
+ status,
+ type,
+ referenceId,
+ paymentMethodId,
+ paymentMethod,
+ cardType,
+ cardCountry,
+ effectiveDate,
+ createdDate,
+ updatedDate);
+ }
+ }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
new file mode 100644
index 0000000..1c8e9f3
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.invoice.api.Invoice;
+
+public class PaymentAttempt {
+ private final UUID paymentAttemptId;
+ private final UUID accountId;
+ private final UUID invoiceId;
+ private final BigDecimal paymentAttemptAmount;
+ private final DateTime paymentAttemptDate;
+ private final Integer retryCount;
+ private final DateTime nextRetryDate;
+
+ public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+ this(paymentAttemptId, invoice, null, null);
+ }
+
+ public PaymentAttempt(UUID paymentAttemptId, Invoice invoice, Integer retryCount, DateTime nextRetryDate) {
+ this.paymentAttemptId = paymentAttemptId;
+ this.accountId = invoice.getAccountId();
+ this.invoiceId = invoice.getId();
+ this.paymentAttemptAmount = invoice.getBalance();
+ this.paymentAttemptDate = new DateTime(DateTimeZone.UTC);
+ this.retryCount = retryCount;
+ this.nextRetryDate = nextRetryDate;
+ }
+
+ public UUID getPaymentAttemptId() {
+ return paymentAttemptId;
+ }
+
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ public UUID getInvoiceId() {
+ return invoiceId;
+ }
+
+ public BigDecimal getPaymentAttemptAmount() {
+ return paymentAttemptAmount;
+ }
+
+ public DateTime getPaymentAttemptDate() {
+ return paymentAttemptDate;
+ }
+
+ public Integer getRetryCount() {
+ return retryCount;
+ }
+
+ public DateTime getNextRetryDate() {
+ return nextRetryDate;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", accountId=" + accountId + ", invoiceId=" + invoiceId + ", paymentAttemptAmount=" + paymentAttemptAmount + ", paymentAttemptDate=" + paymentAttemptDate + ", retryCount=" + retryCount + ", nextRetryDate=" + nextRetryDate + "]";
+ }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/PaymentInfoRequest.java b/payment/src/main/java/com/ning/billing/payment/PaymentInfoRequest.java
new file mode 100644
index 0000000..5a999e2
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/PaymentInfoRequest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import java.util.UUID;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public class PaymentInfoRequest implements BusEvent {
+ private final UUID accountId;
+ private final String paymentId;
+
+ public PaymentInfoRequest(UUID accountId, String paymentId) {
+ this.accountId = accountId;
+ this.paymentId = paymentId;
+ }
+
+ public UUID getAccountId() {
+ return accountId;
+ }
+
+ public String getPaymentId() {
+ return paymentId;
+ }
+
+ @Override
+ public String toString() {
+ return "PaymentInfoRequest [accountId=" + accountId + ", paymentId=" + paymentId + "]";
+ }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
new file mode 100644
index 0000000..71c7e41
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPlugin.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.Either;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentMethodInfo;
+import com.ning.billing.payment.api.PaymentProviderAccount;
+
+public class NoOpPaymentProviderPlugin implements PaymentProviderPlugin {
+
+ @Override
+ public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
+ PaymentInfo payment = new PaymentInfo.Builder()
+ .setPaymentId(UUID.randomUUID().toString())
+ .setAmount(invoice.getBalance())
+ .setStatus("Processed")
+ .setCreatedDate(new DateTime(DateTimeZone.UTC))
+ .setEffectiveDate(new DateTime(DateTimeZone.UTC))
+ .setType("Electronic")
+ .build();
+ return Either.right(payment);
+ }
+
+ @Override
+ public Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId) {
+ return Either.right(null);
+ }
+
+ @Override
+ public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
+ return Either.left(new PaymentError("unsupported", "Account creation not supported in this plugin"));
+ }
+
+ @Override
+ public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+ return Either.right(null);
+ }
+
+ @Override
+ public Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+ return Either.right(null);
+ }
+
+ public void setDefaultPaymentMethodOnAccount(PaymentProviderAccount account, String paymentMethodId) {
+ // NO-OP
+ }
+
+ @Override
+ public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+ return Either.right(paymentMethod);
+ }
+
+ @Override
+ public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+ return Either.right(null);
+ }
+
+ @Override
+ public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+ return Either.right(null);
+ }
+
+ @Override
+ public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
+ return Either.right(Arrays.<PaymentMethodInfo>asList());
+ }
+
+ @Override
+ public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+ return Either.right(null);
+ }
+
+ @Override
+ public Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account) {
+ return Either.right(null);
+ }
+
+ @Override
+ public Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account) {
+ return Either.right(null);
+ }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java
new file mode 100644
index 0000000..a403274
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginModule.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class NoOpPaymentProviderPluginModule extends AbstractModule {
+ private final String instanceName;
+
+ public NoOpPaymentProviderPluginModule(String instanceName) {
+ this.instanceName = instanceName;
+ }
+
+ @Override
+ protected void configure() {
+ bind(NoOpPaymentProviderPlugin.class)
+ .annotatedWith(Names.named(instanceName))
+ .toProvider(new NoOpPaymentProviderPluginProvider(instanceName))
+ .asEagerSingleton();
+ }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java
new file mode 100644
index 0000000..5ba98b7
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/NoOpPaymentProviderPluginProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class NoOpPaymentProviderPluginProvider implements Provider<NoOpPaymentProviderPlugin> {
+ private PaymentProviderPluginRegistry registry;
+ private final String instanceName;
+
+ public NoOpPaymentProviderPluginProvider(String instanceName) {
+ this.instanceName = instanceName;
+ }
+
+ @Inject
+ public void setPaymentProviderPluginRegistry(PaymentProviderPluginRegistry registry) {
+ this.registry = registry;
+ }
+
+ @Override
+ public NoOpPaymentProviderPlugin get() {
+ NoOpPaymentProviderPlugin plugin = new NoOpPaymentProviderPlugin();
+
+ registry.register(plugin, instanceName);
+ return plugin;
+ }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
new file mode 100644
index 0000000..7a9ae71
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import java.util.List;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.Either;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentMethodInfo;
+import com.ning.billing.payment.api.PaymentProviderAccount;
+
+public interface PaymentProviderPlugin {
+ Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice);
+ Either<PaymentError, String> createPaymentProviderAccount(Account account);
+
+ Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId);
+ Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+ Either<PaymentError, Void> updatePaymentGateway(String accountKey);
+
+ Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId);
+ Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+ Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod);
+ Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
+ Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
+
+ Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account);
+ Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account);
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
new file mode 100644
index 0000000..fc9c149
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.google.inject.Inject;
+import com.ning.billing.payment.setup.PaymentConfig;
+
+public class PaymentProviderPluginRegistry {
+ private final String defaultPlugin;
+ private final Map<String, PaymentProviderPlugin> pluginsByName = new ConcurrentHashMap<String, PaymentProviderPlugin>();
+
+ @Inject
+ public PaymentProviderPluginRegistry(PaymentConfig config) {
+ this.defaultPlugin = config.getDefaultPaymentProvider();
+ }
+
+ public void register(PaymentProviderPlugin plugin, String name) {
+ pluginsByName.put(name.toLowerCase(), plugin);
+ }
+
+ public PaymentProviderPlugin getPlugin(String name) {
+ PaymentProviderPlugin plugin = pluginsByName.get(StringUtils.defaultIfEmpty(name, defaultPlugin).toLowerCase());
+
+ if (plugin == null) {
+ throw new IllegalArgumentException("No payment provider plugin is configured for " + name);
+ }
+
+ return plugin;
+ }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
new file mode 100644
index 0000000..892d424
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.invoice.api.InvoiceCreationNotification;
+import com.ning.billing.payment.api.Either;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.provider.PaymentProviderPlugin;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+public class RequestProcessor {
+ public static final String PAYMENT_PROVIDER_KEY = "paymentProvider";
+ private final AccountUserApi accountUserApi;
+ private final PaymentApi paymentApi;
+ private final PaymentProviderPluginRegistry pluginRegistry;
+ private final Bus eventBus;
+
+ private static final Logger log = LoggerFactory.getLogger(RequestProcessor.class);
+
+ @Inject
+ public RequestProcessor(AccountUserApi accountUserApi,
+ PaymentApi paymentApi,
+ PaymentProviderPluginRegistry pluginRegistry,
+ Bus eventBus) {
+ this.accountUserApi = accountUserApi;
+ this.paymentApi = paymentApi;
+ this.pluginRegistry = pluginRegistry;
+ this.eventBus = eventBus;
+ }
+
+ @Subscribe
+ public void receiveInvoice(InvoiceCreationNotification event) {
+ log.info("Received invoice creation notification for account {} and invoice {}", event.getAccountId(), event.getInvoiceId());
+ try {
+ final Account account = accountUserApi.getAccountById(event.getAccountId());
+
+ if (account == null) {
+ log.info("could not process invoice payment: could not find a valid account for event {}", event);
+ }
+ else {
+ List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account, Arrays.asList(event.getInvoiceId().toString()));
+ if (!results.isEmpty()) {
+ Either<PaymentError, PaymentInfo> result = results.get(0);
+ eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
+ }
+ }
+ }
+ catch (EventBusException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Subscribe
+ public void receivePaymentInfoRequest(PaymentInfoRequest paymentInfoRequest) throws EventBusException {
+ final Account account = accountUserApi.getAccountById(paymentInfoRequest.getAccountId());
+ if (account == null) {
+ log.info("could not process payment info request: could not find a valid account for event {}", paymentInfoRequest);
+ }
+ else {
+ final String paymentProviderName = account.getFieldValue(PAYMENT_PROVIDER_KEY);
+ final PaymentProviderPlugin plugin = pluginRegistry.getPlugin(paymentProviderName);
+
+ Either<PaymentError, PaymentInfo> result = plugin.getPaymentInfo(paymentInfoRequest.getPaymentId());
+
+ eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
+ }
+ }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/DefaultPaymentService.java b/payment/src/main/java/com/ning/billing/payment/setup/DefaultPaymentService.java
new file mode 100644
index 0000000..eae1baa
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/DefaultPaymentService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.setup;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.ning.billing.lifecycle.KillbillService;
+import com.ning.billing.lifecycle.LifecycleHandlerType;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentService;
+import com.ning.billing.util.bus.Bus;
+
+public class DefaultPaymentService implements PaymentService {
+ private static final Logger log = LoggerFactory.getLogger(DefaultPaymentService.class);
+
+ private static final String SERVICE_NAME = "payment-service";
+
+ private final RequestProcessor requestProcessor;
+ private final Bus eventBus;
+ private final PaymentApi api;
+
+ @Inject
+ public DefaultPaymentService(final RequestProcessor requestProcessor, final PaymentApi api, final Bus eventBus) {
+ this.requestProcessor = requestProcessor;
+ this.eventBus = eventBus;
+ this.api = api;
+ }
+
+ @Override
+ public String getName() {
+ return SERVICE_NAME;
+ }
+
+ @LifecycleHandlerType(LifecycleHandlerType.LifecycleLevel.REGISTER_EVENTS)
+ public void registerForNotifications() {
+ try {
+ eventBus.register(requestProcessor);
+ }
+ catch (Bus.EventBusException e) {
+ log.error("Unable to register with the EventBus!", e);
+ }
+ }
+
+ @Override
+ public PaymentApi getPaymentApi() {
+ return api;
+ }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
new file mode 100644
index 0000000..cdc5384
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.setup;
+
+import org.skife.config.Config;
+import org.skife.config.DefaultNull;
+
+public interface PaymentConfig {
+ @Config("killbill.payment.provider.default")
+ @DefaultNull
+ public String getDefaultPaymentProvider();
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
new file mode 100644
index 0000000..935b968
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.setup;
+
+import java.util.Properties;
+
+import org.skife.config.ConfigurationObjectFactory;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.payment.api.DefaultPaymentApi;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentService;
+import com.ning.billing.payment.dao.DefaultPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+
+public class PaymentModule extends AbstractModule {
+ private final Properties props;
+
+ public PaymentModule() {
+ this.props = System.getProperties();
+ }
+
+ public PaymentModule(Properties props) {
+ this.props = props;
+ }
+
+ protected void installPaymentDao() {
+ bind(PaymentDao.class).to(DefaultPaymentDao.class).asEagerSingleton();
+ }
+
+ protected void installPaymentProviderPlugins(PaymentConfig config) {
+ }
+
+ @Override
+ protected void configure() {
+ final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(props);
+ final PaymentConfig paymentConfig = factory.build(PaymentConfig.class);
+
+ bind(PaymentConfig.class).toInstance(paymentConfig);
+ bind(PaymentProviderPluginRegistry.class).asEagerSingleton();
+ bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
+ bind(RequestProcessor.class).asEagerSingleton();
+ bind(PaymentService.class).to(DefaultPaymentService.class).asEagerSingleton();
+ installPaymentProviderPlugins(paymentConfig);
+ installPaymentDao();
+ }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
new file mode 100644
index 0000000..3cbe802
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.util;
+
+import javax.annotation.Nullable;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.common.util.concurrent.AbstractFuture;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+public class EventBusFuture<T, V extends EventBusResponse<T>> extends AbstractFuture<V> {
+ public static <V, W extends EventBusRequest<V>, X extends EventBusResponse<V>> EventBusFuture<V, X> post(final Bus eventBus, final W event) throws EventBusException {
+ final EventBusFuture<V, X> responseFuture = new EventBusFuture<V, X>(eventBus, event.getId());
+
+ eventBus.register(responseFuture);
+ eventBus.post(event);
+ return responseFuture;
+ }
+
+ private final Bus eventBus;
+ private final T requestId;
+
+ private EventBusFuture(Bus eventBus, T requestId) {
+ this.eventBus = eventBus;
+ this.requestId = requestId;
+ }
+
+ @Subscribe
+ public void handleResponse(V response) {
+ if (requestId.equals(response.getRequestId())) {
+ set(response);
+ }
+ }
+
+ @Override
+ public boolean set(@Nullable V value) {
+ boolean result = super.set(value);
+
+ try {
+ eventBus.unregister(this);
+ }
+ catch (EventBusException ex) {
+ throw new RuntimeException(ex);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean setException(Throwable throwable) {
+ boolean result = super.setException(throwable);
+
+ try {
+ eventBus.unregister(this);
+ }
+ catch (EventBusException ex) {
+ throw new RuntimeException(ex);
+ }
+ return result;
+ }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
new file mode 100644
index 0000000..a895afc
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.util;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface EventBusRequest<T> extends BusEvent {
+ T getId();
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
new file mode 100644
index 0000000..ff5b62c
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.util;
+
+import com.ning.billing.util.bus.BusEvent;
+
+public interface EventBusResponse<T> extends BusEvent {
+ T getRequestId();
+}
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
new file mode 100644
index 0000000..a62c366
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -0,0 +1,71 @@
+group PaymentSqlDao;
+
+paymentAttemptFields(prefix) ::= <<
+ <prefix>payment_attempt_id,
+ <prefix>invoice_id,
+ <prefix>account_id,
+ <prefix>amount,
+ <prefix>currency,
+ <prefix>payment_id,
+ <prefix>payment_attempt_dt,
+ <prefix>invoice_dt,
+ <prefix>retry_count,
+ <prefix>next_retry_dt,
+ <prefix>created_dt,
+ <prefix>updated_dt
+>>
+
+paymentInfoFields(prefix) ::= <<
+ <prefix>payment_id,
+ <prefix>amount,
+ <prefix>refund_amount,
+ <prefix>bank_identification_number,
+ <prefix>payment_number,
+ <prefix>payment_type,
+ <prefix>status,
+ <prefix>reference_id,
+ <prefix>payment_method,
+ <prefix>card_type,
+ <prefix>card_country,
+ <prefix>effective_dt,
+ <prefix>created_dt,
+ <prefix>updated_dt
+>>
+
+insertPaymentAttempt() ::= <<
+ INSERT INTO payment_attempts (<paymentAttemptFields()>)
+ VALUES (:payment_attempt_id, :invoice_id, :account_id, :amount, :currency, :payment_id, :payment_attempt_dt, :invoice_dt, :retry_count, :next_retry_dt, :created_dt, :updated_dt);
+>>
+
+getPaymentAttemptForPaymentId() ::= <<
+ SELECT <paymentAttemptFields()>
+ FROM payment_attempts
+ WHERE payment_id = :payment_id
+>>
+
+getPaymentAttemptForInvoiceId() ::= <<
+ SELECT <paymentAttemptFields()>
+ FROM payment_attempts
+ WHERE invoice_id = :invoice_id
+>>
+
+updatePaymentAttemptWithPaymentId() ::= <<
+ UPDATE payment_attempts
+ SET payment_id = :payment_id,
+ updated_dt = NOW()
+ WHERE payment_attempt_id = :payment_attempt_id
+>>
+
+insertPaymentInfo() ::= <<
+ INSERT INTO payments (<paymentInfoFields()>)
+ VALUES (:payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number, :payment_type, :status, :reference_id, :payment_method, :card_type, :card_country, :effective_dt, :created_dt, :updated_dt);
+>>
+
+updatePaymentInfo() ::= <<
+ UPDATE payments
+ SET payment_method = :payment_method,
+ card_type = :card_type,
+ card_country = :card_country,
+ updated_dt = NOW()
+ WHERE payment_id = :payment_id
+>>
\ No newline at end of file
diff --git a/payment/src/main/resources/com/ning/billing/payment/ddl.sql b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
new file mode 100644
index 0000000..abbd8a6
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
@@ -0,0 +1,36 @@
+DROP TABLE IF EXISTS payment_attempts;
+CREATE TABLE payment_attempts (
+ payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
+ account_id char(36) COLLATE utf8_bin NOT NULL,
+ invoice_id char(36) COLLATE utf8_bin NOT NULL,
+ amount decimal(8,2),
+ currency char(3),
+ payment_attempt_dt datetime NOT NULL,
+ payment_id varchar(36) COLLATE utf8_bin,
+ retry_count tinyint,
+ next_retry_dt datetime,
+ invoice_dt datetime NOT NULL,
+ created_dt datetime NOT NULL,
+ updated_dt datetime NOT NULL,
+ PRIMARY KEY (payment_attempt_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+
+DROP TABLE IF EXISTS payments;
+CREATE TABLE payments (
+ payment_id varchar(36) COLLATE utf8_bin NOT NULL,
+ amount decimal(8,2),
+ refund_amount decimal(8,2),
+ payment_number varchar(36) COLLATE utf8_bin,
+ bank_identification_number varchar(36) COLLATE utf8_bin,
+ status varchar(20) COLLATE utf8_bin,
+ reference_id varchar(36) COLLATE utf8_bin,
+ payment_type varchar(20) COLLATE utf8_bin,
+ payment_method_id varchar(36) COLLATE utf8_bin,
+ payment_method varchar(20) COLLATE utf8_bin,
+ card_type varchar(20) COLLATE utf8_bin,
+ card_country varchar(50) COLLATE utf8_bin,
+ effective_dt datetime,
+ created_dt datetime NOT NULL,
+ updated_dt datetime NOT NULL,
+ PRIMARY KEY (payment_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
new file mode 100644
index 0000000..b9f761a
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+
+@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+@Test(groups = "fast")
+public class TestMockPaymentApi extends TestPaymentApi {
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
new file mode 100644
index 0000000..8c9d623
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.api;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.user.AccountBuilder;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.payment.TestHelper;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+public abstract class TestPaymentApi {
+ @Inject
+ private Bus eventBus;
+ @Inject
+ protected PaymentApi paymentApi;
+ @Inject
+ protected TestHelper testHelper;
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() throws EventBusException {
+ eventBus.start();
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() throws EventBusException {
+ eventBus.stop();
+ }
+
+ @Test(enabled=true)
+ public void testCreateCreditCardPayment() throws AccountApiException {
+ final DateTime now = new DateTime(DateTimeZone.UTC);
+ final Account account = testHelper.createTestCreditCardAccount();
+ final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+ final BigDecimal amount = new BigDecimal("10.00");
+ final UUID subscriptionId = UUID.randomUUID();
+
+ invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+ subscriptionId,
+ "test plan", "test phase",
+ now,
+ now.plusMonths(1),
+ amount,
+ new BigDecimal("1.0"),
+ Currency.USD));
+
+ List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()));
+
+ assertEquals(results.size(), 1);
+ assertTrue(results.get(0).isRight());
+
+ PaymentInfo paymentInfo = results.get(0).getRight();
+
+ assertNotNull(paymentInfo.getPaymentId());
+ assertTrue(paymentInfo.getAmount().compareTo(amount) == 0);
+ assertNotNull(paymentInfo.getPaymentNumber());
+ assertFalse(paymentInfo.getStatus().equals("Error"));
+
+ PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(paymentInfo.getPaymentId());
+ assertNotNull(paymentAttempt);
+ assertNotNull(paymentAttempt.getPaymentAttemptId());
+ assertEquals(paymentAttempt.getInvoiceId(), invoice.getId());
+ assertTrue(paymentAttempt.getAmount().compareTo(amount) == 0);
+ assertEquals(paymentAttempt.getCurrency(), Currency.USD);
+ assertEquals(paymentAttempt.getPaymentId(), paymentInfo.getPaymentId());
+ assertEquals(paymentAttempt.getPaymentAttemptDate().withMillisOfSecond(0).withSecondOfMinute(0), now.withMillisOfSecond(0).withSecondOfMinute(0));
+
+ }
+
+ private PaymentProviderAccount setupAccountWithPaymentMethod() throws AccountApiException {
+ final Account account = testHelper.createTestPayPalAccount();
+ paymentApi.createPaymentProviderAccount(account);
+
+ String accountKey = account.getExternalKey();
+
+ PaypalPaymentMethodInfo paymentMethod = new PaypalPaymentMethodInfo.Builder()
+ .setBaid("12345")
+ .setEmail(account.getEmail())
+ .setDefaultMethod(true)
+ .build();
+ Either<PaymentError, String> paymentMethodIdOrError = paymentApi.addPaymentMethod(accountKey, paymentMethod);
+
+ assertTrue(paymentMethodIdOrError.isRight());
+ assertNotNull(paymentMethodIdOrError.getRight());
+
+ Either<PaymentError, PaymentMethodInfo> paymentMethodInfoOrError = paymentApi.getPaymentMethod(accountKey, paymentMethodIdOrError.getRight());
+
+ assertTrue(paymentMethodInfoOrError.isRight());
+ assertNotNull(paymentMethodInfoOrError.getRight());
+
+ Either<PaymentError, PaymentProviderAccount> accountOrError = paymentApi.getPaymentProviderAccount(accountKey);
+
+ assertTrue(accountOrError.isRight());
+
+ return accountOrError.getRight();
+ }
+
+ @Test(enabled=true)
+ public void testCreatePaymentMethod() throws AccountApiException {
+ PaymentProviderAccount account = setupAccountWithPaymentMethod();
+ assertNotNull(account);
+ }
+
+ @Test(enabled=true)
+ public void testUpdatePaymentProviderAccountContact() throws AccountApiException {
+ final Account account = testHelper.createTestPayPalAccount();
+ paymentApi.createPaymentProviderAccount(account);
+
+ String newName = "Tester " + RandomStringUtils.randomAlphanumeric(10);
+ String newNumber = "888-888-" + RandomStringUtils.randomNumeric(4);
+
+ final Account accountToUpdate = new AccountBuilder(account.getId())
+ .name(newName)
+ .firstNameLength(newName.length())
+ .externalKey(account.getExternalKey())
+ .phone(newNumber)
+ .email(account.getEmail())
+ .currency(account.getCurrency())
+ .billingCycleDay(account.getBillCycleDay())
+ .build();
+
+ Either<PaymentError, Void> voidOrError = paymentApi.updatePaymentProviderAccountContact(accountToUpdate.getExternalKey());
+ assertTrue(voidOrError.isRight());
+ }
+
+ @Test(enabled=true)
+ public void testCannotDeleteDefaultPaymentMethod() throws AccountApiException {
+ PaymentProviderAccount account = setupAccountWithPaymentMethod();
+
+ Either<PaymentError, Void> errorOrVoid = paymentApi.deletePaymentMethod(account.getAccountKey(), account.getDefaultPaymentMethodId());
+
+ assertTrue(errorOrVoid.isLeft());
+ }
+
+ @Test(enabled=true)
+ public void testDeleteNonDefaultPaymentMethod() throws AccountApiException {
+ final Account account = testHelper.createTestPayPalAccount();
+ paymentApi.createPaymentProviderAccount(account);
+
+ String accountKey = account.getExternalKey();
+
+ PaypalPaymentMethodInfo paymentMethod1 = new PaypalPaymentMethodInfo.Builder().setDefaultMethod(false).setBaid("12345").setEmail(account.getEmail()).build();
+ Either<PaymentError, String> paymentMethodIdOrError1 = paymentApi.addPaymentMethod(accountKey, paymentMethod1);
+
+ assertTrue(paymentMethodIdOrError1.isRight());
+ assertNotNull(paymentMethodIdOrError1.getRight());
+
+ PaypalPaymentMethodInfo paymentMethod2 = new PaypalPaymentMethodInfo.Builder().setDefaultMethod(true).setBaid("12345").setEmail(account.getEmail()).build();
+
+ Either<PaymentError, String> paymentMethodIdOrError2 = paymentApi.addPaymentMethod(accountKey, paymentMethod2);
+
+ assertTrue(paymentMethodIdOrError2.isRight());
+ assertNotNull(paymentMethodIdOrError2.getRight());
+
+ Either<PaymentError, List<PaymentMethodInfo>> paymentMethodsOrError = paymentApi.getPaymentMethods(accountKey);
+
+ assertTrue(paymentMethodsOrError.isRight());
+
+ Either<PaymentError, Void> errorOrVoid1 = paymentApi.deletePaymentMethod(accountKey, paymentMethodIdOrError1.getRight());
+ Either<PaymentError, Void> errorOrVoid2 = paymentApi.deletePaymentMethod(accountKey, paymentMethodIdOrError2.getRight());
+
+ assertTrue(errorOrVoid1.isRight());
+ assertTrue(errorOrVoid2.isLeft());
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
new file mode 100644
index 0000000..bb83927
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public class MockPaymentDao implements PaymentDao {
+ private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+ private final Map<UUID, PaymentAttempt> paymentAttempts = new ConcurrentHashMap<UUID, PaymentAttempt>();
+
+ @Override
+ public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
+ for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
+ if (paymentId.equals(paymentAttempt.getPaymentId())) {
+ return paymentAttempt;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public PaymentAttempt createPaymentAttempt(Invoice invoice) {
+ PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+ paymentAttempts.put(paymentAttempt.getPaymentAttemptId(), paymentAttempt);
+ return paymentAttempt;
+ }
+
+ @Override
+ public void savePaymentInfo(PaymentInfo paymentInfo) {
+ payments.put(paymentInfo.getPaymentId(), paymentInfo);
+ }
+
+ @Override
+ public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId) {
+ PaymentAttempt existingPaymentAttempt = paymentAttempts.get(paymentAttemptId);
+
+ if (existingPaymentAttempt != null) {
+ paymentAttempts.put(existingPaymentAttempt.getPaymentAttemptId(),
+ existingPaymentAttempt.cloner().setPaymentId(paymentId).build());
+ }
+ }
+
+ @Override
+ public PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId) {
+ for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
+ if (invoiceId.equals(paymentAttempt.getInvoiceId())) {
+ return paymentAttempt;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void updatePaymentInfo(String paymentMethodType, String paymentId, String cardType, String cardCountry) {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
new file mode 100644
index 0000000..6c57c77
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDao.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.testng.annotations.Test;
+
+import com.ning.billing.payment.api.PaymentInfo;
+
+public abstract class TestPaymentDao {
+
+ protected PaymentDao dao;
+
+ @Test
+ public void testCreatePayment() {
+ PaymentInfo paymentInfo = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+ .setAmount(BigDecimal.TEN)
+ .setStatus("Processed")
+ .setBankIdentificationNumber("1234")
+ .setPaymentNumber("12345")
+ .setPaymentMethodId("12345")
+ .setReferenceId("12345")
+ .setType("Electronic")
+ .setCreatedDate(new DateTime(DateTimeZone.UTC))
+ .setUpdatedDate(new DateTime(DateTimeZone.UTC))
+ .setEffectiveDate(new DateTime(DateTimeZone.UTC))
+ .build();
+
+ dao.savePaymentInfo(paymentInfo);
+ }
+
+ @Test
+ public void testUpdatePayment() {
+ PaymentInfo paymentInfo = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+ .setAmount(BigDecimal.TEN)
+ .setStatus("Processed")
+ .setBankIdentificationNumber("1234")
+ .setPaymentNumber("12345")
+ .setPaymentMethodId("12345")
+ .setReferenceId("12345")
+ .setType("Electronic")
+ .setCreatedDate(new DateTime(DateTimeZone.UTC))
+ .setUpdatedDate(new DateTime(DateTimeZone.UTC))
+ .setEffectiveDate(new DateTime(DateTimeZone.UTC))
+ .build();
+
+ dao.savePaymentInfo(paymentInfo);
+
+ dao.updatePaymentInfo("CreditCard", paymentInfo.getPaymentId(), "Visa", "US");
+
+ }
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
new file mode 100644
index 0000000..da48c03
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithEmbeddedDb.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.dbi.MysqlTestingHelper;
+
+public class TestPaymentDaoWithEmbeddedDb
+{
+ @Test(enabled = true, groups = { "slow", "database" })
+ public class TestPaymentDaoWithEmbeddedDB extends TestPaymentDao {
+ private final MysqlTestingHelper helper = new MysqlTestingHelper();
+
+ @BeforeClass(alwaysRun = true)
+ public void startMysql() throws IOException {
+ final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+
+ helper.startMysql();
+ helper.initDb(paymentddl);
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void stopMysql() {
+ helper.stopMysql();
+ }
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() throws IOException {
+ dao = new DefaultPaymentDao(helper.getDBI());
+ }
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java
new file mode 100644
index 0000000..f5af240
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/TestPaymentDaoWithMock.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.dao;
+
+import java.io.IOException;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+@Test(groups = { "fast" })
+public class TestPaymentDaoWithMock extends TestPaymentDao {
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() throws IOException {
+ dao = new MockPaymentDao();
+ }
+}
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/MockPaymentInfoReceiver.java b/payment/src/test/java/com/ning/billing/payment/MockPaymentInfoReceiver.java
new file mode 100644
index 0000000..cf2494d
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockPaymentInfoReceiver.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public class MockPaymentInfoReceiver {
+ private final List<PaymentInfo> processedPayments = Collections.synchronizedList(new ArrayList<PaymentInfo>());
+ private final List<PaymentError> errors = Collections.synchronizedList(new ArrayList<PaymentError>());
+
+ @Subscribe
+ public void processedPayment(PaymentInfo paymentInfo) {
+ processedPayments.add(paymentInfo);
+ }
+
+ @Subscribe
+ public void processedPaymentError(PaymentError paymentError) {
+ errors.add(paymentError);
+ }
+
+ public List<PaymentInfo> getProcessedPayments() {
+ return new ArrayList<PaymentInfo>(processedPayments);
+ }
+
+ public List<PaymentError> getErrors() {
+ return new ArrayList<PaymentError>(errors);
+ }
+
+ public void clear() {
+ processedPayments.clear();
+ errors.clear();
+ }
+}
\ No newline at end of file
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
new file mode 100644
index 0000000..375bcfd
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.CreditCardPaymentMethodInfo;
+import com.ning.billing.payment.api.Either;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentMethodInfo;
+import com.ning.billing.payment.api.PaymentProviderAccount;
+import com.ning.billing.payment.api.PaypalPaymentMethodInfo;
+
+public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
+ private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+ private final Map<String, PaymentProviderAccount> accounts = new ConcurrentHashMap<String, PaymentProviderAccount>();
+ private final Map<String, PaymentMethodInfo> paymentMethods = new ConcurrentHashMap<String, PaymentMethodInfo>();
+
+ @Override
+ public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
+ PaymentInfo payment = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+ .setAmount(invoice.getBalance())
+ .setStatus("Processed")
+ .setBankIdentificationNumber("1234")
+ .setCreatedDate(new DateTime())
+ .setEffectiveDate(new DateTime())
+ .setPaymentNumber("12345")
+ .setReferenceId("12345")
+ .setType("Electronic")
+ .build();
+ payments.put(payment.getPaymentId(), payment);
+ return Either.right(payment);
+ }
+
+ @Override
+ public Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId) {
+ PaymentInfo payment = payments.get(paymentId);
+
+ if (payment == null) {
+ return Either.left(new PaymentError("notfound", "No payment found for id " + paymentId));
+ }
+ else {
+ return Either.right(payment);
+ }
+ }
+
+ @Override
+ public Either<PaymentError, String> createPaymentProviderAccount(Account account) {
+ if (account != null) {
+ String id = String.valueOf(RandomStringUtils.randomAlphanumeric(10));
+ accounts.put(account.getExternalKey(),
+ new PaymentProviderAccount.Builder().setAccountKey(account.getExternalKey())
+ .setId(id)
+ .build());
+
+ return Either.right(id);
+ }
+ else {
+ return Either.left(new PaymentError("unknown", "Did not get account to create payment provider account"));
+ }
+ }
+
+ @Override
+ public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+ if (accountKey != null) {
+ return Either.right(accounts.get(accountKey));
+ }
+ else {
+ return Either.left(new PaymentError("unknown", "Did not get account for accountKey " + accountKey));
+ }
+ }
+
+ @Override
+ public Either<PaymentError, String> addPaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+ if (paymentMethod != null) {
+ PaymentProviderAccount account = accounts.get(accountKey);
+
+ if (account != null && account.getId() != null) {
+ String existingDefaultMethod = account.getDefaultPaymentMethodId();
+
+ String paymentMethodId = RandomStringUtils.randomAlphanumeric(10);
+ boolean shouldBeDefault = Boolean.TRUE.equals(paymentMethod.getDefaultMethod()) || existingDefaultMethod == null;
+ PaymentMethodInfo realPaymentMethod = null;
+
+ if (paymentMethod instanceof PaypalPaymentMethodInfo) {
+ PaypalPaymentMethodInfo paypalPaymentMethod = (PaypalPaymentMethodInfo)paymentMethod;
+
+ realPaymentMethod = new PaypalPaymentMethodInfo.Builder(paypalPaymentMethod)
+ .setId(paymentMethodId)
+ .setAccountId(accountKey)
+ .setDefaultMethod(shouldBeDefault)
+ .setBaid(paypalPaymentMethod.getBaid())
+ .setEmail(paypalPaymentMethod.getEmail())
+ .build();
+ }
+ else if (paymentMethod instanceof CreditCardPaymentMethodInfo) {
+ CreditCardPaymentMethodInfo ccPaymentMethod = (CreditCardPaymentMethodInfo)paymentMethod;
+ realPaymentMethod = new CreditCardPaymentMethodInfo.Builder(ccPaymentMethod).setId(paymentMethodId).build();
+ }
+ if (realPaymentMethod == null) {
+ return Either.left(new PaymentError("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin"));
+ }
+ else {
+ if (shouldBeDefault) {
+ setDefaultPaymentMethodOnAccount(account, paymentMethodId);
+ }
+ paymentMethods.put(paymentMethodId, realPaymentMethod);
+ return Either.right(paymentMethodId);
+ }
+ }
+ else {
+ return Either.left(new PaymentError("noaccount", "Could not retrieve account for accountKey " + accountKey));
+ }
+ }
+ else {
+ return Either.left(new PaymentError("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey));
+ }
+ }
+
+ public void setDefaultPaymentMethodOnAccount(PaymentProviderAccount account, String paymentMethodId) {
+ if (paymentMethodId != null && account != null) {
+ accounts.put(account.getAccountKey(),
+ new PaymentProviderAccount.Builder()
+ .copyFrom(account)
+ .setDefaultPaymentMethod(paymentMethodId)
+ .build());
+ List<PaymentMethodInfo> paymentMethodsToUpdate = new ArrayList<PaymentMethodInfo>();
+ for (PaymentMethodInfo paymentMethod : paymentMethods.values()) {
+ if (account.getAccountKey().equals(paymentMethod.getAccountId()) && !paymentMethodId.equals(paymentMethod.getId())) {
+ if (paymentMethod instanceof PaypalPaymentMethodInfo) {
+ PaypalPaymentMethodInfo paypalPaymentMethod = (PaypalPaymentMethodInfo)paymentMethod;
+ paymentMethodsToUpdate.add(new PaypalPaymentMethodInfo.Builder(paypalPaymentMethod).setDefaultMethod(false).build());
+ }
+ else if (paymentMethod instanceof CreditCardPaymentMethodInfo) {
+ CreditCardPaymentMethodInfo ccPaymentMethod = (CreditCardPaymentMethodInfo)paymentMethod;
+ paymentMethodsToUpdate.add(new CreditCardPaymentMethodInfo.Builder(ccPaymentMethod).setDefaultMethod(false).build());
+ }
+ }
+ }
+ for (PaymentMethodInfo paymentMethod : paymentMethodsToUpdate) {
+ paymentMethods.put(paymentMethod.getId(), paymentMethod);
+ }
+ }
+ }
+
+ @Override
+ public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethod) {
+ if (paymentMethod != null) {
+ PaymentMethodInfo realPaymentMethod = null;
+
+ if (paymentMethod instanceof PaypalPaymentMethodInfo) {
+ PaypalPaymentMethodInfo paypalPaymentMethod = (PaypalPaymentMethodInfo)paymentMethod;
+ realPaymentMethod = new PaypalPaymentMethodInfo.Builder(paypalPaymentMethod).build();
+ }
+ else if (paymentMethod instanceof CreditCardPaymentMethodInfo) {
+ CreditCardPaymentMethodInfo ccPaymentMethod = (CreditCardPaymentMethodInfo)paymentMethod;
+ realPaymentMethod = new CreditCardPaymentMethodInfo.Builder(ccPaymentMethod).build();
+ }
+ if (realPaymentMethod == null) {
+ return Either.left(new PaymentError("unsupported", "Payment method " + paymentMethod.getType() + " not supported by the plugin"));
+ }
+ else {
+ paymentMethods.put(paymentMethod.getId(), paymentMethod);
+ return Either.right(realPaymentMethod);
+ }
+ }
+ else {
+ return Either.left(new PaymentError("unknown", "Could not create add payment method " + paymentMethod + " for " + accountKey));
+ }
+ }
+
+ @Override
+ public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+ PaymentMethodInfo paymentMethodInfo = paymentMethods.get(paymentMethodId);
+ if (paymentMethodInfo != null) {
+ if (Boolean.FALSE.equals(paymentMethodInfo.getDefaultMethod()) || paymentMethodInfo.getDefaultMethod() == null) {
+ if (paymentMethods.remove(paymentMethodId) == null) {
+ return Either.left(new PaymentError("unknown", "Did not get any result back"));
+ }
+ }
+ else {
+ return Either.left(new PaymentError("error", "Cannot delete default payment method"));
+ }
+ }
+ return Either.right(null);
+ }
+
+ @Override
+ public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+ if (paymentMethodId == null) {
+ return Either.left(new PaymentError("unknown", "Could not retrieve payment method for paymentMethodId " + paymentMethodId));
+ }
+
+ return Either.right(paymentMethods.get(paymentMethodId));
+ }
+
+ @Override
+ public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(final String accountKey) {
+
+ Collection<PaymentMethodInfo> filteredPaymentMethods = Collections2.filter(paymentMethods.values(), new Predicate<PaymentMethodInfo>() {
+ @Override
+ public boolean apply(PaymentMethodInfo input) {
+ return accountKey.equals(input.getAccountId());
+ }
+ });
+ List<PaymentMethodInfo> result = new ArrayList<PaymentMethodInfo>(filteredPaymentMethods);
+ return Either.right(result);
+ }
+
+ @Override
+ public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+ return Either.right(null);
+ }
+
+ @Override
+ public Either<PaymentError, Void> updatePaymentProviderAccountExistingContact(Account account) {
+ // nothing to do here
+ return Either.right(null);
+ }
+
+ @Override
+ public Either<PaymentError, Void> updatePaymentProviderAccountWithNewContact(Account account) {
+ // nothing to do here
+ return Either.right(null);
+ }
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.java
new file mode 100644
index 0000000..441fcf8
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class MockPaymentProviderPluginModule extends AbstractModule {
+ private final String instanceName;
+
+ public MockPaymentProviderPluginModule(String instanceName) {
+ this.instanceName = instanceName;
+ }
+
+ @Override
+ protected void configure() {
+ bind(MockPaymentProviderPlugin.class)
+ .annotatedWith(Names.named(instanceName))
+ .toProvider(new MockPaymentProviderPluginProvider(instanceName))
+ .asEagerSingleton();
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.java
new file mode 100644
index 0000000..1170007
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class MockPaymentProviderPluginProvider implements Provider<MockPaymentProviderPlugin> {
+ private PaymentProviderPluginRegistry registry;
+ private final String instanceName;
+
+ public MockPaymentProviderPluginProvider(String instanceName) {
+ this.instanceName = instanceName;
+ }
+
+ @Inject
+ public void setPaymentProviderPluginRegistry(PaymentProviderPluginRegistry registry) {
+ this.registry = registry;
+ }
+
+ @Override
+ public MockPaymentProviderPlugin get() {
+ MockPaymentProviderPlugin plugin = new MockPaymentProviderPlugin();
+
+ registry.register(plugin, instanceName);
+ return plugin;
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java
new file mode 100644
index 0000000..e051440
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.setup;
+
+import com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+
+public class PaymentModuleWithMocks extends PaymentModule {
+ @Override
+ protected void installPaymentDao() {
+ bind(PaymentDao.class).to(MockPaymentDao.class).asEagerSingleton();
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
new file mode 100644
index 0000000..9d9372c
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.setup;
+
+import com.ning.billing.util.bus.Bus;
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.collect.ImmutableMap;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.util.bus.InMemoryBus;
+
+public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
+ public PaymentTestModuleWithEmbeddedDb() {
+ super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock")));
+ }
+
+ @Override
+ protected void installPaymentProviderPlugins(PaymentConfig config) {
+ install(new MockPaymentProviderPluginModule("my-mock"));
+ }
+
+ @Override
+ protected void configure() {
+ super.configure();
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
new file mode 100644
index 0000000..144afa4
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.setup;
+
+import com.ning.billing.util.bus.InMemoryBus;
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.collect.ImmutableMap;
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+
+import com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.util.bus.Bus;
+
+public class PaymentTestModuleWithMocks extends PaymentModule {
+ public PaymentTestModuleWithMocks() {
+ super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock")));
+ }
+
+ @Override
+ protected void installPaymentDao() {
+ bind(PaymentDao.class).to(MockPaymentDao.class).asEagerSingleton();
+ }
+
+ @Override
+ protected void installPaymentProviderPlugins(PaymentConfig config) {
+ install(new MockPaymentProviderPluginModule("my-mock"));
+ }
+
+ @Override
+ protected void configure() {
+ super.configure();
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+ bind(MockAccountDao.class).asEagerSingleton();
+ bind(AccountDao.class).to(MockAccountDao.class);
+ bind(MockInvoiceDao.class).asEagerSingleton();
+ bind(InvoiceDao.class).to(MockInvoiceDao.class);
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
new file mode 100644
index 0000000..ac05da4
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.user.AccountBuilder;
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+
+public class TestHelper {
+ protected final AccountDao accountDao;
+ protected final InvoiceDao invoiceDao;
+
+ @Inject
+ public TestHelper(AccountDao accountDao, InvoiceDao invoiceDao) {
+ this.accountDao = accountDao;
+ this.invoiceDao = invoiceDao;
+ }
+
+ // These helper methods can be overridden in a plugin implementation
+ public Account createTestCreditCardAccount() throws AccountApiException {
+ final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
+ final String externalKey = RandomStringUtils.randomAlphanumeric(10);
+ final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
+ .firstNameLength(name.length())
+ .externalKey(externalKey)
+ .phone("123-456-7890")
+ .email("ccuser@example.com")
+ .currency(Currency.USD)
+ .billingCycleDay(1)
+ .build();
+ accountDao.create(account);
+ return account;
+ }
+
+ public Account createTestPayPalAccount() throws AccountApiException {
+ final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
+ final String externalKey = RandomStringUtils.randomAlphanumeric(10);
+ final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
+ .firstNameLength(name.length())
+ .externalKey(externalKey)
+ .phone("123-456-7890")
+ .email("ppuser@example.com")
+ .currency(Currency.USD)
+ .billingCycleDay(1)
+ .build();
+ accountDao.create(account);
+ return account;
+ }
+
+ public Invoice createTestInvoice(Account account,
+ DateTime targetDate,
+ Currency currency,
+ InvoiceItem... items) {
+ Invoice invoice = new DefaultInvoice(UUID.randomUUID(), account.getId(), new DateTime(), targetDate, currency);
+
+ for (InvoiceItem item : items) {
+ if (item instanceof RecurringInvoiceItem) {
+ RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+ invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+ recurringInvoiceItem.getSubscriptionId(),
+ recurringInvoiceItem.getPlanName(),
+ recurringInvoiceItem.getPhaseName(),
+ recurringInvoiceItem.getStartDate(),
+ recurringInvoiceItem.getEndDate(),
+ recurringInvoiceItem.getAmount(),
+ recurringInvoiceItem.getRate(),
+ recurringInvoiceItem.getCurrency()));
+ }
+ }
+ invoiceDao.create(invoice);
+ return invoice;
+ }
+
+ public Invoice createTestInvoice(Account account) {
+ final DateTime now = new DateTime(DateTimeZone.UTC);
+ final UUID subscriptionId = UUID.randomUUID();
+ final BigDecimal amount = new BigDecimal("10.00");
+ final InvoiceItem item = new RecurringInvoiceItem(null, subscriptionId, "test plan", "test phase", now, now.plusMonths(1), amount, new BigDecimal("1.0"), Currency.USD);
+
+ return createTestInvoice(account, now, Currency.USD, item);
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
new file mode 100644
index 0000000..80de67f
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.util.UUID;
+
+import com.ning.billing.invoice.api.InvoicePayment;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+@Test
+@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+public class TestNotifyInvoicePaymentApi {
+ @Inject
+ private Bus eventBus;
+ @Inject
+ private RequestProcessor invoiceProcessor;
+ @Inject
+ private InvoicePaymentApi invoicePaymentApi;
+ @Inject
+ private TestHelper testHelper;
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() throws EventBusException {
+ eventBus.start();
+ eventBus.register(invoiceProcessor);
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() throws EventBusException {
+ eventBus.unregister(invoiceProcessor);
+ eventBus.stop();
+ }
+
+ @Test
+ public void testNotifyPaymentSuccess() throws AccountApiException {
+ final Account account = testHelper.createTestCreditCardAccount();
+ final Invoice invoice = testHelper.createTestInvoice(account);
+
+ PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+
+ invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
+ invoice.getBalance(),
+ invoice.getCurrency(),
+ paymentAttempt.getPaymentAttemptId(),
+ paymentAttempt.getPaymentAttemptDate());
+
+ InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayment(paymentAttempt.getPaymentAttemptId());
+
+ assertNotNull(invoicePayment);
+ }
+
+ @Test
+ public void testNotifyPaymentFailure() throws AccountApiException {
+ final Account account = testHelper.createTestCreditCardAccount();
+ final Invoice invoice = testHelper.createTestInvoice(account);
+
+ PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+ invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
+ paymentAttempt.getPaymentAttemptId(),
+ paymentAttempt.getPaymentAttemptDate());
+
+ InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayment(paymentAttempt.getPaymentAttemptId());
+
+ assertNotNull(invoicePayment);
+ }
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
new file mode 100644
index 0000000..8768117
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.setup.PaymentTestModuleWithEmbeddedDb;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.clock.MockClockModule;
+
+public class TestPaymentInvoiceIntegration {
+ // create payment for received invoice and save it -- positive and negative
+ // check that notification for payment attempt is created
+ // check that invoice-payment is saved
+ @Inject
+ private Bus eventBus;
+ @Inject
+ private RequestProcessor invoiceProcessor;
+ @Inject
+ private InvoicePaymentApi invoicePaymentApi;
+ @Inject
+ private PaymentApi paymentApi;
+ @Inject
+ private TestHelper testHelper;
+
+ private MockPaymentInfoReceiver paymentInfoReceiver;
+
+ private IDBI dbi;
+ private MysqlTestingHelper helper;
+
+ @BeforeClass(alwaysRun = true)
+ public void startMysql() throws IOException {
+ final String accountddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+ final String utilddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ final String invoiceddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+ final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+
+ helper = new MysqlTestingHelper();
+ helper.startMysql();
+ helper.initDb(accountddl + "\n" + invoiceddl + "\n" + utilddl + "\n" + paymentddl);
+ dbi = helper.getDBI();
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void stopMysql() {
+ helper.stopMysql();
+ }
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() throws EventBusException {
+ Injector injector = Guice.createInjector(new PaymentTestModuleWithEmbeddedDb(),
+ new AccountModule(),
+ new InvoiceModuleWithMocks(),
+ new MockClockModule(),
+ new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(IDBI.class).toInstance(dbi);
+ }
+ });
+ injector.injectMembers(this);
+
+ paymentInfoReceiver = new MockPaymentInfoReceiver();
+
+ eventBus.start();
+ eventBus.register(invoiceProcessor);
+ eventBus.register(paymentInfoReceiver);
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() throws EventBusException {
+ eventBus.unregister(invoiceProcessor);
+ eventBus.unregister(paymentInfoReceiver);
+ eventBus.stop();
+ }
+
+ @Test
+ public void testInvoiceIntegration() throws Exception {
+ final Account account = testHelper.createTestCreditCardAccount();
+ final Invoice invoice = testHelper.createTestInvoice(account);
+
+ await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ List<PaymentInfo> processedPayments = paymentInfoReceiver.getProcessedPayments();
+ List<PaymentError> errors = paymentInfoReceiver.getErrors();
+
+ return processedPayments.size() == 1 || errors.size() == 1;
+ }
+ });
+
+ assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
+ assertTrue(paymentInfoReceiver.getErrors().isEmpty());
+
+ List<PaymentInfo> payments = paymentInfoReceiver.getProcessedPayments();
+ PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(payments.get(0).getPaymentId());
+ Assert.assertNotNull(paymentAttempt);
+
+ Invoice invoiceForPayment = invoicePaymentApi.getInvoiceForPaymentAttemptId(paymentAttempt.getPaymentAttemptId());
+
+ Assert.assertNotNull(invoiceForPayment);
+ Assert.assertEquals(invoiceForPayment.getId(), invoice.getId());
+ Assert.assertEquals(invoiceForPayment.getAccountId(), account.getId());
+
+ DateTime invoicePaymentAttempt = invoiceForPayment.getLastPaymentAttempt();
+ DateTime correctedDate = invoicePaymentAttempt.minus(invoicePaymentAttempt.millisOfSecond().get());
+ Assert.assertTrue(correctedDate.isEqual(paymentAttempt.getPaymentAttemptDate()));
+
+ Assert.assertEquals(invoiceForPayment.getBalance().floatValue(), new BigDecimal("0").floatValue());
+ Assert.assertEquals(invoiceForPayment.getAmountPaid().floatValue(), invoice.getAmountPaid().floatValue());
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
new file mode 100644
index 0000000..2c0aa13
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+public class TestPaymentProvider {
+ @Inject
+ private Bus eventBus;
+ @Inject
+ private RequestProcessor invoiceProcessor;
+ @Inject
+ private TestHelper testHelper;
+
+ private MockPaymentInfoReceiver paymentInfoReceiver;
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() throws EventBusException {
+ paymentInfoReceiver = new MockPaymentInfoReceiver();
+
+ eventBus.start();
+ eventBus.register(invoiceProcessor);
+ eventBus.register(paymentInfoReceiver);
+
+ assertTrue(true);
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() throws EventBusException {
+ eventBus.unregister(invoiceProcessor);
+ eventBus.unregister(paymentInfoReceiver);
+ eventBus.stop();
+
+ assertTrue(true);
+ }
+
+ @Test
+ public void testSimpleInvoice() throws Exception {
+ final Account account = testHelper.createTestCreditCardAccount();
+
+ testHelper.createTestInvoice(account);
+
+ await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ List<PaymentInfo> processedPayments = paymentInfoReceiver.getProcessedPayments();
+ List<PaymentError> errors = paymentInfoReceiver.getErrors();
+
+ return processedPayments.size() == 1 || errors.size() == 1;
+ }
+ });
+
+ assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
+ assertTrue(paymentInfoReceiver.getErrors().isEmpty());
+
+ final PaymentInfo paymentInfo = paymentInfoReceiver.getProcessedPayments().get(0);
+ final PaymentInfoRequest paymentInfoRequest = new PaymentInfoRequest(account.getId(), paymentInfo.getPaymentId());
+
+ paymentInfoReceiver.clear();
+ eventBus.post(paymentInfoRequest);
+ await().atMost(5, MINUTES).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ List<PaymentInfo> processedPayments = paymentInfoReceiver.getProcessedPayments();
+ List<PaymentError> errors = paymentInfoReceiver.getErrors();
+
+ return processedPayments.size() == 1 || errors.size() == 1;
+ }
+ });
+
+ assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
+ assertTrue(paymentInfoReceiver.getErrors().isEmpty());
+ assertEquals(paymentInfoReceiver.getProcessedPayments().get(0), paymentInfo);
+ }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
new file mode 100644
index 0000000..0e76771
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.util;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.UUID;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
+
+@Test
+public class TestSyncWaitOnEventBus {
+ private static final class TestEvent implements EventBusRequest<UUID> {
+ private final UUID id;
+ private final String msg;
+
+ public TestEvent(UUID id, String msg) {
+ this.id = id;
+ this.msg = msg;
+ }
+
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+ }
+
+ private static final class TestResponse implements EventBusResponse<UUID> {
+ private final UUID id;
+ private final String msg;
+
+ public TestResponse(UUID id, String msg) {
+ this.id = id;
+ this.msg = msg;
+ }
+
+ @Override
+ public UUID getRequestId() {
+ return id;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+ }
+
+ private Bus eventBus;
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp() throws Exception {
+ eventBus = new InMemoryBus();
+ eventBus.start();
+ eventBus.register(new Object() {
+ @Subscribe
+ public void handleEvent(TestEvent event) throws Exception {
+ Thread.sleep(100);
+ eventBus.post(new TestResponse(event.getId(), event.getMsg()));
+ }
+ });
+ }
+
+ @AfterMethod(alwaysRun = true)
+ public void tearDown() {
+ eventBus.stop();
+ }
+
+ public void test() throws Exception {
+ final TestEvent event = new TestEvent(UUID.randomUUID(), "Hello World!");
+
+ Future<TestResponse> future = EventBusFuture.post(eventBus, event);
+ TestResponse response = future.get(1, TimeUnit.SECONDS);
+
+ assertEquals(response.getRequestId(), event.getId());
+ assertEquals(response.getMsg(), event.getMsg());
+ }
+}
payment/src/test/resources/log4j.xml 30(+30 -0)
diff --git a/payment/src/test/resources/log4j.xml b/payment/src/test/resources/log4j.xml
new file mode 100644
index 0000000..82b5a26
--- /dev/null
+++ b/payment/src/test/resources/log4j.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ Copyright 2010 Ning, Inc.
+ ~
+ ~ Ning licenses this file to you under the Apache License, version 2.0
+ ~ (the "License"); you may not use this file except in compliance with the
+ ~ License. You may obtain a copy of the License at:
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ ~ License for the specific language governing permissions and limitations
+ ~ under the License.
+ -->
+<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
+<log4j:configuration debug="false"
+ xmlns:log4j='http://jakarta.apache.org/log4j/'>
+ <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%p %d{ISO8601} %t %c %m%n"/>
+ </layout>
+ </appender>
+
+ <root>
+ <level value="info"/>
+ <appender-ref ref="CONSOLE"/>
+ </root>
+</log4j:configuration>
pom.xml 100(+88 -12)
diff --git a/pom.xml b/pom.xml
index 8c8fbb3..179d4c3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
<packaging>pom</packaging>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
<url>http://github.com/ning/killbill</url>
@@ -29,7 +29,7 @@
</license>
</licenses>
<scm>
- <connection>scm:git:git://github.com/stephane/killbill.git</connection>
+ <connection>scm:git:git://github.com/ning/killbill.git</connection>
<developerConnection>scm:git:git@github.com:ning/killbill.git</developerConnection>
<url>http://github.com/ning/killbill/tree/master</url>
</scm>
@@ -56,6 +56,12 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
+ <artifactId>killbill-api</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
<artifactId>killbill-account</artifactId>
<version>${project.version}</version>
</dependency>
@@ -64,7 +70,6 @@
<artifactId>killbill-account</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
- <scope>test</scope>
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
@@ -73,6 +78,23 @@
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
+ <artifactId>killbill-entitlement</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-payment</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-payment</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
<artifactId>killbill-catalog</artifactId>
<version>${project.version}</version>
</dependency>
@@ -81,14 +103,17 @@
<artifactId>killbill-catalog</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
- <scope>test</scope>
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
- <artifactId>killbill-util</artifactId>
+ <artifactId>killbill-invoice</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-invoice</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
- <scope>test</scope>
</dependency>
<dependency>
<groupId>com.ning.billing</groupId>
@@ -96,19 +121,25 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>com.ning.billing</groupId>
+ <artifactId>killbill-util</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
- <version>1.9.0</version>
+ <version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
- <version>1.9.0</version>
+ <version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
- <version>1.9.0</version>
+ <version>1.9.2</version>
</dependency>
<dependency>
<groupId>com.jolbox</groupId>
@@ -134,9 +165,15 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>com.google.inject.extensions</groupId>
+ <artifactId>guice-multibindings</artifactId>
+ <version>3.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>com.mogwee</groupId>
<artifactId>mogwee-executors</artifactId>
- <version>1.1.0</version>
+ <version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
@@ -176,6 +213,11 @@
<version>2.5</version>
</dependency>
<dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ <version>3.2.1</version>
+ </dependency>
+ <dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.0</version>
@@ -201,7 +243,7 @@
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi</artifactId>
- <version>2.27</version>
+ <version>2.31.2</version>
</dependency>
<dependency>
<groupId>org.skife.config</groupId>
@@ -231,7 +273,13 @@
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
- <version>6.0</version>
+ <version>6.3.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jayway.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <version>1.3.3</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -284,6 +332,18 @@
</configuration>
</plugin>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.2</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.3</version>
@@ -321,6 +381,7 @@
<exclude>**/.project</exclude>
<exclude>.git/**</exclude>
<exclude>.gitignore</exclude>
+ <exclude>ignore/**</exclude>
<exclude>API.txt</exclude>
<exclude>RELEASE.sh</exclude>
<exclude>deploy.sh</exclude>
@@ -391,6 +452,21 @@
<attachClasses>true</attachClasses>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.1.2</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>jar</goal>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
<profiles>
util/pom.xml 23(+21 -2)
diff --git a/util/pom.xml b/util/pom.xml
index 74d243c..68648ef 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.2-SNAPSHOT</version>
+ <version>0.1.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>
@@ -55,6 +55,15 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.antlr</groupId>
+ <artifactId>stringtemplate</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
@@ -62,7 +71,12 @@
<dependency>
<groupId>com.mysql</groupId>
<artifactId>management</artifactId>
- <version>5.0.11</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.mysql</groupId>
+ <artifactId>management-dbfiles</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
@@ -83,6 +97,11 @@
<artifactId>management-dbfiles</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.jayway.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
index 680a826..14c123a 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/FieldStoreDao.java
@@ -33,6 +33,7 @@ import org.skife.jdbi.v2.sqlobject.BinderFactory;
import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
import org.skife.jdbi.v2.sqlobject.SqlBatch;
import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
@@ -42,10 +43,11 @@ import com.ning.billing.util.entity.EntityCollectionDao;
@ExternalizedSqlViaStringTemplate3
@RegisterMapper(FieldStoreDao.CustomFieldMapper.class)
-public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transmogrifier {
+public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transactional<FieldStoreDao>, Transmogrifier {
+
@Override
- @SqlBatch
- public void save(@Bind("objectId") final String objectId,
+ @SqlBatch(transactional=false)
+ public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
@Bind("objectType") final String objectType,
@CustomFieldBinder final List<CustomField> entities);
@@ -65,8 +67,10 @@ public interface FieldStoreDao extends EntityCollectionDao<CustomField>, Transmo
@Target({ElementType.PARAMETER})
public @interface CustomFieldBinder {
public static class CustomFieldBinderFactory implements BinderFactory {
+ @Override
public Binder build(Annotation annotation) {
return new Binder<CustomFieldBinder, CustomField>() {
+ @Override
public void bind(SQLStatement q, CustomFieldBinder bind, CustomField customField) {
q.bind("id", customField.getId().toString());
q.bind("fieldName", customField.getName());
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
index 8134203..11d149c 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityCollectionDao.java
@@ -26,8 +26,9 @@ import java.util.List;
* @param <T>
*/
public interface EntityCollectionDao<T extends Entity> {
- @SqlBatch
- public void save(@Bind("objectId") final String objectId,
+
+ @SqlBatch(transactional=false)
+ public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
@Bind("objectType") final String objectType,
@BindBean final List<T> entities);
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
index 3b5dd46..3e68158 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
@@ -16,20 +16,21 @@
package com.ning.billing.util.entity;
+import java.util.List;
+
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
-import org.skife.jdbi.v2.sqlobject.SqlBatch;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import java.util.List;
+import com.ning.billing.account.api.AccountApiException;
public interface EntityDao<T extends Entity> {
@SqlUpdate
- public void create(@BindBean T entity);
+ public void create(@BindBean final T entity) throws AccountApiException;
@SqlUpdate
- public void update(@BindBean T entity);
+ public void update(@BindBean final T entity) throws AccountApiException;
@SqlQuery
public T getById(@Bind("id") final String id);
@@ -39,4 +40,7 @@ public interface EntityDao<T extends Entity> {
@SqlUpdate
public void test();
+
+ @SqlUpdate
+ public void deleteByKey(String key) throws AccountApiException;
}
diff --git a/util/src/main/java/com/ning/billing/util/globallocker/GlobalLock.java b/util/src/main/java/com/ning/billing/util/globallocker/GlobalLock.java
new file mode 100644
index 0000000..12ebc0d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globallocker/GlobalLock.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.globallocker;
+
+public interface GlobalLock
+{
+ public void release();
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/globallocker/GlobalLocker.java b/util/src/main/java/com/ning/billing/util/globallocker/GlobalLocker.java
new file mode 100644
index 0000000..5312e09
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globallocker/GlobalLocker.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.globallocker;
+
+public interface GlobalLocker {
+
+ GlobalLock lockWithNumberOfTries(final LockerService service, final String lockKey, final int retry);
+ Boolean isFree(final LockerService service, final String lockKey);
+
+ public enum LockerService {
+
+ // Only service needing global lock
+ INVOICE("invoice");
+
+ private final String svcName;
+
+ LockerService(String svcName) {
+ this.svcName = svcName;
+ }
+
+ @Override
+ public String toString() {
+ return svcName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLocker.java b/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLocker.java
new file mode 100644
index 0000000..f9de358
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLocker.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.globallocker;
+
+import com.google.inject.Inject;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MySqlGlobalLocker implements GlobalLocker {
+
+ private final static Logger logger = LoggerFactory.getLogger(MySqlGlobalLocker.class);
+
+ private final static long DEFAULT_TIMEOUT = 3L; // 3 seconds
+
+ private final IDBI dbi;
+ private long timeout;
+
+ @Inject
+ public MySqlGlobalLocker(IDBI dbi) {
+ this.dbi = dbi;
+ this.timeout = DEFAULT_TIMEOUT;
+ }
+
+ public void setTimeout(final long timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public GlobalLock lockWithNumberOfTries(final LockerService service, final String lockKey, final int retry) {
+
+ final String lockName = getLockName(service, lockKey);
+ int tries_left = retry;
+ while (tries_left-- > 0) {
+ GlobalLock lock = lock(lockName);
+ if (lock != null) {
+ return lock;
+ }
+ }
+ logger.error(String.format("Failed to acquire lock %s for service %s after %d retry", lockKey, service, retry));
+ throw new LockFailedException();
+ }
+
+ private GlobalLock lock(final String lockName) throws LockFailedException {
+
+ final Handle h = dbi.open();
+ final MySqlGlobalLockerDao dao = h.attach(MySqlGlobalLockerDao.class);
+
+ final boolean obtained = dao.lock(lockName, timeout);
+ if (obtained) {
+ return new GlobalLock() {
+ @Override
+ public void release() {
+ try {
+ dao.releaseLock(lockName);
+ }
+ finally {
+ if (h != null) {
+ h.close();
+ }
+ }
+ }
+ };
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Boolean isFree(final LockerService service, final String lockKey) {
+
+ final String lockName = getLockName(service, lockKey);
+ final Handle h = dbi.open();
+ try {
+ final MySqlGlobalLockerDao dao = h.attach(MySqlGlobalLockerDao.class);
+ return dao.isFree(lockName);
+ } finally {
+ if (h != null) {
+ h.close();
+ }
+ }
+ }
+
+ private String getLockName(final LockerService service, final String lockKey) {
+ StringBuilder tmp = new StringBuilder()
+ .append(service.toString())
+ .append("-")
+ .append(lockKey);
+ return tmp.toString();
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLockerDao.java b/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLockerDao.java
new file mode 100644
index 0000000..876f6e1
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/globallocker/MySqlGlobalLockerDao.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.globallocker;
+
+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;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@RegisterMapper(MySqlGlobalLockerDao.LockMapper.class)
+public interface MySqlGlobalLockerDao {
+
+ @SqlQuery("Select GET_LOCK(:lockName, :timeout);")
+ public Boolean lock(@Bind("lockName") final String lockName, @Bind("timeout") final long timeout);
+
+ @SqlQuery("Select RELEASE_LOCK(:lockName);")
+ public Boolean releaseLock(@Bind("lockName") final String lockName);
+
+ @SqlQuery("Select IS_FREE_LOCK(:lockName);")
+ public Boolean isFree(@Bind("lockName") final String lockName);
+
+ class LockMapper implements ResultSetMapper<Boolean> {
+ @Override
+ public Boolean map(int index, ResultSet r, StatementContext ctx) throws SQLException {
+ return (r.getByte(1) == 1);
+ }
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/BusModule.java b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
new file mode 100644
index 0000000..d6f7a37
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/glue/BusModule.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.InMemoryBus;
+
+public class BusModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(BusService.class).to(DefaultBusService.class);
+ bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+
+ }
+
+}
diff --git a/util/src/main/java/com/ning/billing/util/glue/ClockModule.java b/util/src/main/java/com/ning/billing/util/glue/ClockModule.java
index 187a500..e7c7c3f 100644
--- a/util/src/main/java/com/ning/billing/util/glue/ClockModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/ClockModule.java
@@ -22,8 +22,8 @@ import com.ning.billing.util.clock.DefaultClock;
public class ClockModule extends AbstractModule {
- @Override
- protected void configure() {
- bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
- }
+ @Override
+ protected void configure() {
+ bind(Clock.class).to(DefaultClock.class).asEagerSingleton();
+ }
}
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java b/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java
index 31fb306..560a09f 100644
--- a/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java
+++ b/util/src/main/java/com/ning/billing/util/glue/TagDescriptionDaoProvider.java
@@ -18,10 +18,10 @@ package com.ning.billing.util.glue;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
import org.skife.jdbi.v2.IDBI;
-public class TagDescriptionDaoProvider implements Provider<TagDescriptionDao>
+public class TagDescriptionDaoProvider implements Provider<TagDefinitionSqlDao>
{
private final IDBI dbi;
@@ -32,8 +32,8 @@ public class TagDescriptionDaoProvider implements Provider<TagDescriptionDao>
}
@Override
- public TagDescriptionDao get()
+ public TagDefinitionSqlDao get()
{
- return dbi.onDemand(TagDescriptionDao.class);
+ return dbi.onDemand(TagDefinitionSqlDao.class);
}
}
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java b/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java
index 2c612e6..aa0f080 100644
--- a/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java
+++ b/util/src/main/java/com/ning/billing/util/glue/TagStoreDaoProvider.java
@@ -18,10 +18,10 @@ package com.ning.billing.util.glue;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import com.ning.billing.util.tag.dao.TagStoreDao;
+import com.ning.billing.util.tag.dao.TagStoreSqlDao;
import org.skife.jdbi.v2.IDBI;
-public class TagStoreDaoProvider implements Provider<TagStoreDao>
+public class TagStoreDaoProvider implements Provider<TagStoreSqlDao>
{
private final IDBI dbi;
@@ -32,8 +32,8 @@ public class TagStoreDaoProvider implements Provider<TagStoreDao>
}
@Override
- public TagStoreDao get()
+ public TagStoreSqlDao get()
{
- return dbi.onDemand(TagStoreDao.class);
+ return dbi.onDemand(TagStoreSqlDao.class);
}
}
diff --git a/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java b/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
index ae14782..10651be 100644
--- a/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/TagStoreModule.java
@@ -17,15 +17,24 @@
package com.ning.billing.util.glue;
import com.google.inject.AbstractModule;
-import com.ning.billing.util.tag.dao.TagDescriptionDao;
-import com.ning.billing.util.tag.dao.TagStoreDao;
+import com.ning.billing.util.api.TagDefinitionUserApi;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.tag.api.DefaultTagDefinitionUserApi;
+import com.ning.billing.util.tag.dao.DefaultTagDefinitionDao;
+import com.ning.billing.util.tag.dao.TagDefinitionDao;
+import com.ning.billing.util.tag.dao.TagDefinitionSqlDao;
+import com.ning.billing.util.tag.dao.TagStoreSqlDao;
public class TagStoreModule extends AbstractModule
{
@Override
protected void configure()
{
- bind(TagDescriptionDao.class).toProvider(TagDescriptionDaoProvider.class).asEagerSingleton();
- bind(TagStoreDao.class).toProvider(TagStoreDaoProvider.class).asEagerSingleton();
+ bind(TagDefinitionSqlDao.class).toProvider(TagDescriptionDaoProvider.class).asEagerSingleton();
+ bind(TagDefinitionDao.class).to(DefaultTagDefinitionDao.class).asEagerSingleton();
+ bind(TagStoreSqlDao.class).toProvider(TagStoreDaoProvider.class).asEagerSingleton();
+ bind(TagDefinitionUserApi.class).to(DefaultTagDefinitionUserApi.class).asEagerSingleton();
}
+
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
index 818d831..2f511ec 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
@@ -49,13 +49,13 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
//
@SqlQuery
@Mapper(NotificationSqlMapper.class)
- public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max);
+ public List<Notification> getReadyNotifications(@Bind("now") Date now, @Bind("max") int max, @Bind("queue_name") String queueName);
@SqlUpdate
- public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("notification_id") String eventId, @Bind("now") Date now);
+ public int claimNotification(@Bind("owner") String owner, @Bind("next_available") Date nextAvailable, @Bind("id") long id, @Bind("now") Date now);
@SqlUpdate
- public void clearNotification(@Bind("notification_id") String eventId, @Bind("owner") String owner);
+ public void clearNotification(@Bind("id") long id, @Bind("owner") String owner);
@SqlUpdate
public void insertNotification(@Bind(binder = NotificationSqlDaoBinder.class) Notification evt);
@@ -71,12 +71,13 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
@Override
public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, Notification evt) {
- stmt.bind("notification_id", evt.getId().toString());
+ stmt.bind("notification_id", evt.getUUID().toString());
stmt.bind("created_dt", getDate(new DateTime()));
stmt.bind("notification_key", evt.getNotificationKey());
stmt.bind("effective_dt", getDate(evt.getEffectiveDate()));
+ stmt.bind("queue_name", evt.getQueueName());
stmt.bind("processing_available_dt", getDate(evt.getNextAvailableDate()));
- stmt.bind("processing_owner", (String) null);
+ stmt.bind("processing_owner", evt.getOwner());
stmt.bind("processing_state", NotificationLifecycleState.AVAILABLE.toString());
}
}
@@ -93,14 +94,16 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
public Notification map(int index, ResultSet r, StatementContext ctx)
throws SQLException {
- final UUID id = UUID.fromString(r.getString("notification_id"));
+ final long id = r.getLong("id");
+ final UUID uuid = UUID.fromString(r.getString("notification_id"));
final String notificationKey = r.getString("notification_key");
+ final String queueName = r.getString("queue_name");
final DateTime effectiveDate = getDate(r, "effective_dt");
final DateTime nextAvailableDate = getDate(r, "processing_available_dt");
final String processingOwner = r.getString("processing_owner");
final NotificationLifecycleState processingState = NotificationLifecycleState.valueOf(r.getString("processing_state"));
- return new DefaultNotification(id, processingOwner, nextAvailableDate,
+ return new DefaultNotification(id, uuid, processingOwner, queueName, nextAvailableDate,
processingState, notificationKey, effectiveDate);
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
index 2946e13..26e6c4e 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotification.java
@@ -22,33 +22,41 @@ import org.joda.time.DateTime;
public class DefaultNotification implements Notification {
- private final UUID id;
+ private final long id;
+ private final UUID uuid;
private final String owner;
+ private final String queueName;
private final DateTime nextAvailableDate;
private final NotificationLifecycleState lifecycleState;
private final String notificationKey;
private final DateTime effectiveDate;
- public DefaultNotification(UUID id, String owner, DateTime nextAvailableDate,
+ public DefaultNotification(long id, UUID uuid, String owner, String queueName, DateTime nextAvailableDate,
NotificationLifecycleState lifecycleState,
String notificationKey, DateTime effectiveDate) {
super();
this.id = id;
+ this.uuid = uuid;
this.owner = owner;
+ this.queueName = queueName;
this.nextAvailableDate = nextAvailableDate;
this.lifecycleState = lifecycleState;
this.notificationKey = notificationKey;
this.effectiveDate = effectiveDate;
}
- public DefaultNotification(String notificationKey, DateTime effectiveDate) {
- this(UUID.randomUUID(), null, null, NotificationLifecycleState.AVAILABLE, notificationKey, effectiveDate);
+ @Override
+ public long getId() {
+ return id;
}
+ public DefaultNotification(String queueName, String notificationKey, DateTime effectiveDate) {
+ this(-1L, UUID.randomUUID(), null, queueName, null, NotificationLifecycleState.AVAILABLE, notificationKey, effectiveDate);
+ }
@Override
- public UUID getId() {
- return id;
+ public UUID getUUID() {
+ return uuid;
}
@Override
@@ -94,4 +102,10 @@ public class DefaultNotification implements Notification {
public DateTime getEffectiveDate() {
return effectiveDate;
}
+
+ @Override
+ public String getQueueName() {
+ return queueName;
+ }
+
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
index 80f7385..8e2aaf8 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
@@ -17,14 +17,10 @@
package com.ning.billing.util.notificationq;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Date;
import java.util.List;
-
import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import com.ning.billing.util.clock.Clock;
@@ -35,49 +31,49 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
protected final NotificationSqlDao dao;
- public DefaultNotificationQueue(final DBI dbi, final Clock clock, final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
+ public DefaultNotificationQueue(final IDBI dbi, final Clock clock, final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
+
super(clock, svcName, queueName, handler, config);
this.dao = dbi.onDemand(NotificationSqlDao.class);
}
@Override
- protected void doProcessEvents(int sequenceId) {
+ protected int doProcessEvents(final int sequenceId) {
+
+ logDebug("ENTER doProcessEvents");
List<Notification> notifications = getReadyNotifications(sequenceId);
- for (Notification cur : notifications) {
+ if (notifications.size() == 0) {
+ logDebug("EXIT doProcessEvents");
+ return 0;
+ }
+
+ logDebug("START processing %d events at time %s", notifications.size(), clock.getUTCNow().toDate());
+
+ int result = 0;
+ for (final Notification cur : notifications) {
nbProcessedEvents.incrementAndGet();
- handler.handleReadyNotification(cur.getNotificationKey());
+ logDebug("handling notification %s, key = %s for time %s",
+ cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
+ handler.handleReadyNotification(cur.getNotificationKey(), cur.getEffectiveDate());
+ result++;
+ clearNotification(cur);
+ logDebug("done handling notification %s, key = %s for time %s",
+ cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
}
- // If anything happens before we get to clear those notifications, somebody else will pick them up
- clearNotifications(notifications);
+ return result;
}
@Override
public void recordFutureNotificationFromTransaction(final Transmogrifier transactionalDao,
final DateTime futureNotificationTime, final NotificationKey notificationKey) {
NotificationSqlDao transactionalNotificationDao = transactionalDao.become(NotificationSqlDao.class);
- Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+ Notification notification = new DefaultNotification(getFullQName(), notificationKey.toString(), futureNotificationTime);
transactionalNotificationDao.insertNotification(notification);
}
- private void clearNotifications(final Collection<Notification> cleared) {
-
- log.debug(String.format("NotificationQueue %s clearEventsReady START cleared size = %d",
- getFullQName(),
- cleared.size()));
-
- dao.inTransaction(new Transaction<Void, NotificationSqlDao>() {
-
- @Override
- public Void inTransaction(NotificationSqlDao transactional,
- TransactionStatus status) throws Exception {
- for (Notification cur : cleared) {
- transactional.clearNotification(cur.getId().toString(), hostname);
- log.debug(String.format("NotificationQueue %s cleared events %s", getFullQName(), cur.getId()));
- }
- return null;
- }
- });
+ private void clearNotification(final Notification cleared) {
+ dao.clearNotification(cleared.getId(), hostname);
}
private List<Notification> getReadyNotifications(final int seqId) {
@@ -85,35 +81,34 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
final Date now = clock.getUTCNow().toDate();
final Date nextAvailable = clock.getUTCNow().plus(config.getDaoClaimTimeMs()).toDate();
- log.debug(String.format("NotificationQueue %s getEventsReady START effectiveNow = %s", getFullQName(), now));
-
- List<Notification> result = dao.inTransaction(new Transaction<List<Notification>, NotificationSqlDao>() {
-
- @Override
- public List<Notification> inTransaction(NotificationSqlDao transactionalDao,
- TransactionStatus status) throws Exception {
-
- List<Notification> claimedNotifications = new ArrayList<Notification>();
- List<Notification> input = transactionalDao.getReadyNotifications(now, config.getDaoMaxReadyEvents());
- for (Notification cur : input) {
- final boolean claimed = (transactionalDao.claimNotification(hostname, nextAvailable, cur.getId().toString(), now) == 1);
- if (claimed) {
- claimedNotifications.add(cur);
- transactionalDao.insertClaimedHistory(seqId, hostname, now, cur.getId().toString());
- }
- }
- return claimedNotifications;
+ List<Notification> input = dao.getReadyNotifications(now, config.getDaoMaxReadyEvents(), getFullQName());
+
+ List<Notification> claimedNotifications = new ArrayList<Notification>();
+ for (Notification cur : input) {
+ logDebug("about to claim notification %s, key = %s for time %s",
+ cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate());
+ final boolean claimed = (dao.claimNotification(hostname, nextAvailable, cur.getId(), now) == 1);
+ logDebug("claimed notification %s, key = %s for time %s result = %s",
+ cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate(), Boolean.valueOf(claimed));
+ if (claimed) {
+ claimedNotifications.add(cur);
+ dao.insertClaimedHistory(seqId, hostname, now, cur.getUUID().toString());
}
- });
+ }
- for (Notification cur : result) {
- log.debug(String.format("NotificationQueue %sclaimed events %s",
- getFullQName(), cur.getId()));
+ for (Notification cur : claimedNotifications) {
if (cur.getOwner() != null && !cur.getOwner().equals(hostname)) {
log.warn(String.format("NotificationQueue %s stealing notification %s from %s",
getFullQName(), cur, cur.getOwner()));
}
}
- return result;
+ return claimedNotifications;
+ }
+
+ private void logDebug(String format, Object...args) {
+ if (log.isDebugEnabled()) {
+ String realDebug = String.format(format, args);
+ log.debug(String.format("Thread %d [queue = %s] %s", Thread.currentThread().getId(), getFullQName(), realDebug));
+ }
}
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
index 5181113..3b96ee4 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueueService.java
@@ -16,17 +16,16 @@
package com.ning.billing.util.notificationq;
-import org.skife.jdbi.v2.DBI;
-
+import org.skife.jdbi.v2.IDBI;
import com.google.inject.Inject;
import com.ning.billing.util.clock.Clock;
public class DefaultNotificationQueueService extends NotificationQueueServiceBase {
- private final DBI dbi;
+ private final IDBI dbi;
@Inject
- public DefaultNotificationQueueService(final DBI dbi, final Clock clock) {
+ public DefaultNotificationQueueService(final IDBI dbi, final Clock clock) {
super(clock);
this.dbi = dbi;
}
@@ -37,5 +36,4 @@ public class DefaultNotificationQueueService extends NotificationQueueServiceBas
NotificationConfig config) {
return new DefaultNotificationQueue(dbi, clock, svcName, queueName, handler, config);
}
-
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
index 651469b..d59098b 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/Notification.java
@@ -23,9 +23,15 @@ import org.joda.time.DateTime;
public interface Notification extends NotificationLifecycle {
- public UUID getId();
+ public long getId();
+
+ public UUID getUUID();
public String getNotificationKey();
public DateTime getEffectiveDate();
+
+ public String getQueueName();
+
+
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java
new file mode 100644
index 0000000..4e771ba
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationError.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.notificationq;
+
+public class NotificationError extends Error {
+
+ private static final long serialVersionUID = 131398536;
+
+ public NotificationError() {
+ super();
+ }
+
+ public NotificationError(String msg, Throwable arg1) {
+ super(msg, arg1);
+ }
+
+ public NotificationError(String msg) {
+ super(msg);
+ }
+
+ public NotificationError(Throwable msg) {
+ super(msg);
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
index 23f0de0..e1dcdbf 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
@@ -38,22 +38,28 @@ public interface NotificationQueue {
* This is only valid when the queue has been configured with isNotificationProcessingOff is true
* In which case, it will callback users for all the ready notifications.
*
+ * @return the number of entries we processed
*/
- public void processReadyNotification();
+ public int processReadyNotification();
/**
- * Stops the queue.
+ * Stops the queue. Blocks until queue is completely stopped.
*
* @see NotificationQueueHandler.completedQueueStop to be notified when the notification thread exited
*/
public void stopQueue();
/**
- * Starts the queue.
+ * Starts the queue. Blocks until queue has completely started.
*
* @see NotificationQueueHandler.completedQueueStart to be notified when the notification thread started
*/
public void startQueue();
+ /**
+ *
+ * @return the name of that queue
+ */
+ public String getFullQName();
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
index cefd1eb..cc1ea28 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
@@ -17,34 +17,28 @@
package com.ning.billing.util.notificationq;
import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.DBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ning.billing.util.Hostname;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
-import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
public abstract class NotificationQueueBase implements NotificationQueue {
protected final static Logger log = LoggerFactory.getLogger(NotificationQueueBase.class);
+ private static final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
+ private static final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
+ private static final long NANO_TO_MS = (1000 * 1000);
+
protected static final String NOTIFICATION_THREAD_PREFIX = "Notification-";
protected final long STOP_WAIT_TIMEOUT_MS = 60000;
@@ -64,6 +58,9 @@ public abstract class NotificationQueueBase implements NotificationQueue {
// Use this object's monitor for synchronization (no need for volatile)
protected boolean isProcessingEvents;
+ private boolean startedComplete = false;
+ private boolean stoppedComplete = false;
+
// Package visibility on purpose
NotificationQueueBase(final Clock clock, final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
this.clock = clock;
@@ -91,15 +88,15 @@ public abstract class NotificationQueueBase implements NotificationQueue {
@Override
- public void processReadyNotification() {
- doProcessEvents(sequenceId.incrementAndGet());
+ public int processReadyNotification() {
+ return doProcessEvents(sequenceId.incrementAndGet());
}
@Override
public void stopQueue() {
if (config.isNotificationProcessingOff()) {
- handler.completedQueueStop();
+ completedQueueStop();
return;
}
@@ -113,7 +110,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
log.warn("NotificationQueue got interrupted exception when stopping notifications", e);
}
}
-
+ waitForNotificationStopCompletion();
}
@Override
@@ -125,7 +122,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
if (config.isNotificationProcessingOff()) {
log.warn(String.format("KILLBILL NOTIFICATION PROCESSING FOR SVC %s IS OFF !!!", getFullQName()));
- handler.completedQueueStart();
+ completedQueueStart();
return;
}
final NotificationQueueBase notificationQueue = this;
@@ -139,7 +136,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
Thread.currentThread().getId()));
// Thread is now started, notify the listener
- handler.completedQueueStart();
+ completedQueueStart();
try {
while (true) {
@@ -171,7 +168,7 @@ public abstract class NotificationQueueBase implements NotificationQueue {
// Just to make it really obvious in the log
e.printStackTrace();
} finally {
- handler.completedQueueStop();
+ completedQueueStop();
log.info(String.format("NotificationQueue thread %s [%d] exited...",
Thread.currentThread().getName(),
Thread.currentThread().getId()));
@@ -182,12 +179,64 @@ public abstract class NotificationQueueBase implements NotificationQueue {
Thread.sleep(config.getNotificationSleepTimeMs());
}
});
+ waitForNotificationStartCompletion();
+ }
+
+ private void completedQueueStop() {
+ synchronized (this) {
+ stoppedComplete = true;
+ this.notifyAll();
+ }
+ }
+
+ private void completedQueueStart() {
+ synchronized (this) {
+ startedComplete = true;
+ this.notifyAll();
+ }
+ }
+
+ private void waitForNotificationStartCompletion() {
+ waitForNotificationEventCompletion(true);
}
+ private void waitForNotificationStopCompletion() {
+ waitForNotificationEventCompletion(false);
+ }
+
+ private void waitForNotificationEventCompletion(boolean startEvent) {
- protected String getFullQName() {
+ long ini = System.nanoTime();
+ synchronized(this) {
+ do {
+ if ((startEvent ? startedComplete : stoppedComplete)) {
+ break;
+ }
+ try {
+ this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
+ } catch (InterruptedException e ) {
+ Thread.currentThread().interrupt();
+ throw new NotificationError(e);
+ }
+ } while (!(startEvent ? startedComplete : stoppedComplete) &&
+ (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
+
+ if (!(startEvent ? startedComplete : stoppedComplete)) {
+ log.error("Could not {} notification thread in {} msec !!!",
+ (startEvent ? "start" : "stop"),
+ MAX_NOTIFICATION_THREAD_WAIT_MS);
+ throw new NotificationError("Failed to start service!!");
+ }
+ log.info("Notification thread has been {} in {} ms",
+ (startEvent ? "started" : "stopped"),
+ (System.nanoTime() - ini) / NANO_TO_MS);
+ }
+ }
+
+ @Override
+ public String getFullQName() {
return svcName + ":" + queueName;
}
- protected abstract void doProcessEvents(int sequenceId);
+ protected abstract int doProcessEvents(int sequenceId);
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
index a18906b..4d56b03 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueService.java
@@ -16,33 +16,24 @@
package com.ning.billing.util.notificationq;
-import java.util.NoSuchElementException;
+import org.joda.time.DateTime;
public interface NotificationQueueService {
public interface NotificationQueueHandler {
/**
- * Called when the Notification thread has been started
- */
- public void completedQueueStart();
-
- /**
* Called for each notification ready
*
- * @param key the notification key associated to that notification entry
- */
- public void handleReadyNotification(String notificationKey);
- /**
- * Called right before the Notification thread is about to exit
+ * @param notificationKey the notification key associated to that notification entry
*/
- public void completedQueueStop();
- }
+ public void handleReadyNotification(String notificationKey, DateTime eventDateTime);
+ }
- public static final class NotficationQueueAlreadyExists extends Exception {
+ public static final class NotificationQueueAlreadyExists extends Exception {
private static final long serialVersionUID = 1541281L;
- public NotficationQueueAlreadyExists(String msg) {
+ public NotificationQueueAlreadyExists(String msg) {
super(msg);
}
}
@@ -65,11 +56,11 @@ public interface NotificationQueueService {
*
* @return a new NotificationQueue
*
- * @throws NotficationQueueAlreadyExists is the queue associated with that service and name already exits
+ * @throws com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueAlreadyExists is the queue associated with that service and name already exits
*
*/
- NotificationQueue createNotificationQueue(final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config)
- throws NotficationQueueAlreadyExists;
+ public NotificationQueue createNotificationQueue(final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config)
+ throws NotificationQueueAlreadyExists;
/**
* Retrieves an already created NotificationQueue by service and name if it exists
@@ -80,7 +71,14 @@ public interface NotificationQueueService {
*
* @throws NoSuchNotificationQueue if queue does not exist
*/
- NotificationQueue getNotificationQueue(final String svcName, final String queueName)
+ public NotificationQueue getNotificationQueue(final String svcName, final String queueName)
throws NoSuchNotificationQueue;
+
+ /**
+ *
+ * @param services
+ * @return the number of processed notifications
+ */
+ public int triggerManualQueueProcessing(final String [] services, final Boolean keepRunning);
}
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
index a4dc64e..3f8f26f 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueServiceBase.java
@@ -16,12 +16,16 @@
package com.ning.billing.util.notificationq;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Joiner;
import com.google.inject.Inject;
import com.ning.billing.util.clock.Clock;
@@ -43,7 +47,7 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
@Override
public NotificationQueue createNotificationQueue(String svcName,
String queueName, NotificationQueueHandler handler,
- NotificationConfig config) throws NotficationQueueAlreadyExists {
+ NotificationConfig config) throws NotificationQueueAlreadyExists {
if (svcName == null || queueName == null || handler == null || config == null) {
throw new RuntimeException("Need to specify all parameters");
}
@@ -53,7 +57,7 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
synchronized(queues) {
result = queues.get(compositeName);
if (result != null) {
- throw new NotficationQueueAlreadyExists(String.format("Queue for svc %s and name %s already exist",
+ throw new NotificationQueueAlreadyExists(String.format("Queue for svc %s and name %s already exist",
svcName, queueName));
}
result = createNotificationQueueInternal(svcName, queueName, handler, config);
@@ -79,6 +83,48 @@ public abstract class NotificationQueueServiceBase implements NotificationQueueS
}
+ //
+ // Test ONLY
+ //
+ @Override
+ public int triggerManualQueueProcessing(final String [] services, final Boolean keepRunning) {
+
+ int result = 0;
+
+ List<NotificationQueue> manualQueues = null;
+ if (services == null) {
+ manualQueues = new ArrayList<NotificationQueue>(queues.values());
+ } else {
+ Joiner join = Joiner.on(",");
+ join.join(services);
+
+ log.info("Trigger manual processing for services {} ", join.toString());
+ manualQueues = new LinkedList<NotificationQueue>();
+ synchronized (queues) {
+ for (String svc : services) {
+ addQueuesForService(manualQueues, svc);
+ }
+ }
+ }
+ for (NotificationQueue cur : manualQueues) {
+ int processedNotifications = 0;
+ do {
+ processedNotifications = cur.processReadyNotification();
+ log.info("Got {} results from queue {}", processedNotifications, cur.getFullQName());
+ result += processedNotifications;
+ } while(keepRunning && processedNotifications > 0);
+ }
+ return result;
+ }
+
+ private final void addQueuesForService(final List<NotificationQueue> result, final String svcName) {
+ for (String cur : queues.keySet()) {
+ if (cur.startsWith(svcName)) {
+ result.add(queues.get(cur));
+ }
+ }
+ }
+
protected abstract NotificationQueue createNotificationQueueInternal(String svcName,
String queueName, NotificationQueueHandler handler,
NotificationConfig config);
diff --git a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java
new file mode 100644
index 0000000..0e1f99e
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionService.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.tag.api;
+
+import com.google.inject.Inject;
+import com.ning.billing.util.api.TagDefinitionService;
+import com.ning.billing.util.api.TagDefinitionUserApi;
+
+public class DefaultTagDefinitionService implements TagDefinitionService {
+ private static final String TAG_DEFINITION_SERVICE_NAME = "tag-service";
+ private final TagDefinitionUserApi api;
+
+ @Inject
+ public DefaultTagDefinitionService(final TagDefinitionUserApi api) {
+ this.api = api;
+ }
+
+ @Override
+ public TagDefinitionUserApi getTagDefinitionUserApi() {
+ return api;
+ }
+
+ @Override
+ public String getName() {
+ return TAG_DEFINITION_SERVICE_NAME;
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java
new file mode 100644
index 0000000..452eb5e
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagDefinitionUserApi.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.tag.api;
+
+import java.util.List;
+import com.google.inject.Inject;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagDefinitionUserApi;
+import com.ning.billing.util.tag.TagDefinition;
+import com.ning.billing.util.tag.dao.TagDefinitionDao;
+
+public class DefaultTagDefinitionUserApi implements TagDefinitionUserApi {
+ private TagDefinitionDao dao;
+
+ @Inject
+ public DefaultTagDefinitionUserApi(TagDefinitionDao dao) {
+ this.dao = dao;
+ }
+
+ @Override
+ public List<TagDefinition> getTagDefinitions() {
+ return dao.getTagDefinitions();
+ }
+
+ @Override
+ public TagDefinition create(final String name, final String description, final String createdBy) throws TagDefinitionApiException {
+ return dao.create(name, description, createdBy);
+ }
+
+ @Override
+ public void deleteAllTagsForDefinition(final String definitionName) throws TagDefinitionApiException {
+ dao.deleteAllTagsForDefinition(definitionName);
+ }
+
+ @Override
+ public void deleteTagDefinition(final String definitionName) throws TagDefinitionApiException {
+ dao.deleteAllTagsForDefinition(definitionName);
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
new file mode 100644
index 0000000..57ca679
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/DefaultTagDefinitionDao.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.tag.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.skife.jdbi.v2.IDBI;
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.ControlTagType;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.tag.DefaultTagDefinition;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class DefaultTagDefinitionDao implements TagDefinitionDao {
+ private final TagDefinitionSqlDao dao;
+ private final Clock clock;
+
+ @Inject
+ public DefaultTagDefinitionDao(IDBI dbi, Clock clock) {
+ this.dao = dbi.onDemand(TagDefinitionSqlDao.class);
+ this.clock = clock;
+ }
+
+ @Override
+ public List<TagDefinition> getTagDefinitions() {
+ // get user definitions from the database
+ List<TagDefinition> definitionList = new ArrayList<TagDefinition>();
+ definitionList.addAll(dao.get());
+
+ // add control tag definitions
+ for (ControlTagType controlTag : ControlTagType.values()) {
+ definitionList.add(new DefaultTagDefinition(controlTag.toString(), controlTag.getDescription(), null, null));
+ }
+
+ return definitionList;
+ }
+
+ @Override
+ public TagDefinition getByName(final String definitionName) {
+ return dao.getByName(definitionName);
+ }
+
+ @Override
+ public TagDefinition create(final String definitionName, final String description, final String createdBy) throws TagDefinitionApiException {
+ if (isControlTagName(definitionName)) {
+ throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_CONFLICTS_WITH_CONTROL_TAG, definitionName);
+ }
+
+ TagDefinition existingDefinition = dao.getByName(definitionName);
+
+ if (existingDefinition != null) {
+ throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_ALREADY_EXISTS, definitionName);
+ }
+
+ TagDefinition definition = new DefaultTagDefinition(definitionName, description, createdBy, clock.getUTCNow());
+ dao.create(definition);
+ return definition;
+ }
+
+ private boolean isControlTagName(final String definitionName) {
+ for (ControlTagType controlTagName : ControlTagType.values()) {
+ if (controlTagName.toString().equals(definitionName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void deleteAllTagsForDefinition(final String definitionName) throws TagDefinitionApiException {
+ TagDefinition existingDefinition = dao.getByName(definitionName);
+ if (existingDefinition == null) {
+ throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST, definitionName);
+ }
+
+ dao.deleteAllTagsForDefinition(definitionName);
+ }
+
+ @Override
+ public void deleteTagDefinition(final String definitionName) throws TagDefinitionApiException {
+ if (dao.tagDefinitionUsageCount(definitionName) > 0) {
+ throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_IN_USE, definitionName);
+ }
+
+ TagDefinition existingDefinition = dao.getByName(definitionName);
+
+ if (existingDefinition == null) {
+ throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST, definitionName);
+ }
+
+ dao.deleteTagDefinition(definitionName);
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.java
new file mode 100644
index 0000000..3164518
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagDefinitionDao.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.tag.dao;
+
+import java.util.List;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.tag.TagDefinition;
+
+public interface TagDefinitionDao {
+ public List<TagDefinition> getTagDefinitions();
+
+ public TagDefinition getByName(String definitionName);
+
+ public TagDefinition create(String definitionName, String description, String createdBy) throws TagDefinitionApiException;
+
+ public void deleteAllTagsForDefinition(String definitionName) throws TagDefinitionApiException;
+
+ public void deleteTagDefinition(String definitionName) throws TagDefinitionApiException;
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java
new file mode 100644
index 0000000..1296083
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagMapper.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.tag.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+import com.ning.billing.account.api.ControlTagType;
+import com.ning.billing.util.tag.DefaultControlTag;
+import com.ning.billing.util.tag.DefaultTagDefinition;
+import com.ning.billing.util.tag.DescriptiveTag;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class TagMapper implements ResultSetMapper<Tag> {
+ @Override
+ public Tag map(final int index, final ResultSet result, final StatementContext context) throws SQLException {
+ String name = result.getString("tag_definition_name");
+
+ UUID id = UUID.fromString(result.getString("id"));
+ String addedBy = result.getString("added_by");
+ DateTime addedDate = new DateTime(result.getTimestamp("added_date"));
+
+ Tag tag;
+ try {
+ ControlTagType controlTagType = ControlTagType.valueOf(name);
+ tag = new DefaultControlTag(id, addedBy, addedDate, controlTagType);
+ } catch (Throwable t) {
+ String description = result.getString("tag_description");
+ String createdBy = result.getString("created_by");
+ DateTime creationDate = new DateTime(result.getDate("creation_date"));
+
+ UUID tagDefinitionId = UUID.fromString(result.getString("tag_definition_id"));
+ TagDefinition tagDefinition = new DefaultTagDefinition(tagDefinitionId, name, description, createdBy, creationDate);
+ tag = new DescriptiveTag(id, tagDefinition, addedBy, addedDate);
+ }
+
+ return tag;
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java
new file mode 100644
index 0000000..bf4ed62
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagStoreSqlDao.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.tag.dao;
+
+
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import com.ning.billing.util.entity.EntityCollectionDao;
+import com.ning.billing.util.tag.Tag;
+
+@ExternalizedSqlViaStringTemplate3
+@RegisterMapper(TagMapper.class)
+public interface TagStoreSqlDao extends EntityCollectionDao<Tag>, Transactional<TagStoreSqlDao> {
+ @Override
+ @SqlBatch(transactional=false)
+ public void batchSaveFromTransaction(@Bind("objectId") final String objectId,
+ @Bind("objectType") final String objectType,
+ @TagBinder final List<Tag> entities);
+}
\ No newline at end of file
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java b/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
new file mode 100644
index 0000000..707526d
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.tag;
+
+import java.util.UUID;
+import org.joda.time.DateTime;
+import com.ning.billing.account.api.ControlTagType;
+
+public class DefaultControlTag extends DescriptiveTag implements ControlTag {
+ private final ControlTagType controlTagType;
+
+ public DefaultControlTag(final String addedBy,
+ final DateTime addedDate, final ControlTagType controlTagType) {
+ this(UUID.randomUUID(), addedBy, addedDate, controlTagType);
+ }
+
+ public DefaultControlTag(final UUID id, final String addedBy,
+ final DateTime addedDate, final ControlTagType controlTagType) {
+
+ super(id, controlTagType.toString(), addedBy, addedDate);
+ this.controlTagType = controlTagType;
+ }
+
+ @Override
+ public ControlTagType getControlTagType() {
+ return controlTagType;
+ }
+}
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
index 5d49fb2..2fd8144 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultTagStore.java
@@ -17,6 +17,7 @@
package com.ning.billing.util.tag;
import java.util.UUID;
+import com.ning.billing.account.api.ControlTagType;
import com.ning.billing.util.entity.EntityCollectionBase;
public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagStore {
@@ -26,7 +27,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
@Override
public String getEntityKey(final Tag entity) {
- return entity.getName();
+ return entity.getTagDefinitionName();
}
@Override
@@ -36,10 +37,14 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
*/
public boolean processPayment() {
for (Tag tag : entities.values()) {
- if (!tag.getProcessPayment()) {
- return false;
+ if (tag instanceof ControlTag) {
+ ControlTag controlTag = (ControlTag) tag;
+ if (controlTag.getControlTagType() == ControlTagType.AUTO_BILLING_OFF) {
+ return false;
+ }
}
}
+
return true;
}
@@ -50,10 +55,14 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
@Override
public boolean generateInvoice() {
for (Tag tag : entities.values()) {
- if (!tag.getGenerateInvoice()) {
- return false;
+ if (tag instanceof ControlTag) {
+ ControlTag controlTag = (ControlTag) tag;
+ if (controlTag.getControlTagType() == ControlTagType.AUTO_INVOICING_OFF) {
+ return false;
+ }
}
}
+
return true;
}
@@ -65,7 +74,7 @@ public class DefaultTagStore extends EntityCollectionBase<Tag> implements TagSto
@Override
public boolean containsTag(final String tagName) {
for (Tag tag : entities.values()) {
- if (tag.getName().equals(tagName)) {
+ if (tag.getTagDefinitionName().equals(tagName)) {
return true;
}
}
diff --git a/util/src/main/java/com/ning/billing/util/tag/TagBuilder.java b/util/src/main/java/com/ning/billing/util/tag/TagBuilder.java
index cd4247f..626acd3 100644
--- a/util/src/main/java/com/ning/billing/util/tag/TagBuilder.java
+++ b/util/src/main/java/com/ning/billing/util/tag/TagBuilder.java
@@ -21,10 +21,7 @@ import org.joda.time.DateTime;
public class TagBuilder {
private UUID id = UUID.randomUUID();
- private UUID tagDescriptionId;
private String name;
- private boolean processPayment;
- private boolean generateInvoice;
private String addedBy;
private DateTime dateAdded;
@@ -33,23 +30,8 @@ public class TagBuilder {
return this;
}
- public TagBuilder tagDescriptionId(UUID tagDescriptionId) {
- this.tagDescriptionId = tagDescriptionId;
- return this;
- }
-
- public TagBuilder name(String name) {
- this.name = name;
- return this;
- }
-
- public TagBuilder processPayment(boolean processPayment) {
- this.processPayment = processPayment;
- return this;
- }
-
- public TagBuilder generateInvoice(boolean generateInvoice) {
- this.generateInvoice = generateInvoice;
+ public TagBuilder tagDescriptionName(String tagDescriptionName) {
+ this.name = tagDescriptionName;
return this;
}
@@ -64,6 +46,6 @@ public class TagBuilder {
}
public Tag build() {
- return new DefaultTag(id, tagDescriptionId, name, processPayment, generateInvoice, addedBy, dateAdded);
+ return new DescriptiveTag(id, name, addedBy, dateAdded);
}
}
diff --git a/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg b/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
index 57163a9..9d3e96e 100644
--- a/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/customfield/dao/FieldStoreDao.sql.stg
@@ -1,6 +1,6 @@
-group IFieldStoreDao;
+group FieldStoreDao;
-save() ::= <<
+batchSaveFromTransaction() ::= <<
INSERT INTO custom_fields(id, object_id, object_type, field_name, field_value)
VALUES (:id, :objectId, :objectType, :fieldName, :fieldValue)
ON DUPLICATE KEY UPDATE
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
index 30471c7..a0ef302 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -11,30 +11,29 @@ CREATE INDEX custom_fields_object_id_object_type ON custom_fields(object_id, obj
CREATE UNIQUE INDEX custom_fields_unique ON custom_fields(object_id, object_type, field_name);
DROP TABLE IF EXISTS tag_descriptions;
-CREATE TABLE tag_descriptions (
+DROP TABLE IF EXISTS tag_definitions;
+CREATE TABLE tag_definitions (
id char(36) NOT NULL,
name varchar(20) NOT NULL,
created_by varchar(50) NOT NULL,
creation_date datetime NOT NULL,
description varchar(200) NOT NULL,
- generate_invoice boolean DEFAULT false,
- process_payment boolean DEFAULT false,
PRIMARY KEY(id)
) ENGINE=innodb;
-CREATE UNIQUE INDEX tag_descriptions_name ON tag_descriptions(name);
+CREATE UNIQUE INDEX tag_definitions_name ON tag_definitions(name);
DROP TABLE IF EXISTS tags;
CREATE TABLE tags (
id char(36) NOT NULL,
- tag_description_id char(36) NOT NULL,
+ tag_definition_name varchar(20) NOT NULL,
object_id char(36) NOT NULL,
object_type varchar(30) NOT NULL,
- date_added datetime NOT NULL,
+ added_date datetime NOT NULL,
added_by varchar(50) NOT NULL,
PRIMARY KEY(id)
) ENGINE = innodb;
CREATE INDEX tags_by_object ON tags(object_id);
-CREATE UNIQUE INDEX tags_unique ON tags(tag_description_id, object_id);
+CREATE UNIQUE INDEX tags_unique ON tags(tag_definition_name, object_id);
DROP TABLE IF EXISTS notifications;
CREATE TABLE notifications (
@@ -43,14 +42,14 @@ CREATE TABLE notifications (
created_dt datetime NOT NULL,
notification_key varchar(256) NOT NULL,
effective_dt datetime NOT NULL,
- processing_owner char(36) DEFAULT NULL,
+ queue_name char(64) NOT NULL,
+ processing_owner char(50) DEFAULT NULL,
processing_available_dt datetime DEFAULT NULL,
processing_state varchar(14) DEFAULT 'AVAILABLE',
PRIMARY KEY(id)
) ENGINE=innodb;
-CREATE INDEX `idx_comp_where` ON notifications (`effective_dt`,`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX `idx_update` ON notifications (`notification_id`,`processing_state`,`processing_owner`,`processing_available_dt`);
-CREATE INDEX `idx_update1` ON notifications (`notification_id`,`processing_owner`);
+CREATE INDEX `idx_comp_where` ON notifications (`effective_dt`, `queue_name`, `processing_state`,`processing_owner`,`processing_available_dt`);
+CREATE INDEX `idx_update` ON notifications (`processing_state`,`processing_owner`,`processing_available_dt`);
CREATE INDEX `idx_get_ready` ON notifications (`effective_dt`,`created_dt`,`id`);
DROP TABLE IF EXISTS claimed_notifications;
diff --git a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
index 5a44431..7a7ecab 100644
--- a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
@@ -2,48 +2,49 @@ group NotificationSqlDao;
getReadyNotifications(now, max) ::= <<
select
- notification_id
- , notification_key
+ id
+ , notification_id
+ , notification_key
, created_dt
, effective_dt
+ , queue_name
, processing_owner
, processing_available_dt
, processing_state
from notifications
where
effective_dt \<= :now
+ and queue_name = :queue_name
and processing_state != 'PROCESSED'
and (processing_owner IS NULL OR processing_available_dt \<= :now)
order by
effective_dt asc
, created_dt asc
- , id asc
+ , id
limit :max
;
>>
-claimNotification(owner, next_available, notification_id, now) ::= <<
+claimNotification(owner, next_available, id, now) ::= <<
update notifications
set
processing_owner = :owner
, processing_available_dt = :next_available
, processing_state = 'IN_PROCESSING'
where
- notification_id = :notification_id
+ id = :id
and processing_state != 'PROCESSED'
and (processing_owner IS NULL OR processing_available_dt \<= :now)
;
>>
-clearNotification(notification_id, owner) ::= <<
+clearNotification(id, owner) ::= <<
update notifications
set
- processing_owner = NULL
- , processing_state = 'PROCESSED'
+ processing_state = 'PROCESSED'
where
- notification_id = :notification_id
- and processing_owner = :owner
+ id = :id
;
>>
@@ -53,6 +54,7 @@ insertNotification() ::= <<
, notification_key
, created_dt
, effective_dt
+ , queue_name
, processing_owner
, processing_available_dt
, processing_state
@@ -61,6 +63,7 @@ insertNotification() ::= <<
, :notification_key
, :created_dt
, :effective_dt
+ , :queue_name
, :processing_owner
, :processing_available_dt
, :processing_state
diff --git a/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
new file mode 100644
index 0000000..72268d0
--- /dev/null
+++ b/util/src/main/resources/com/ning/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
@@ -0,0 +1,49 @@
+group TagDefinitionDao;
+
+get() ::= <<
+ SELECT id, name, created_by, creation_date, description
+ FROM tag_definitions;
+>>
+
+create() ::= <<
+ INSERT INTO tag_definitions(id, name, created_by, creation_date, description)
+ VALUES(:id, :name, :createdBy, :creationDate, :description);
+>>
+
+update() ::= <<
+ UPDATE tag_definitions
+ SET name = :name, created_by = :createdBy, creation_date = :creationDate,
+ description = :description)
+ WHERE id = :id;
+>>
+
+load() ::= <<
+ SELECT id, name, created_by, creation_date, description
+ FROM tag_definitions
+ WHERE id = :id;
+>>
+
+deleteAllTagsForDefinition() ::= <<
+ DELETE FROM tags
+ WHERE tag_definition_name = :name;
+>>
+
+deleteTagDefinition() ::= <<
+ DELETE FROM tag_definitions
+ WHERE name = :name;
+>>
+
+tagDefinitionUsageCount() ::= <<
+ SELECT COUNT(id)
+ FROM tags
+ WHERE tag_definition_name = :name
+>>
+
+getByName() ::= <<
+ SELECT id, name, created_by, creation_date, description
+ FROM tag_definitions
+ WHERE name = :name;
+>>
+;
+
+
diff --git a/util/src/test/java/com/ning/billing/dbi/DBIProvider.java b/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
index a4a7b61..d83c033 100644
--- a/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
+++ b/util/src/test/java/com/ning/billing/dbi/DBIProvider.java
@@ -25,13 +25,14 @@ import com.ning.jdbi.metrics.MetricsTimingCollector;
import com.ning.jdbi.metrics.SqlJdbiGroupStrategy;
import com.yammer.metrics.core.MetricsRegistry;
import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.TimingCollector;
import org.skife.jdbi.v2.logging.Log4JLog;
import org.skife.jdbi.v2.tweak.SQLLog;
import java.util.concurrent.TimeUnit;
-public class DBIProvider implements Provider<DBI>
+public class DBIProvider implements Provider<IDBI>
{
private final MetricsRegistry metricsRegistry;
private final DbiConfig config;
@@ -44,7 +45,7 @@ public class DBIProvider implements Provider<DBI>
}
@Override
- public DBI get()
+ public IDBI get()
{
final BoneCPConfig dbConfig = new BoneCPConfig();
dbConfig.setJdbcUrl(config.getJdbcUrl());
@@ -54,7 +55,7 @@ public class DBIProvider implements Provider<DBI>
dbConfig.setMaxConnectionsPerPartition(config.getMaxActive());
dbConfig.setConnectionTimeout(config.getConnectionTimeout().getPeriod(), config.getConnectionTimeout().getUnit());
dbConfig.setPartitionCount(1);
- dbConfig.setDefaultTransactionIsolation("READ_COMMITTED");
+ dbConfig.setDefaultTransactionIsolation("REPEATABLE_READ");
dbConfig.setDisableJMX(false);
final BoneCPDataSource ds = new BoneCPDataSource(dbConfig);
diff --git a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
index e8a77f6..5ee7e88 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -16,8 +16,12 @@
package com.ning.billing.dbi;
-import com.mysql.management.MysqldResource;
-import com.mysql.management.MysqldResourceI;
+import java.io.File;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.HashMap;
+import java.util.Map;
+
import org.apache.commons.io.FileUtils;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
@@ -27,17 +31,17 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
-import java.io.File;
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.HashMap;
-import java.util.Map;
+import com.mysql.management.MysqldResource;
+import com.mysql.management.MysqldResourceI;
/**
* Utility class to embed MySQL for testing purposes
*/
public class MysqlTestingHelper
{
+
+ public static final String USE_LOCAL_DB_PROP = "com.ning.billing.dbi.test.useLocalDb";
+
private static final Logger log = LoggerFactory.getLogger(MysqlTestingHelper.class);
private static final String DB_NAME = "test_killbill";
@@ -46,24 +50,38 @@ public class MysqlTestingHelper
private File dbDir;
private MysqldResource mysqldResource;
- private int port = 0;
+ private int port;
public MysqlTestingHelper()
{
- // New socket on any free port
- final ServerSocket socket;
- try {
- socket = new ServerSocket(0);
- port = socket.getLocalPort();
- socket.close();
- }
- catch (IOException e) {
- Assert.fail();
+ if (isUsingLocalInstance()) {
+ port = 3306;
+ } else {
+ // New socket on any free port
+ final ServerSocket socket;
+ try {
+ socket = new ServerSocket(0);
+ port = socket.getLocalPort();
+ socket.close();
+ }
+ catch (IOException e) {
+ Assert.fail();
+ }
}
}
+
+ public boolean isUsingLocalInstance() {
+ return (System.getProperty(USE_LOCAL_DB_PROP) != null);
+ }
+
public void startMysql() throws IOException
{
+
+ if (isUsingLocalInstance()) {
+ return;
+ }
+
dbDir = File.createTempFile("mysql", "");
dbDir.delete();
dbDir.mkdir();
@@ -73,7 +91,7 @@ public class MysqlTestingHelper
dbOpts.put(MysqldResourceI.PORT, Integer.toString(port));
dbOpts.put(MysqldResourceI.INITIALIZE_USER, "true");
dbOpts.put(MysqldResourceI.INITIALIZE_USER_NAME, USERNAME);
- dbOpts.put(MysqldResourceI.INITIALIZE_PASSWORD, PASSWORD);
+ dbOpts.put("default-time-zone", "+00:00");
mysqldResource.start("test-mysqld-thread", dbOpts);
if (!mysqldResource.isRunning()) {
@@ -86,6 +104,12 @@ public class MysqlTestingHelper
public void cleanupTable(final String table)
{
+
+ if (!isUsingLocalInstance() && (mysqldResource == null || !mysqldResource.isRunning())) {
+ log.error("Asked to cleanup table " + table + " but MySQL is not running!");
+ return;
+ }
+
if (mysqldResource == null || !mysqldResource.isRunning()) {
log.error("Asked to cleanup table " + table + " but MySQL is not running!");
return;
@@ -113,7 +137,7 @@ public class MysqlTestingHelper
}
}
- public DBI getDBI()
+ public IDBI getDBI()
{
final String dbiString = "jdbc:mysql://localhost:" + port + "/" + DB_NAME + "?createDatabaseIfNotExist=true";
return new DBI(dbiString, USERNAME, PASSWORD);
@@ -121,6 +145,9 @@ public class MysqlTestingHelper
public void initDb(final String ddl) throws IOException
{
+ if (isUsingLocalInstance()) {
+ return;
+ }
final IDBI dbi = getDBI();
dbi.withHandle(new HandleCallback<Void>()
{
diff --git a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
index 7698697..ad3e5d3 100644
--- a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
+++ b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
@@ -19,6 +19,8 @@ package com.ning.billing.util.clock;
import com.ning.billing.catalog.api.Duration;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
@@ -26,6 +28,8 @@ import java.util.List;
// STEPH should really be in tests but not accessible from other sub modules
public class ClockMock extends DefaultClock {
+ private static final Logger log = LoggerFactory.getLogger(ClockMock.class);
+
private enum DeltaType {
DELTA_NONE,
DELTA_DURATION,
@@ -34,13 +38,13 @@ public class ClockMock extends DefaultClock {
private long deltaFromRealityMs;
private List<Duration> deltaFromRealityDuration;
- private long deltaFromRealitDurationEpsilon;
+ private long deltaFromRealityDurationEpsilon;
private DeltaType deltaType;
public ClockMock() {
deltaType = DeltaType.DELTA_NONE;
deltaFromRealityMs = 0;
- deltaFromRealitDurationEpsilon = 0;
+ deltaFromRealityDurationEpsilon = 0;
deltaFromRealityDuration = null;
}
@@ -54,41 +58,53 @@ public class ClockMock extends DefaultClock {
return getNow(DateTimeZone.UTC);
}
+ private void logClockAdjustment(DateTime prev, DateTime next) {
+ log.info(String.format(" ************ ADJUSTING CLOCK FROM %s to %s ********************", prev, next));
+ }
+
public synchronized void setDeltaFromReality(Duration delta, long epsilon) {
+ DateTime prev = getUTCNow();
deltaType = DeltaType.DELTA_DURATION;
deltaFromRealityDuration = new ArrayList<Duration>();
deltaFromRealityDuration.add(delta);
- deltaFromRealitDurationEpsilon = epsilon;
+ deltaFromRealityDurationEpsilon = epsilon;
deltaFromRealityMs = 0;
+ logClockAdjustment(prev, getUTCNow());
}
public synchronized void addDeltaFromReality(Duration delta) {
+ DateTime prev = getUTCNow();
if (deltaType != DeltaType.DELTA_DURATION) {
throw new RuntimeException("ClockMock should be set with type DELTA_DURATION");
}
deltaFromRealityDuration.add(delta);
+ logClockAdjustment(prev, getUTCNow());
}
public synchronized void setDeltaFromReality(long delta) {
+ DateTime prev = getUTCNow();
deltaType = DeltaType.DELTA_ABS;
deltaFromRealityDuration = null;
- deltaFromRealitDurationEpsilon = 0;
+ deltaFromRealityDurationEpsilon = 0;
deltaFromRealityMs = delta;
+ logClockAdjustment(prev, getUTCNow());
}
public synchronized void addDeltaFromReality(long delta) {
+ DateTime prev = getUTCNow();
if (deltaType != DeltaType.DELTA_ABS) {
throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
}
deltaFromRealityDuration = null;
- deltaFromRealitDurationEpsilon = 0;
+ deltaFromRealityDurationEpsilon = 0;
deltaFromRealityMs += delta;
+ logClockAdjustment(prev, getUTCNow());
}
public synchronized void resetDeltaFromReality() {
deltaType = DeltaType.DELTA_NONE;
deltaFromRealityDuration = null;
- deltaFromRealitDurationEpsilon = 0;
+ deltaFromRealityDurationEpsilon = 0;
deltaFromRealityMs = 0;
}
@@ -109,8 +125,6 @@ public class ClockMock extends DefaultClock {
DateTime result = input;
for (Duration cur : deltaFromRealityDuration) {
-
- int length = cur.getNumber();
switch (cur.getUnit()) {
case DAYS:
result = result.plusDays(cur.getNumber());
@@ -126,11 +140,11 @@ public class ClockMock extends DefaultClock {
case UNLIMITED:
default:
- throw new RuntimeException("ClockMock is adjusting an unlimtited time period");
+ throw new RuntimeException("ClockMock is adjusting an unlimited time period");
}
}
- if (deltaFromRealitDurationEpsilon != 0) {
- result = result.plus(deltaFromRealitDurationEpsilon);
+ if (deltaFromRealityDurationEpsilon != 0) {
+ result = result.plus(deltaFromRealityDurationEpsilon);
}
return result;
}
diff --git a/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java b/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java
new file mode 100644
index 0000000..89de280
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/clock/MockClockModule.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.clock;
+
+import com.google.inject.AbstractModule;
+
+
+public class MockClockModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+ }
+
+}
diff --git a/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
new file mode 100644
index 0000000..4512a5d
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/customfield/TestFieldStore.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.customfield;
+
+import java.io.IOException;
+import java.util.UUID;
+import org.apache.commons.io.IOUtils;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.customfield.dao.FieldStoreDao;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+@Test(groups={"util"})
+public class TestFieldStore {
+ Logger log = LoggerFactory.getLogger(TestFieldStore.class);
+ private final MysqlTestingHelper helper = new MysqlTestingHelper();
+ private IDBI dbi;
+
+ @BeforeClass(alwaysRun = true)
+ protected void setup() throws IOException {
+ // Health check test to make sure MySQL is setup properly
+ try {
+ final String utilDdl = IOUtils.toString(TestFieldStore.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+
+ helper.startMysql();
+ helper.initDb(utilDdl);
+
+ dbi = helper.getDBI();
+ }
+ catch (Throwable t) {
+ log.error("Setup failed", t);
+ fail(t.toString());
+ }
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void stopMysql()
+ {
+ helper.stopMysql();
+ }
+
+ @Test
+ public void testFieldStore() {
+ final UUID id = UUID.randomUUID();
+ final String objectType = "Test widget";
+
+ final FieldStore fieldStore1 = new DefaultFieldStore(id, objectType);
+
+ String fieldName = "TestField1";
+ String fieldValue = "Kitty Hawk";
+ fieldStore1.setValue(fieldName, fieldValue);
+
+ FieldStoreDao fieldStoreDao = dbi.onDemand(FieldStoreDao.class);
+ fieldStoreDao.inTransaction(new Transaction<Void, FieldStoreDao>() {
+ @Override
+ public Void inTransaction(FieldStoreDao transactional,
+ TransactionStatus status) throws Exception {
+ transactional.batchSaveFromTransaction(id.toString(), objectType, fieldStore1.getEntityList());
+ return null;
+ }
+ });
+
+
+ final FieldStore fieldStore2 = DefaultFieldStore.create(id, objectType);
+ fieldStore2.add(fieldStoreDao.load(id.toString(), objectType));
+
+ assertEquals(fieldStore2.getValue(fieldName), fieldValue);
+
+ fieldValue = "Cape Canaveral";
+ fieldStore2.setValue(fieldName, fieldValue);
+ assertEquals(fieldStore2.getValue(fieldName), fieldValue);
+ fieldStoreDao.inTransaction(new Transaction<Void, FieldStoreDao>() {
+ @Override
+ public Void inTransaction(FieldStoreDao transactional,
+ TransactionStatus status) throws Exception {
+ transactional.batchSaveFromTransaction(id.toString(), objectType, fieldStore2.getEntityList());
+ return null;
+ }
+ });
+
+
+ final FieldStore fieldStore3 = DefaultFieldStore.create(id, objectType);
+ assertEquals(fieldStore3.getValue(fieldName), null);
+ fieldStore3.add(fieldStoreDao.load(id.toString(), objectType));
+
+ assertEquals(fieldStore3.getValue(fieldName), fieldValue);
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/globallocker/MockGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globallocker/MockGlobalLocker.java
new file mode 100644
index 0000000..b1f3378
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/globallocker/MockGlobalLocker.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.globallocker;
+
+import com.ning.billing.util.globallocker.GlobalLock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+
+public class MockGlobalLocker implements GlobalLocker {
+
+ @Override
+ public GlobalLock lockWithNumberOfTries(LockerService service,
+ String lockKey, int retry) {
+ return new GlobalLock() {
+ @Override
+ public void release() {
+ }
+ };
+ }
+
+ @Override
+ public Boolean isFree(LockerService service, String lockKey) {
+ return Boolean.TRUE;
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java b/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
new file mode 100644
index 0000000..1fd8290
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/globallocker/TestMysqlGlobalLocker.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.globallocker;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.util.globallocker.GlobalLock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+import com.ning.billing.util.globallocker.LockFailedException;
+import com.ning.billing.util.globallocker.MySqlGlobalLocker;
+import com.ning.billing.util.globallocker.GlobalLocker.LockerService;
+
+@Guice(modules=TestMysqlGlobalLocker.TestMysqlGlobalLockerModule.class)
+public class TestMysqlGlobalLocker {
+
+ @Inject
+ private IDBI dbi;
+
+ @Inject
+ private MysqlTestingHelper helper;
+
+ @BeforeClass(alwaysRun=true)
+ public void setup() throws IOException {
+ helper.startMysql();
+ createSimpleTable(dbi);
+ }
+
+ @AfterClass(alwaysRun=true)
+ public void tearDown() {
+ helper.stopMysql();
+ }
+
+ // Used as a manual test to validate the simple DAO by stepping through that locking is done and release correctly
+ @Test(groups= "slow", enabled = true)
+ public void testSimpleLocking() {
+
+ final String lockName = UUID.randomUUID().toString();
+
+ GlobalLocker locker = new MySqlGlobalLocker(dbi);
+ GlobalLock lock = locker.lockWithNumberOfTries(LockerService.INVOICE, lockName, 3);
+
+ dbi.inTransaction(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(Handle conn, TransactionStatus status)
+ throws Exception {
+ conn.execute("insert into dummy (dummy_id) values ('" + UUID.randomUUID().toString() + "')");
+ return null;
+ }
+ });
+ Assert.assertEquals(locker.isFree(LockerService.INVOICE, lockName), Boolean.FALSE);
+
+ boolean gotException = false;
+ try {
+ locker.lockWithNumberOfTries(LockerService.INVOICE, lockName, 1);
+ } catch (LockFailedException e) {
+ gotException = true;
+ }
+ Assert.assertTrue(gotException);
+
+ lock.release();
+
+ Assert.assertEquals(locker.isFree(LockerService.INVOICE, lockName), Boolean.TRUE);
+ }
+
+ private void createSimpleTable(IDBI dbi) {
+ dbi.inTransaction(new TransactionCallback<Void>() {
+
+ @Override
+ public Void inTransaction(Handle h, TransactionStatus status)
+ throws Exception {
+ h.execute("create table dummy " +
+ "(id int(11) unsigned NOT NULL AUTO_INCREMENT, " +
+ "dummy_id char(36) NOT NULL, " +
+ "PRIMARY KEY(id)" +
+ ") ENGINE=innodb;");
+ return null;
+ }
+ });
+ }
+
+ public final static class TestMysqlGlobalLockerModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ final IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ }
+ }
+}
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
index 89dfd76..cb01e00 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
@@ -16,49 +16,42 @@
package com.ning.billing.util.notificationq.dao;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
-
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
-import org.skife.config.ConfigurationObjectFactory;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.testng.Assert;
-import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterSuite;
-import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
-
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
-import com.ning.billing.dbi.DBIProvider;
-import com.ning.billing.dbi.DbiConfig;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.util.notificationq.DefaultNotification;
import com.ning.billing.util.notificationq.Notification;
import com.ning.billing.util.notificationq.NotificationLifecycle.NotificationLifecycleState;
import com.ning.billing.util.notificationq.dao.NotificationSqlDao.NotificationSqlMapper;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
@Guice(modules = TestNotificationSqlDao.TestNotificationSqlDaoModule.class)
public class TestNotificationSqlDao {
private static AtomicInteger sequenceId = new AtomicInteger();
@Inject
- private DBI dbi;
+ private IDBI dbi;
@Inject
MysqlTestingHelper helper;
@@ -110,12 +103,12 @@ public class TestNotificationSqlDao {
String notificationKey = UUID.randomUUID().toString();
DateTime effDt = new DateTime();
- Notification notif = new DefaultNotification(notificationKey, effDt);
+ Notification notif = new DefaultNotification("testBasic",notificationKey, effDt);
dao.insertNotification(notif);
Thread.sleep(1000);
DateTime now = new DateTime();
- List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3);
+ List<Notification> notifications = dao.getReadyNotifications(now.toDate(), 3, "testBasic");
assertNotNull(notifications);
assertEquals(notifications.size(), 1);
@@ -127,23 +120,23 @@ public class TestNotificationSqlDao {
assertEquals(notification.getNextAvailableDate(), null);
DateTime nextAvailable = now.plusMinutes(5);
- int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId().toString(), now.toDate());
+ int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId(), now.toDate());
assertEquals(res, 1);
- dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getId().toString());
+ dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getUUID().toString());
- notification = fetchNotification(notification.getId().toString());
+ notification = fetchNotification(notification.getUUID().toString());
assertEquals(notification.getNotificationKey(), notificationKey);
validateDate(notification.getEffectiveDate(), effDt);
assertEquals(notification.getOwner().toString(), ownerId);
assertEquals(notification.getProcessingState(), NotificationLifecycleState.IN_PROCESSING);
validateDate(notification.getNextAvailableDate(), nextAvailable);
- dao.clearNotification(notification.getId().toString(), ownerId);
+ dao.clearNotification(notification.getId(), ownerId);
- notification = fetchNotification(notification.getId().toString());
+ notification = fetchNotification(notification.getUUID().toString());
assertEquals(notification.getNotificationKey(), notificationKey);
validateDate(notification.getEffectiveDate(), effDt);
- assertEquals(notification.getOwner(), null);
+ //assertEquals(notification.getOwner(), null);
assertEquals(notification.getProcessingState(), NotificationLifecycleState.PROCESSED);
validateDate(notification.getNextAvailableDate(), nextAvailable);
@@ -155,10 +148,12 @@ public class TestNotificationSqlDao {
@Override
public Notification withHandle(Handle handle) throws Exception {
Notification res = handle.createQuery(" select" +
- " notification_id" +
+ " id " +
+ ", notification_id" +
", notification_key" +
", created_dt" +
", effective_dt" +
+ ", queue_name" +
", processing_owner" +
", processing_available_dt" +
", processing_state" +
@@ -199,8 +194,8 @@ public class TestNotificationSqlDao {
final MysqlTestingHelper helper = new MysqlTestingHelper();
bind(MysqlTestingHelper.class).toInstance(helper);
- DBI dbi = helper.getDBI();
- bind(DBI.class).toInstance(dbi);
+ IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
/*
bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
index 7ee2e10..e96d2cf 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
@@ -52,14 +52,16 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
public void recordFutureNotificationFromTransaction(
Transmogrifier transactionalDao, DateTime futureNotificationTime,
NotificationKey notificationKey) {
- Notification notification = new DefaultNotification(notificationKey.toString(), futureNotificationTime);
+ Notification notification = new DefaultNotification("MockQueue", notificationKey.toString(), futureNotificationTime);
synchronized(notifications) {
notifications.add(notification);
}
}
@Override
- protected void doProcessEvents(int sequenceId) {
+ protected int doProcessEvents(int sequenceId) {
+
+ int result = 0;
List<Notification> processedNotifications = new ArrayList<Notification>();
List<Notification> oldNotifications = new ArrayList<Notification>();
@@ -73,9 +75,11 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
readyNotifications.add(cur);
}
}
+
+ result = readyNotifications.size();
for (Notification cur : readyNotifications) {
- handler.handleReadyNotification(cur.getNotificationKey());
- DefaultNotification processedNotification = new DefaultNotification(cur.getId(), hostname, clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
+ handler.handleReadyNotification(cur.getNotificationKey(), cur.getEffectiveDate());
+ DefaultNotification processedNotification = new DefaultNotification(-1L, cur.getUUID(), hostname, "MockQueue", clock.getUTCNow().plus(config.getDaoClaimTimeMs()), NotificationLifecycleState.PROCESSED, cur.getNotificationKey(), cur.getEffectiveDate());
oldNotifications.add(cur);
processedNotifications.add(processedNotification);
@@ -87,5 +91,6 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
notifications.addAll(processedNotifications);
}
}
+ return result;
}
}
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
index 2e3bb3c..fbcf8f9 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
@@ -16,8 +16,9 @@
package com.ning.billing.util.notificationq;
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.sql.SQLException;
@@ -25,21 +26,19 @@ import java.util.Collection;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
-import org.skife.config.ConfigurationObjectFactory;
-import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Transaction;
import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.tweak.HandleCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
-import org.testng.annotations.AfterSuite;
-import org.testng.annotations.AfterTest;
-import org.testng.annotations.BeforeClass;
+import org.testng.Assert;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Guice;
@@ -49,8 +48,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
-import com.ning.billing.dbi.DBIProvider;
-import com.ning.billing.dbi.DbiConfig;
+import com.google.inject.name.Names;
import com.ning.billing.dbi.MysqlTestingHelper;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.ClockMock;
@@ -59,370 +57,367 @@ import com.ning.billing.util.notificationq.dao.NotificationSqlDao;
@Guice(modules = TestNotificationQueue.TestNotificationQueueModule.class)
public class TestNotificationQueue {
-
- private final static Logger log = LoggerFactory.getLogger(TestNotificationQueue.class);
-
- @Inject
- private DBI dbi;
-
+ Logger log = LoggerFactory.getLogger(TestNotificationQueue.class);
@Inject
- MysqlTestingHelper helper;
-
- @Inject
- private Clock clock;
-
- private DummySqlTest dao;
-
- // private NotificationQueue queue;
-
- private void startMysql() throws IOException, ClassNotFoundException, SQLException {
- final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
- final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
- helper.startMysql();
- helper.initDb(ddl);
- helper.initDb(testDdl);
- }
-
- @BeforeSuite(alwaysRun = true)
- public void setup() throws Exception {
- startMysql();
- dao = dbi.onDemand(DummySqlTest.class);
- }
-
- @BeforeTest
- public void beforeTest() {
- dbi.withHandle(new HandleCallback<Void>() {
-
- @Override
- public Void withHandle(Handle handle) throws Exception {
- handle.execute("delete from notifications");
- handle.execute("delete from claimed_notifications");
- handle.execute("delete from dummy");
- return null;
- }
- });
- // Reset time to real value
- ((ClockMock) clock).resetDeltaFromReality();
- }
-
-
-
- /**
- * Verify that we can call start/stop on a disabled queue and that both start/stop callbacks are called
- *
- * @throws InterruptedException
- */
- @Test
- public void testSimpleQueueDisabled() throws InterruptedException {
-
- final TestStartStop testStartStop = new TestStartStop(false, false);
- DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "dead",
- new NotificationQueueHandler() {
- @Override
- public void handleReadyNotification(String notificationKey) {
- }
- @Override
- public void completedQueueStop() {
- testStartStop.stopped();
- }
- @Override
- public void completedQueueStart() {
- testStartStop.started();
- }
- },
- getNotificationConfig(true, 100, 1, 10000));
-
- executeTest(testStartStop, queue, new WithTest() {
+ private IDBI dbi;
+
+ @Inject
+ MysqlTestingHelper helper;
+
+ @Inject
+ private Clock clock;
+
+ private DummySqlTest dao;
+
+ private int eventsReceived;
+
+ // private NotificationQueue queue;
+
+ private void startMysql() throws IOException, ClassNotFoundException, SQLException {
+ final String ddl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+ final String testDdl = IOUtils.toString(NotificationSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl_test.sql"));
+ helper.startMysql();
+ helper.initDb(ddl);
+ helper.initDb(testDdl);
+ }
+
+ @BeforeSuite(alwaysRun = true)
+ public void setup() throws Exception {
+ startMysql();
+ dao = dbi.onDemand(DummySqlTest.class);
+ }
+
+ @BeforeTest
+ public void beforeTest() {
+ dbi.withHandle(new HandleCallback<Void>() {
+
+ @Override
+ public Void withHandle(Handle handle) throws Exception {
+ handle.execute("delete from notifications");
+ handle.execute("delete from claimed_notifications");
+ handle.execute("delete from dummy");
+ return null;
+ }
+ });
+ // Reset time to real value
+ ((ClockMock) clock).resetDeltaFromReality();
+ }
+
+
+
+ /**
+ * Test that we can post a notification in the future from a transaction and get the notification
+ * callback with the correct key when the time is ready
+ * @throws Exception
+ */
+ @Test(groups={"fast"}, enabled = true)
+ public void testSimpleNotification() throws Exception {
+
+ final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
+
+ final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "foo",
+ new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
+ synchronized (expectedNotifications) {
+ log.info("Handler received key: " + notificationKey);
+
+ expectedNotifications.put(notificationKey.toString(), Boolean.TRUE);
+ expectedNotifications.notify();
+ }
+ }
+ },
+ getNotificationConfig(false, 100, 1, 10000));
+
+
+ queue.startQueue();
+
+ final UUID key = UUID.randomUUID();
+ final DummyObject obj = new DummyObject("foo", key);
+ final DateTime now = new DateTime();
+ final DateTime readyTime = now.plusMillis(2000);
+ final NotificationKey notificationKey = new NotificationKey() {
+ @Override
+ public String toString() {
+ return key.toString();
+ }
+ };
+ expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
+
+
+ // Insert dummy to be processed in 2 sec'
+ dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+ @Override
+ public Void inTransaction(DummySqlTest transactional,
+ TransactionStatus status) throws Exception {
+
+ transactional.insertDummy(obj);
+ queue.recordFutureNotificationFromTransaction(transactional,
+ readyTime, notificationKey);
+ log.info("Posted key: " + notificationKey);
+
+ return null;
+ }
+ });
+
+ // Move time in the future after the notification effectiveDate
+ ((ClockMock) clock).setDeltaFromReality(3000);
+
+ // Notification should have kicked but give it at least a sec' for thread scheduling
+ await().atMost(1, MINUTES).until(new Callable<Boolean>() {
@Override
- public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
- // Do nothing
+ public Boolean call() throws Exception {
+ return expectedNotifications.get(notificationKey.toString());
}
});
- assertTrue(true);
- }
-
- /**
- * Test that we can post a notification in the future from a transaction and get the notification
- * callback with the correct key when the time is ready
- *
- * @throws InterruptedException
- */
- @Test
- public void testSimpleNotification() throws InterruptedException {
-
- final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
-
- final TestStartStop testStartStop = new TestStartStop(false, false);
- DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "foo",
- new NotificationQueueHandler() {
- @Override
- public void handleReadyNotification(String notificationKey) {
- synchronized (expectedNotifications) {
- expectedNotifications.put(notificationKey, Boolean.TRUE);
- expectedNotifications.notify();
- }
- }
- @Override
- public void completedQueueStop() {
- testStartStop.stopped();
- }
- @Override
- public void completedQueueStart() {
- testStartStop.started();
- }
- },
- getNotificationConfig(false, 100, 1, 10000));
-
-
- executeTest(testStartStop, queue, new WithTest() {
- @Override
- public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-
- final UUID key = UUID.randomUUID();
- final DummyObject obj = new DummyObject("foo", key);
- final DateTime now = new DateTime();
- final DateTime readyTime = now.plusMillis(2000);
- final NotificationKey notificationKey = new NotificationKey() {
- @Override
- public String toString() {
- return key.toString();
- }
- };
- expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
-
-
- // Insert dummy to be processed in 2 sec'
- dao.inTransaction(new Transaction<Void, DummySqlTest>() {
- @Override
- public Void inTransaction(DummySqlTest transactional,
- TransactionStatus status) throws Exception {
-
- transactional.insertDummy(obj);
- readyQueue.recordFutureNotificationFromTransaction(transactional,
- readyTime, notificationKey);
- return null;
- }
- });
-
- // Move time in the future after the notification effectiveDate
- ((ClockMock) clock).setDeltaFromReality(3000);
-
- // Notification should have kicked but give it at least a sec' for thread scheduling
- int nbTry = 1;
- boolean success = false;
- do {
- synchronized(expectedNotifications) {
- if (expectedNotifications.get(notificationKey.toString())) {
- success = true;
- break;
- }
- expectedNotifications.wait(1000);
- }
- } while (nbTry-- > 0);
- assertEquals(success, true);
- }
- });
- }
-
- @Test
- public void testManyNotifications() throws InterruptedException {
- final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
-
- final TestStartStop testStartStop = new TestStartStop(false, false);
- DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
- new NotificationQueueHandler() {
- @Override
- public void handleReadyNotification(String notificationKey) {
- synchronized (expectedNotifications) {
- expectedNotifications.put(notificationKey, Boolean.TRUE);
- expectedNotifications.notify();
- }
- }
- @Override
- public void completedQueueStop() {
- testStartStop.stopped();
- }
- @Override
- public void completedQueueStart() {
- testStartStop.started();
- }
- },
- getNotificationConfig(false, 100, 10, 10000));
-
-
- executeTest(testStartStop, queue, new WithTest() {
- @Override
- public void test(final DefaultNotificationQueue readyQueue) throws InterruptedException {
-
- final DateTime now = clock.getUTCNow();
- final int MAX_NOTIFICATIONS = 100;
- for (int i = 0; i < MAX_NOTIFICATIONS; i++) {
-
- final int nextReadyTimeIncrementMs = 1000;
-
- final UUID key = UUID.randomUUID();
- final DummyObject obj = new DummyObject("foo", key);
- final int currentIteration = i;
-
- final NotificationKey notificationKey = new NotificationKey() {
- @Override
- public String toString() {
- return key.toString();
- }
- };
- expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
-
- dao.inTransaction(new Transaction<Void, DummySqlTest>() {
- @Override
- public Void inTransaction(DummySqlTest transactional,
- TransactionStatus status) throws Exception {
-
- transactional.insertDummy(obj);
- readyQueue.recordFutureNotificationFromTransaction(transactional,
- now.plus((currentIteration + 1) * nextReadyTimeIncrementMs), notificationKey);
- return null;
- }
- });
-
- // Move time in the future after the notification effectiveDate
- if (i == 0) {
- ((ClockMock) clock).setDeltaFromReality(nextReadyTimeIncrementMs);
- } else {
- ((ClockMock) clock).addDeltaFromReality(nextReadyTimeIncrementMs);
- }
- }
-
- // Wait a little longer since there are a lot of callback that need to happen
- int nbTry = MAX_NOTIFICATIONS + 1;
- boolean success = false;
- do {
- synchronized(expectedNotifications) {
-
- Collection<Boolean> completed = Collections2.filter(expectedNotifications.values(), new Predicate<Boolean>() {
- @Override
- public boolean apply(Boolean input) {
- return input;
- }
- });
-
- if (completed.size() == MAX_NOTIFICATIONS) {
- success = true;
- break;
- }
- //log.debug(String.format("BEFORE WAIT : Got %d notifications at time %s (real time %s)", completed.size(), clock.getUTCNow(), new DateTime()));
- expectedNotifications.wait(1000);
- }
- } while (nbTry-- > 0);
- assertEquals(success, true);
- }
- });
- }
-
- NotificationConfig getNotificationConfig(final boolean off,
- final long sleepTime, final int maxReadyEvents, final long claimTimeMs) {
- return new NotificationConfig() {
+ Assert.assertTrue(expectedNotifications.get(notificationKey.toString()));
+ }
+
+ @Test
+ public void testManyNotifications() throws InterruptedException {
+ final Map<String, Boolean> expectedNotifications = new TreeMap<String, Boolean>();
+
+ final DefaultNotificationQueue queue = new DefaultNotificationQueue(dbi, clock, "test-svc", "many",
+ new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
+ synchronized (expectedNotifications) {
+ expectedNotifications.put(notificationKey, Boolean.TRUE);
+ expectedNotifications.notify();
+ }
+ }
+ },
+ getNotificationConfig(false, 100, 10, 10000));
+
+
+ queue.startQueue();
+
+ final DateTime now = clock.getUTCNow();
+ final int MAX_NOTIFICATIONS = 100;
+ for (int i = 0; i < MAX_NOTIFICATIONS; i++) {
+
+ final int nextReadyTimeIncrementMs = 1000;
+
+ final UUID key = UUID.randomUUID();
+ final DummyObject obj = new DummyObject("foo", key);
+ final int currentIteration = i;
+
+ final NotificationKey notificationKey = new NotificationKey() {
+ @Override
+ public String toString() {
+ return key.toString();
+ }
+ };
+ expectedNotifications.put(notificationKey.toString(), Boolean.FALSE);
+
+ dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+ @Override
+ public Void inTransaction(DummySqlTest transactional,
+ TransactionStatus status) throws Exception {
+
+ transactional.insertDummy(obj);
+ queue.recordFutureNotificationFromTransaction(transactional,
+ now.plus((currentIteration + 1) * nextReadyTimeIncrementMs), notificationKey);
+ return null;
+ }
+ });
+
+ // Move time in the future after the notification effectiveDate
+ if (i == 0) {
+ ((ClockMock) clock).setDeltaFromReality(nextReadyTimeIncrementMs);
+ } else {
+ ((ClockMock) clock).addDeltaFromReality(nextReadyTimeIncrementMs);
+ }
+ }
+
+ // Wait a little longer since there are a lot of callback that need to happen
+ int nbTry = MAX_NOTIFICATIONS + 1;
+ boolean success = false;
+ do {
+ synchronized(expectedNotifications) {
+
+ Collection<Boolean> completed = Collections2.filter(expectedNotifications.values(), new Predicate<Boolean>() {
+ @Override
+ public boolean apply(Boolean input) {
+ return input;
+ }
+ });
+
+ if (completed.size() == MAX_NOTIFICATIONS) {
+ success = true;
+ break;
+ }
+ //log.debug(String.format("BEFORE WAIT : Got %d notifications at time %s (real time %s)", completed.size(), clock.getUTCNow(), new DateTime()));
+ expectedNotifications.wait(1000);
+ }
+ } while (nbTry-- > 0);
+ assertEquals(success, true);
+
+ }
+
+ /**
+ * Test that we can post a notification in the future from a transaction and get the notification
+ * callback with the correct key when the time is ready
+ * @throws Exception
+ */
+ @Test(groups={"fast"}, enabled = true)
+ public void testMultipleHandlerNotification() throws Exception {
+
+ final Map<String, Boolean> expectedNotificationsFred = new TreeMap<String, Boolean>();
+ final Map<String, Boolean> expectedNotificationsBarney = new TreeMap<String, Boolean>();
+
+ NotificationQueueService notificationQueueService = new DefaultNotificationQueueService(dbi, clock);
+
+ NotificationConfig config=new NotificationConfig() {
@Override
public boolean isNotificationProcessingOff() {
- return off;
+ return false;
}
@Override
public long getNotificationSleepTimeMs() {
- return sleepTime;
+ return 10;
}
@Override
public int getDaoMaxReadyEvents() {
- return maxReadyEvents;
+ return 1;
}
@Override
public long getDaoClaimTimeMs() {
- return claimTimeMs;
- }
- };
- }
-
- private static class TestStartStop {
- private boolean started;
- private boolean stopped;
-
- public TestStartStop(boolean started, boolean stopped) {
- super();
- this.started = started;
- this.stopped = stopped;
- }
-
- public void started() {
- synchronized(this) {
- started = true;
- notify();
- }
- }
-
- public void stopped() {
- synchronized(this) {
- stopped = true;
- notify();
- }
- }
-
- public boolean waitForStartComplete(int timeoutMs) throws InterruptedException {
- return waitForEventCompletion(timeoutMs, true);
- }
-
- public boolean waitForStopComplete(int timeoutMs) throws InterruptedException {
- return waitForEventCompletion(timeoutMs, false);
- }
-
- private boolean waitForEventCompletion(int timeoutMs, boolean start) throws InterruptedException {
- DateTime init = new DateTime();
- synchronized(this) {
- while (! ((start ? started : stopped))) {
- wait(timeoutMs);
- if (init.plusMillis(timeoutMs).isAfterNow()) {
- break;
- }
- }
+ return 60000;
}
- return (start ? started : stopped);
- }
- }
+ };
- private interface WithTest {
- public void test(DefaultNotificationQueue readyQueue) throws InterruptedException;
- }
- private void executeTest(final TestStartStop testStartStop,
- DefaultNotificationQueue queue, WithTest test) throws InterruptedException{
-
- queue.startQueue();
- boolean started = testStartStop.waitForStartComplete(3000);
- assertEquals(started, true);
-
- test.test(queue);
-
- queue.stopQueue();
- boolean stopped = testStartStop.waitForStopComplete(3000);
- assertEquals(stopped, true);
- }
-
-
- public static class TestNotificationQueueModule extends AbstractModule {
- @Override
- protected void configure() {
-
- bind(Clock.class).to(ClockMock.class);
+ final NotificationQueue queueFred = notificationQueueService.createNotificationQueue("UtilTest", "Fred", new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
+ log.info("Fred received key: " + notificationKey);
+ expectedNotificationsFred.put(notificationKey, Boolean.TRUE);
+ eventsReceived++;
+ }
+ },
+ config);
- final MysqlTestingHelper helper = new MysqlTestingHelper();
- bind(MysqlTestingHelper.class).toInstance(helper);
- DBI dbi = helper.getDBI();
- bind(DBI.class).toInstance(dbi);
- /*
+ final NotificationQueue queueBarney = notificationQueueService.createNotificationQueue("UtilTest", "Barney", new NotificationQueueHandler() {
+ @Override
+ public void handleReadyNotification(String notificationKey, DateTime eventDateTime) {
+ log.info("Barney received key: " + notificationKey);
+ expectedNotificationsBarney.put(notificationKey, Boolean.TRUE);
+ eventsReceived++;
+ }
+ },
+ config);
+
+ queueFred.startQueue();
+// We don't start Barney so it can never pick up notifications
+
+
+ final UUID key = UUID.randomUUID();
+ final DummyObject obj = new DummyObject("foo", key);
+ final DateTime now = new DateTime();
+ final DateTime readyTime = now.plusMillis(2000);
+ final NotificationKey notificationKeyFred = new NotificationKey() {
+ @Override
+ public String toString() {
+ return "Fred" ;
+ }
+ };
+
+
+ final NotificationKey notificationKeyBarney = new NotificationKey() {
+ @Override
+ public String toString() {
+ return "Barney" ;
+ }
+ };
+
+ expectedNotificationsFred.put(notificationKeyFred.toString(), Boolean.FALSE);
+ expectedNotificationsFred.put(notificationKeyBarney.toString(), Boolean.FALSE);
+
+
+ // Insert dummy to be processed in 2 sec'
+ dao.inTransaction(new Transaction<Void, DummySqlTest>() {
+ @Override
+ public Void inTransaction(DummySqlTest transactional,
+ TransactionStatus status) throws Exception {
+
+ transactional.insertDummy(obj);
+ queueFred.recordFutureNotificationFromTransaction(transactional,
+ readyTime, notificationKeyFred);
+ log.info("posted key: " + notificationKeyFred.toString());
+ queueBarney.recordFutureNotificationFromTransaction(transactional,
+ readyTime, notificationKeyBarney);
+ log.info("posted key: " + notificationKeyBarney.toString());
+
+ return null;
+ }
+ });
+
+ // Move time in the future after the notification effectiveDate
+ ((ClockMock) clock).setDeltaFromReality(3000);
+
+ // Note the timeout is short on this test, but expected behaviour is that it times out.
+ // We are checking that the Fred queue does not pick up the Barney event
+ try {
+ await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return eventsReceived >= 2;
+ }
+ });
+ Assert.fail("There should only have been one event for the queue to pick up - it got more than that");
+ } catch (Exception e) {
+ // expected behavior
+ }
+
+ Assert.assertTrue(expectedNotificationsFred.get(notificationKeyFred.toString()));
+ Assert.assertFalse(expectedNotificationsFred.get(notificationKeyBarney.toString()));
+
+ }
+
+ NotificationConfig getNotificationConfig(final boolean off,
+ final long sleepTime, final int maxReadyEvents, final long claimTimeMs) {
+ return new NotificationConfig() {
+ @Override
+ public boolean isNotificationProcessingOff() {
+ return off;
+ }
+ @Override
+ public long getNotificationSleepTimeMs() {
+ return sleepTime;
+ }
+ @Override
+ public int getDaoMaxReadyEvents() {
+ return maxReadyEvents;
+ }
+ @Override
+ public long getDaoClaimTimeMs() {
+ return claimTimeMs;
+ }
+ };
+ }
+
+
+ public static class TestNotificationQueueModule extends AbstractModule {
+ @Override
+ protected void configure() {
+
+ bind(Clock.class).to(ClockMock.class);
+
+ final MysqlTestingHelper helper = new MysqlTestingHelper();
+ bind(MysqlTestingHelper.class).toInstance(helper);
+ IDBI dbi = helper.getDBI();
+ bind(IDBI.class).toInstance(dbi);
+ IDBI otherDbi = helper.getDBI();
+ bind(IDBI.class).annotatedWith(Names.named("global-lock")).toInstance(otherDbi);
+ /*
bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
bind(DbiConfig.class).toInstance(config);
- */
- }
- }
+ */
+ }
+ }
}
diff --git a/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
new file mode 100644
index 0000000..c913791
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/tag/TestTagStore.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.tag;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+import org.apache.commons.io.IOUtils;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.account.api.ControlTagType;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.DefaultClock;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.tag.dao.TagDefinitionDao;
+import com.ning.billing.util.tag.dao.TagStoreSqlDao;
+
+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;
+
+@Test(groups={"util"})
+public class TestTagStore {
+ private final static String ACCOUNT_TYPE = "ACCOUNT";
+ private final Clock clock = new DefaultClock();
+ private IDBI dbi;
+ private TagDefinition tag1;
+ private TagDefinition tag2;
+ private TagStoreModuleMock module;
+ private TagStoreSqlDao tagStoreSqlDao;
+ private TagDefinitionDao tagDefinitionDao;
+ private final Logger log = LoggerFactory.getLogger(TestTagStore.class);
+
+ @BeforeClass(alwaysRun = true)
+ protected void setup() throws IOException {
+ // Health check test to make sure MySQL is setup properly
+ try {
+ module = new TagStoreModuleMock();
+ final String utilDdl = IOUtils.toString(TestTagStore.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+
+ module.startDb();
+ module.initDb(utilDdl);
+
+ final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module, new MockClockModule());
+ dbi = injector.getInstance(IDBI.class);
+
+ tagStoreSqlDao = injector.getInstance(TagStoreSqlDao.class);
+ tagStoreSqlDao.test();
+
+ tagDefinitionDao = injector.getInstance(TagDefinitionDao.class);
+ tag1 = tagDefinitionDao.create("tag1", "First tag", "test");
+ tag2 = tagDefinitionDao.create("tag2", "Second tag", "test");
+ }
+ catch (Throwable t) {
+ log.error("Failed to start tag store tests", t);
+ fail(t.toString());
+ }
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void stopMysql()
+ {
+ module.stopDb();
+ }
+
+ private void saveTags(final TagStoreSqlDao dao, final String objectType, final String accountId, final List<Tag> tagList) {
+ dao.inTransaction(new Transaction<Void, TagStoreSqlDao>() {
+ @Override
+ public Void inTransaction(TagStoreSqlDao transactional,
+ TransactionStatus status) throws Exception {
+ dao.batchSaveFromTransaction(accountId.toString(), objectType, tagList);
+ return null;
+ }
+ });
+ }
+
+ @Test
+ public void testTagCreationAndRetrieval() {
+ UUID accountId = UUID.randomUUID();
+
+ TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+ Tag tag = new DescriptiveTag(tag2, "test", clock.getUTCNow());
+ tagStore.add(tag);
+
+ TagStoreSqlDao dao = dbi.onDemand(TagStoreSqlDao.class);
+ saveTags(dao, ACCOUNT_TYPE, accountId.toString(), tagStore.getEntityList());
+
+ List<Tag> savedTags = dao.load(accountId.toString(), ACCOUNT_TYPE);
+ assertEquals(savedTags.size(), 1);
+
+ Tag savedTag = savedTags.get(0);
+ assertEquals(savedTag.getAddedBy(), tag.getAddedBy());
+ assertEquals(savedTag.getAddedDate().compareTo(tag.getAddedDate()), 0);
+ assertEquals(savedTag.getTagDefinitionName(), tag.getTagDefinitionName());
+ assertEquals(savedTag.getId(), tag.getId());
+ }
+
+
+ @Test
+ public void testControlTagCreation() {
+ UUID accountId = UUID.randomUUID();
+ TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+
+ ControlTag tag = new DefaultControlTag("testUser", clock.getUTCNow(), ControlTagType.AUTO_INVOICING_OFF);
+ tagStore.add(tag);
+ assertEquals(tagStore.generateInvoice(), false);
+
+ List<Tag> tagList = tagStore.getEntityList();
+ saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
+
+ tagStore.clear();
+ assertEquals(tagStore.getEntityList().size(), 0);
+
+ tagList = tagStoreSqlDao.load(accountId.toString(), ACCOUNT_TYPE);
+ tagStore.add(tagList);
+ assertEquals(tagList.size(), 1);
+
+ assertEquals(tagStore.generateInvoice(), false);
+ }
+
+ @Test
+ public void testDescriptiveTagCreation() {
+ UUID accountId = UUID.randomUUID();
+ TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+
+ String definitionName = "SomeTestTag";
+ TagDefinition tagDefinition = null;
+ try {
+ tagDefinition = tagDefinitionDao.create(definitionName, "Test tag for some test purpose", "testUser");
+ } catch (TagDefinitionApiException e) {
+ fail("Tag definition creation failed.", e);
+ }
+
+ DescriptiveTag tag = new DescriptiveTag(tagDefinition, "testUser", clock.getUTCNow());
+ tagStore.add(tag);
+ assertEquals(tagStore.generateInvoice(), true);
+
+ List<Tag> tagList = tagStore.getEntityList();
+ saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
+
+ tagStore.clear();
+ assertEquals(tagStore.getEntityList().size(), 0);
+
+ tagList = tagStoreSqlDao.load(accountId.toString(), ACCOUNT_TYPE);
+ tagStore.add(tagList);
+ assertEquals(tagList.size(), 1);
+
+ assertEquals(tagStore.generateInvoice(), true);
+ }
+
+ @Test
+ public void testMixedTagCreation() {
+ UUID accountId = UUID.randomUUID();
+ TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+
+ String definitionName = "MixedTagTest";
+ TagDefinition tagDefinition = null;
+ try {
+ tagDefinition = tagDefinitionDao.create(definitionName, "Test tag for some test purpose", "testUser");
+ } catch (TagDefinitionApiException e) {
+ fail("Tag definition creation failed.", e);
+ }
+
+ DescriptiveTag descriptiveTag = new DescriptiveTag(tagDefinition, "testUser", clock.getUTCNow());
+ tagStore.add(descriptiveTag);
+ assertEquals(tagStore.generateInvoice(), true);
+
+ ControlTag controlTag = new DefaultControlTag("testUser", clock.getUTCNow(), ControlTagType.AUTO_INVOICING_OFF);
+ tagStore.add(controlTag);
+ assertEquals(tagStore.generateInvoice(), false);
+
+ List<Tag> tagList = tagStore.getEntityList();
+ saveTags(tagStoreSqlDao, ACCOUNT_TYPE, accountId.toString(), tagList);
+
+ tagStore.clear();
+ assertEquals(tagStore.getEntityList().size(), 0);
+
+ tagList = tagStoreSqlDao.load(accountId.toString(), ACCOUNT_TYPE);
+ tagStore.add(tagList);
+ assertEquals(tagList.size(), 2);
+
+ assertEquals(tagStore.generateInvoice(), false);
+ }
+
+ @Test
+ public void testControlTags() {
+ UUID accountId = UUID.randomUUID();
+ TagStore tagStore = new DefaultTagStore(accountId, ACCOUNT_TYPE);
+ assertEquals(tagStore.generateInvoice(), true);
+ assertEquals(tagStore.processPayment(), true);
+
+ ControlTag invoiceTag = new DefaultControlTag("testUser", clock.getUTCNow(), ControlTagType.AUTO_INVOICING_OFF);
+ tagStore.add(invoiceTag);
+ assertEquals(tagStore.generateInvoice(), false);
+ assertEquals(tagStore.processPayment(), true);
+
+ ControlTag paymentTag = new DefaultControlTag("testUser", clock.getUTCNow(), ControlTagType.AUTO_BILLING_OFF);
+ tagStore.add(paymentTag);
+ assertEquals(tagStore.generateInvoice(), false);
+ assertEquals(tagStore.processPayment(), false);
+ }
+
+ @Test(expectedExceptions = TagDefinitionApiException.class)
+ public void testTagDefinitionCreationWithControlTagName() throws TagDefinitionApiException {
+ String definitionName = ControlTagType.AUTO_BILLING_OFF.toString();
+ tagDefinitionDao.create(definitionName, "This should break", "test");
+ }
+
+ @Test
+ public void testTagDefinitionDeletionForUnusedDefinition() throws TagDefinitionApiException {
+ String definitionName = "TestTag1234";
+ tagDefinitionDao.create(definitionName, "Some test tag", "test");
+
+ TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+ assertNotNull(tagDefinition);
+
+ tagDefinitionDao.deleteTagDefinition(definitionName);
+ tagDefinition = tagDefinitionDao.getByName(definitionName);
+ assertNull(tagDefinition);
+ }
+
+ @Test(expectedExceptions = TagDefinitionApiException.class)
+ public void testTagDefinitionDeletionForDefinitionInUse() throws TagDefinitionApiException {
+ String definitionName = "TestTag12345";
+ tagDefinitionDao.create(definitionName, "Some test tag", "test");
+
+ TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+ assertNotNull(tagDefinition);
+
+ UUID objectId = UUID.randomUUID();
+ String objectType = "TestType";
+ TagStore tagStore = new DefaultTagStore(objectId, objectType);
+ Tag tag = new DescriptiveTag(tagDefinition, "test", clock.getUTCNow());
+ tagStore.add(tag);
+
+ saveTags(tagStoreSqlDao, objectType, objectId.toString(), tagStore.getEntityList());
+
+ List<Tag> tags = tagStoreSqlDao.load(objectId.toString(), objectType);
+ assertEquals(tags.size(), 1);
+
+ tagDefinitionDao.deleteTagDefinition(definitionName);
+ }
+
+ @Test
+ public void testDeleteAllTagsForDefinitionInUse() {
+ String definitionName = "TestTag1234567";
+ try {
+ tagDefinitionDao.create(definitionName, "Some test tag", "test");
+ } catch (TagDefinitionApiException e) {
+ fail("Could not create tag definition", e);
+ }
+
+ TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+ assertNotNull(tagDefinition);
+
+ UUID objectId = UUID.randomUUID();
+ String objectType = "TestType";
+ TagStore tagStore = new DefaultTagStore(objectId, objectType);
+ Tag tag = new DescriptiveTag(tagDefinition, "test", clock.getUTCNow());
+ tagStore.add(tag);
+
+ saveTags(tagStoreSqlDao, objectType, objectId.toString(), tagStore.getEntityList());
+
+ List<Tag> tags = tagStoreSqlDao.load(objectId.toString(), objectType);
+ assertEquals(tags.size(), 1);
+
+ try {
+ tagDefinitionDao.deleteAllTagsForDefinition(definitionName);
+ } catch (TagDefinitionApiException e) {
+ fail("Could not delete tags for tag definition", e);
+ }
+
+ try {
+ tagDefinitionDao.deleteTagDefinition(definitionName);
+ } catch (TagDefinitionApiException e) {
+ fail("Could not delete tag definition", e);
+ }
+ }
+
+ @Test
+ public void testDeleteAllTagsForDefinitionNotInUse() {
+ String definitionName = "TestTag4321";
+ try {
+ tagDefinitionDao.create(definitionName, "Some test tag", "test");
+ } catch (TagDefinitionApiException e) {
+ fail("Could not create tag definition", e);
+ }
+
+ TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+ assertNotNull(tagDefinition);
+
+ try {
+ tagDefinitionDao.deleteAllTagsForDefinition(definitionName);
+ } catch (TagDefinitionApiException e) {
+ fail("Could not delete tags for tag definition", e);
+ }
+
+ try {
+ tagDefinitionDao.deleteTagDefinition(definitionName);
+ } catch (TagDefinitionApiException e) {
+ fail("Could not delete tag definition", e);
+ }
+ }
+
+ @Test(expectedExceptions = TagDefinitionApiException.class)
+ public void testDeleteAllTagsForDefinitionWithWrongName() throws TagDefinitionApiException {
+ String definitionName = "TestTag654321";
+ String wrongDefinitionName = "TestTag564321";
+ try {
+ tagDefinitionDao.create(definitionName, "Some test tag", "test");
+ } catch (TagDefinitionApiException e) {
+ fail("Could not create tag definition", e);
+ }
+
+ TagDefinition tagDefinition = tagDefinitionDao.getByName(definitionName);
+ assertNotNull(tagDefinition);
+
+ tagDefinitionDao.deleteAllTagsForDefinition(wrongDefinitionName);
+
+ try {
+ tagDefinitionDao.deleteTagDefinition(definitionName);
+ } catch (TagDefinitionApiException e) {
+ fail("Could not delete tag definition", e);
+ }
+ }
+
+ @Test
+ public void testGetTagDefinitions() {
+ List<TagDefinition> definitionList = tagDefinitionDao.getTagDefinitions();
+ assertTrue(definitionList.size() >= ControlTagType.values().length);
+ }
+}