killbill-uncached
Changes
account/pom.xml 2(+1 -1)
analytics/pom.xml 2(+1 -1)
api/pom.xml 2(+1 -1)
beatrix/pom.xml 2(+1 -1)
catalog/pom.xml 2(+1 -1)
entitlement/pom.xml 2(+1 -1)
invoice/pom.xml 2(+1 -1)
jaxrs/pom.xml 2(+1 -1)
junction/pom.xml 2(+1 -1)
junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java 34(+28 -6)
junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java 5(+3 -2)
overdue/pom.xml 2(+1 -1)
payment/pom.xml 2(+1 -1)
pom.xml 2(+1 -1)
server/pom.xml 2(+1 -1)
usage/pom.xml 2(+1 -1)
util/pom.xml 2(+1 -1)
Details
account/pom.xml 2(+1 -1)
diff --git a/account/pom.xml b/account/pom.xml
index f20b6b4..433a057 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.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-account</artifactId>
analytics/pom.xml 2(+1 -1)
diff --git a/analytics/pom.xml b/analytics/pom.xml
index 114a533..716bee7 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.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-analytics</artifactId>
api/pom.xml 2(+1 -1)
diff --git a/api/pom.xml b/api/pom.xml
index 0270f7d..d2dcc04 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.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-api</artifactId>
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index e96af52..e6d853c 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -186,11 +186,13 @@ public enum ErrorCode {
INVOICE_TARGET_DATE_TOO_FAR_IN_THE_FUTURE(4005, "The target date was too far in the future. Target Date: %s"),
INVOICE_NOT_FOUND(4006, "No invoice could be found for id %s."),
INVOICE_NOTHING_TO_DO(4007, "No invoice to generate for account %s and date %s"),
- INVOICE_NO_SUCH_CREDIT(4008, "Credit Item for id %s does not exist"),
+ INVOICE_NO_SUCH_CREDIT(4008, "Credit item for id %s does not exist"),
CREDIT_AMOUNT_INVALID(4009, "Credit amount %s should be strictly positive"),
INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID(4010, "Invoice adjustment amount %s should be strictly positive"),
INVOICE_ITEM_NOT_FOUND(4011, "No invoice item could be found for id %s."),
INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT(4012, "Invoice item %s doesn't belong to invoice %s."),
+ INVOICE_NO_SUCH_EXTERNAL_CHARGE(4014, "External charge item for id %s does not exist"),
+ EXTERNAL_CHARGE_AMOUNT_INVALID(4015, "External charge amount %s should be strictly positive"),
/*
*
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
index 63cd82a..ee3aa05 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
@@ -17,6 +17,8 @@
package com.ning.billing.invoice.api;
public enum InvoiceItemType {
+ // Fixed (one-time) external charge (not part of the catalog)
+ EXTERNAL_CHARGE,
// Fixed (one-time) charge
FIXED,
// Recurring charge
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 74fa8e3..c8dd494 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
@@ -120,6 +120,46 @@ public interface InvoiceUserApi {
public void tagInvoiceAsNotWrittenOff(UUID invoiceId, CallContext context) throws TagApiException;
/**
+ * Retrieve an external charge by id.
+ *
+ * @param externalChargeId external charge id
+ * @return the external charge
+ * @throws InvoiceApiException
+ */
+ public InvoiceItem getExternalChargeById(UUID externalChargeId) throws InvoiceApiException;
+
+ /**
+ * Add an external charge to an account.
+ *
+ * @param accountId account id
+ * @param amount the external charge amount
+ * @param description a description for that charge
+ * @param effectiveDate the day to post the external charge, in the account timezone
+ * @param currency the external charge currency
+ * @param context the call context
+ * @return the external charge invoice item
+ * @throws InvoiceApiException
+ */
+ public InvoiceItem insertExternalCharge(UUID accountId, BigDecimal amount, String description, LocalDate effectiveDate,
+ Currency currency, CallContext context) throws InvoiceApiException;
+
+ /**
+ * Add an external charge to an invoice.
+ *
+ * @param accountId account id
+ * @param invoiceId invoice id
+ * @param amount the external charge amount
+ * @param description a description for that charge
+ * @param effectiveDate the day to post the external charge, in the account timezone
+ * @param currency the external charge currency
+ * @param context the call context
+ * @return the external charge invoice item
+ * @throws InvoiceApiException
+ */
+ public InvoiceItem insertExternalChargeForInvoice(UUID accountId, UUID invoiceId, BigDecimal amount, String description,
+ LocalDate effectiveDate, Currency currency, CallContext context) throws InvoiceApiException;
+
+ /**
* Retrieve a credit by id.
*
* @param creditId credit id
beatrix/pom.xml 2(+1 -1)
diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 560a944..0dd6788 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.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
index de00151..f6c2124 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
@@ -21,9 +21,6 @@ import static org.testng.Assert.assertTrue;
import java.math.BigDecimal;
import java.util.List;
-import java.util.UUID;
-
-import junit.framework.Assert;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
@@ -32,6 +29,7 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.BillCycleDay;
import com.ning.billing.api.TestApiListener.NextEvent;
import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedItemCheck;
import com.ning.billing.catalog.api.BillingPeriod;
@@ -103,15 +101,15 @@ public class TestBundleTransfer extends TestIntegrationBase {
final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
assertEquals(invoiceItems.size(), 1);
InvoiceItem theItem = invoiceItems.get(0);
- Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,5,11)) == 0);
- Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2013,5,11)) == 0);
- Assert.assertTrue(theItem.getAmount().compareTo(new BigDecimal("2399.9500")) == 0);
+ assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,5,11)) == 0);
+ assertTrue(theItem.getEndDate().compareTo(new LocalDate(2013,5,11)) == 0);
+ assertTrue(theItem.getAmount().compareTo(new BigDecimal("2399.9500")) == 0);
}
@Test(groups = "slow")
public void testBundleTransferWithBPMonthlyOnly() throws Exception {
- final Account account = createAccountWithPaymentMethod(getAccountData(9));
+ final Account account = createAccountWithPaymentMethod(getAccountData(0));
// Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
@@ -148,7 +146,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
assertListenerStatus();
// BUNDLE TRANSFER
- final Account newAccount = createAccountWithPaymentMethod(getAccountData(15));
+ final Account newAccount = createAccountWithPaymentMethod(getAccountData(0));
busHandler.pushExpectedEvent(NextEvent.TRANSFER);
busHandler.pushExpectedEvent(NextEvent.INVOICE);
@@ -157,15 +155,22 @@ public class TestBundleTransfer extends TestIntegrationBase {
assertTrue(busHandler.isCompleted(DELAY));
assertListenerStatus();
- List<Invoice> invoices =invoiceUserApi.getInvoicesByAccount(newAccount.getId());
+ // Verify the BCD of the new account
+ final BillCycleDay oldBCD = accountUserApi.getAccountById(account.getId()).getBillCycleDay();
+ final BillCycleDay newBCD = accountUserApi.getAccountById(newAccount.getId()).getBillCycleDay();
+ assertEquals(oldBCD.getDayOfMonthUTC(), 1);
+ // Day of the transfer
+ assertEquals(newBCD.getDayOfMonthUTC(), 3);
+
+ final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(newAccount.getId());
assertEquals(invoices.size(), 1);
final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
assertEquals(invoiceItems.size(), 1);
- InvoiceItem theItem = invoiceItems.get(0);
- Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,5,3)) == 0);
- Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2012,5,15)) == 0);
- Assert.assertTrue(theItem.getAmount().compareTo(new BigDecimal("99.98")) == 0);
+ final InvoiceItem theItem = invoiceItems.get(0);
+ assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012, 5, 3)) == 0);
+ assertTrue(theItem.getEndDate().compareTo(new LocalDate(2012, 6, 3)) == 0);
+ assertTrue(theItem.getAmount().compareTo(new BigDecimal("249.95")) == 0);
}
@Test(groups = "slow")
catalog/pom.xml 2(+1 -1)
diff --git a/catalog/pom.xml b/catalog/pom.xml
index 3d0e54c..9deb122 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.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-catalog</artifactId>
entitlement/pom.xml 2(+1 -1)
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index ccf0a6f..7df9de5 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.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-entitlement</artifactId>
invoice/pom.xml 2(+1 -1)
diff --git a/invoice/pom.xml b/invoice/pom.xml
index 0ff62d2..78b0dd0 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.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-invoice</artifactId>
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 927db0b..012bdf2 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
@@ -40,6 +40,7 @@ import com.ning.billing.invoice.api.InvoicePayment;
import com.ning.billing.invoice.api.InvoiceUserApi;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
+import com.ning.billing.invoice.model.ExternalChargeInvoiceItem;
import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
import com.ning.billing.util.api.TagApiException;
import com.ning.billing.util.api.TagUserApi;
@@ -134,12 +135,42 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
}
@Override
+ public InvoiceItem getExternalChargeById(final UUID externalChargeId) throws InvoiceApiException {
+ final InvoiceItem externalChargeItem = dao.getExternalChargeById(externalChargeId);
+ if (externalChargeItem == null) {
+ throw new InvoiceApiException(ErrorCode.INVOICE_NO_SUCH_EXTERNAL_CHARGE, externalChargeId);
+ }
+
+ return new ExternalChargeInvoiceItem(externalChargeItem.getId(), externalChargeItem.getInvoiceId(), externalChargeItem.getAccountId(),
+ externalChargeItem.getPlanName(), externalChargeItem.getStartDate(),
+ externalChargeItem.getAmount(), externalChargeItem.getCurrency());
+ }
+
+ @Override
+ public InvoiceItem insertExternalCharge(final UUID accountId, final BigDecimal amount, @Nullable final String description,
+ final LocalDate effectiveDate, final Currency currency, final CallContext context) throws InvoiceApiException {
+ return insertExternalChargeForInvoice(accountId, null, amount, description, effectiveDate, currency, context);
+ }
+
+ @Override
+ public InvoiceItem insertExternalChargeForInvoice(final UUID accountId, final UUID invoiceId, final BigDecimal amount, @Nullable final String description,
+ final LocalDate effectiveDate, final Currency currency, final CallContext context) throws InvoiceApiException {
+ if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new InvoiceApiException(ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID, amount);
+ }
+
+ return dao.insertExternalCharge(accountId, invoiceId, description, amount, effectiveDate, currency, context);
+ }
+
+ @Override
public InvoiceItem getCreditById(final UUID creditId) throws InvoiceApiException {
final InvoiceItem creditItem = dao.getCreditById(creditId);
if (creditItem == null) {
throw new InvoiceApiException(ErrorCode.INVOICE_NO_SUCH_CREDIT, creditId);
}
- return new CreditAdjInvoiceItem(creditItem.getId(), creditItem.getInvoiceId(), creditItem.getAccountId(), creditItem.getStartDate(), creditItem.getAmount().negate(), creditItem.getCurrency());
+
+ return new CreditAdjInvoiceItem(creditItem.getId(), creditItem.getInvoiceId(), creditItem.getAccountId(),
+ creditItem.getStartDate(), creditItem.getAmount().negate(), creditItem.getCurrency());
}
@Override
@@ -154,11 +185,13 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new InvoiceApiException(ErrorCode.CREDIT_AMOUNT_INVALID, amount);
}
+
return dao.insertCredit(accountId, invoiceId, amount, effectiveDate, currency, context);
}
@Override
- public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final LocalDate effectiveDate, final CallContext context) throws InvoiceApiException {
+ public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
+ final LocalDate effectiveDate, final CallContext context) throws InvoiceApiException {
return insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, null, null, context);
}
@@ -169,6 +202,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
if (amount != null && amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID, amount);
}
+
return dao.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, amount, currency, context);
}
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 4fe738e..f6a0e95 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
@@ -44,6 +44,7 @@ import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.invoice.model.ExternalChargeInvoiceItem;
import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.invoice.model.RefundAdjInvoiceItem;
@@ -525,12 +526,42 @@ public class DefaultInvoiceDao implements InvoiceDao {
}
@Override
+ public InvoiceItem getExternalChargeById(final UUID externalChargeId) throws InvoiceApiException {
+ return invoiceItemSqlDao.getById(externalChargeId.toString());
+ }
+
+ @Override
+ public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, final String description,
+ final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context) {
+ return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
+ @Override
+ public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+ UUID invoiceIdForExternalCharge = invoiceId;
+ // Create an invoice for that external charge if it doesn't exist
+ if (invoiceIdForExternalCharge == null) {
+ final Invoice invoiceForExternalCharge = new DefaultInvoice(accountId, effectiveDate, effectiveDate, currency);
+ transactional.create(invoiceForExternalCharge, context);
+ invoiceIdForExternalCharge = invoiceForExternalCharge.getId();
+ }
+
+ final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceIdForExternalCharge, accountId, description,
+ effectiveDate, amount, currency);
+
+ final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
+ transInvoiceItemDao.create(externalCharge, context);
+
+ return externalCharge;
+ }
+ });
+ }
+
+ @Override
public InvoiceItem getCreditById(final UUID creditId) throws InvoiceApiException {
return invoiceItemSqlDao.getById(creditId.toString());
}
@Override
- public InvoiceItem insertCredit(final UUID accountId, final UUID invoiceId, final BigDecimal positiveCreditAmount,
+ public InvoiceItem insertCredit(final UUID accountId, @Nullable final UUID invoiceId, final BigDecimal positiveCreditAmount,
final LocalDate effectiveDate, final Currency currency, final CallContext context) {
return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
@Override
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 a9104f9..0c9b60e 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
@@ -74,7 +74,6 @@ public interface InvoiceDao {
/**
* Create a refund.
*
- *
* @param paymentId payment associated with that refund
* @param amount amount to refund
* @param isInvoiceAdjusted whether the refund should trigger an invoice or invoice item adjustment
@@ -97,9 +96,51 @@ public interface InvoiceDao {
InvoicePayment getChargebackById(final UUID chargebackId) throws InvoiceApiException;
+ /**
+ * Retrieve am external charge by id.
+ *
+ * @param externalChargeId the external charge id
+ * @return the external charge invoice item
+ * @throws InvoiceApiException
+ */
+ InvoiceItem getExternalChargeById(final UUID externalChargeId) throws InvoiceApiException;
+
+ /**
+ * Add an external charge to a given account and invoice. If invoiceId is null, a new invoice will be created.
+ *
+ * @param accountId the account id
+ * @param invoiceId the invoice id
+ * @param description a description for that charge
+ * @param amount the external charge amount
+ * @param effectiveDate the day to post the external charge, in the account timezone
+ * @param currency the external charge currency
+ * @param context the call context
+ * @return the newly created external charge invoice item
+ */
+ InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final String description,
+ final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context);
+
+ /**
+ * Retrieve a credit by id.
+ *
+ * @param creditId the credit id
+ * @return the credit invoice item
+ * @throws InvoiceApiException
+ */
InvoiceItem getCreditById(final UUID creditId) throws InvoiceApiException;
- InvoiceItem insertCredit(final UUID accountId, final UUID invoiceId, final BigDecimal amount,
+ /**
+ * Add a credit to a given account and invoice. If invoiceId is null, a new invoice will be created.
+ *
+ * @param accountId the account id
+ * @param invoiceId the invoice id
+ * @param amount the credit amount
+ * @param effectiveDate the day to grant the credit, in the account timezone
+ * @param currency the credit currency
+ * @param context the call context
+ * @return the newly created credit invoice item
+ */
+ InvoiceItem insertCredit(final UUID accountId, @Nullable final UUID invoiceId, final BigDecimal amount,
final LocalDate effectiveDate, final Currency currency, final CallContext context);
/**
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 8156f30..d3758c4 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -46,6 +46,7 @@ import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.api.InvoiceItemType;
import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
+import com.ning.billing.invoice.model.ExternalChargeInvoiceItem;
import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
@@ -132,6 +133,9 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
InvoiceItem item = null;
switch (type) {
+ case EXTERNAL_CHARGE:
+ item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, planName, startDate, amount, currency);
+ break;
case FIXED:
item = new FixedPriceInvoiceItem(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, amount, currency);
break;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index 1a08fd7..1c45243 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -97,7 +97,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
if (existingInvoices != null) {
for (final Invoice invoice : existingInvoices) {
for (final InvoiceItem item : invoice.getInvoiceItems()) {
- if (item.getSubscriptionId() == null || // Always include migration invoices, credits etc.
+ if (item.getSubscriptionId() == null || // Always include migration invoices, credits, external charged etc.
!events.getSubscriptionIdsWithAutoInvoiceOff()
.contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag
existingItems.add(item);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
new file mode 100644
index 0000000..ae5dbae
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -0,0 +1,139 @@
+/*
+ * 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.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+
+public class ExternalChargeInvoiceItem extends InvoiceItemBase {
+
+ public ExternalChargeInvoiceItem(final UUID invoiceId, final UUID accountId, final String description, final LocalDate date,
+ final BigDecimal amount, final Currency currency) {
+ super(invoiceId, accountId, null, null, description, null, date, null, amount, currency);
+ }
+
+ public ExternalChargeInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, final String description,
+ final LocalDate date, final BigDecimal amount, final Currency currency) {
+ super(id, invoiceId, accountId, null, (UUID) null, description, null, date, null, amount, currency);
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format("%s (external charge) on %s", getPlanName(), getStartDate().toString());
+ }
+
+ @Override
+ public int hashCode() {
+ int result = accountId.hashCode();
+ result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+ result = 31 * result + (bundleId != null ? bundleId.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.hashCode();
+ result = 31 * result + currency.hashCode();
+ return result;
+ }
+
+ @Override
+ public int compareTo(final InvoiceItem item) {
+ if (!(item instanceof ExternalChargeInvoiceItem)) {
+ return 1;
+ }
+
+ final ExternalChargeInvoiceItem that = (ExternalChargeInvoiceItem) item;
+ final int compareAccounts = getAccountId().compareTo(that.getAccountId());
+ if (compareAccounts == 0) {
+ return getStartDate().compareTo(that.getStartDate());
+ } else {
+ return compareAccounts;
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+ sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+ sb.append("accountId = ").append(accountId.toString()).append(", ");
+ sb.append("description = ").append(planName).append(", ");
+ sb.append("startDate = ").append(startDate.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(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final ExternalChargeInvoiceItem that = (ExternalChargeInvoiceItem) o;
+ if (accountId.compareTo(that.accountId) != 0) {
+ return false;
+ }
+ if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
+ return false;
+ }
+ if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+ return false;
+ }
+ 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;
+ }
+
+ return true;
+ }
+
+ @Override
+ public InvoiceItemType getInvoiceItemType() {
+ return InvoiceItemType.EXTERNAL_CHARGE;
+ }
+}
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 616e935..e505d9e 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
@@ -53,7 +53,7 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
}
public BigDecimal getChargedAmount() {
- return getAmoutForItems(InvoiceItemType.RECURRING, InvoiceItemType.FIXED, InvoiceItemType.REPAIR_ADJ);
+ return getAmoutForItems(InvoiceItemType.EXTERNAL_CHARGE, InvoiceItemType.RECURRING, InvoiceItemType.FIXED, InvoiceItemType.REPAIR_ADJ);
}
public BigDecimal getCBAAmount() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index 2de15d7..23a3445 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -50,6 +50,63 @@ public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
}
@Test(groups = "slow")
+ public void testPostExternalChargeOnNewInvoice() throws Exception {
+ // Initial account balance
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+
+ // Post an external charge
+ final BigDecimal externalChargeAmount = BigDecimal.TEN;
+ final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharge(accountId, externalChargeAmount, UUID.randomUUID().toString(),
+ clock.getUTCToday(), accountCurrency, context);
+ Assert.assertNotNull(externalChargeInvoiceItem.getInvoiceId());
+ Assert.assertNotEquals(externalChargeInvoiceItem.getInvoiceId(), invoiceId);
+ Assert.assertEquals(externalChargeInvoiceItem.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
+ Assert.assertEquals(externalChargeInvoiceItem.getAccountId(), accountId);
+ Assert.assertEquals(externalChargeInvoiceItem.getAmount(), externalChargeAmount);
+ Assert.assertEquals(externalChargeInvoiceItem.getCurrency(), accountCurrency);
+ Assert.assertNull(externalChargeInvoiceItem.getLinkedItemId());
+
+ // Verify the adjusted invoice balance
+ final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(externalChargeInvoiceItem.getInvoiceId()).getBalance();
+ Assert.assertEquals(adjustedInvoiceBalance.compareTo(externalChargeAmount), 0);
+
+ // Verify the adjusted account balance
+ final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(adjustedAccountBalance, accountBalance.add(externalChargeAmount));
+ }
+
+ @Test(groups = "slow")
+ public void testPostExternalChargeOnExistingInvoice() throws Exception {
+ // Verify the initial invoice balance
+ final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+ // Verify the initial account balance
+ final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(accountBalance, invoiceBalance);
+
+ // Post an external charge
+ final BigDecimal externalChargeAmount = BigDecimal.TEN;
+ final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalChargeForInvoice(accountId, invoiceId,
+ externalChargeAmount, UUID.randomUUID().toString(),
+ clock.getUTCToday(), accountCurrency, context);
+ Assert.assertEquals(externalChargeInvoiceItem.getInvoiceId(), invoiceId);
+ Assert.assertEquals(externalChargeInvoiceItem.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
+ Assert.assertEquals(externalChargeInvoiceItem.getAccountId(), accountId);
+ Assert.assertEquals(externalChargeInvoiceItem.getAmount(), externalChargeAmount);
+ Assert.assertEquals(externalChargeInvoiceItem.getCurrency(), accountCurrency);
+ Assert.assertNull(externalChargeInvoiceItem.getLinkedItemId());
+
+ // Verify the adjusted invoice balance
+ final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+ Assert.assertEquals(adjustedInvoiceBalance.compareTo(invoiceBalance.add(externalChargeAmount)), 0);
+
+ // Verify the adjusted account balance
+ final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+ Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+ }
+
+ @Test(groups = "slow")
public void testAdjustFullInvoice() throws Exception {
// Verify the initial invoice balance
final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
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
index 301866c..a7884b5 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -259,6 +259,16 @@ public class MockInvoiceDao implements InvoiceDao {
}
@Override
+ public InvoiceItem getExternalChargeById(final UUID externalChargeId) throws InvoiceApiException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final String description, final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public InvoiceItem getCreditById(final UUID creditId) throws InvoiceApiException {
throw new UnsupportedOperationException();
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java
index 4c98b52..4bf050e 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java
@@ -20,7 +20,6 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
-import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.testng.annotations.Test;
@@ -28,6 +27,7 @@ import com.ning.billing.catalog.api.Currency;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.ExternalChargeInvoiceItem;
import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
@@ -158,4 +158,18 @@ public class TestInvoiceItemDao extends InvoiceDaoTestBase {
final InvoiceItem savedItem = invoiceItemSqlDao.getById(fixedPriceInvoiceItem.getId().toString());
assertEquals(savedItem, fixedPriceInvoiceItem);
}
+
+ @Test(groups = "slow")
+ public void testExternalChargeInvoiceSqlDao() throws Exception {
+ final UUID invoiceId = UUID.randomUUID();
+ final UUID accountId = UUID.randomUUID();
+ final String description = UUID.randomUUID().toString();
+ final LocalDate startDate = new LocalDate(2012, 4, 1);
+ final InvoiceItem externalChargeInvoiceItem = new ExternalChargeInvoiceItem(invoiceId, accountId, description,
+ startDate, TEN, Currency.USD);
+ invoiceItemSqlDao.create(externalChargeInvoiceItem, context);
+
+ final InvoiceItem savedItem = invoiceItemSqlDao.getById(externalChargeInvoiceItem.getId().toString());
+ assertEquals(savedItem, externalChargeInvoiceItem);
+ }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/model/TestExternalChargeInvoiceItem.java b/invoice/src/test/java/com/ning/billing/invoice/model/TestExternalChargeInvoiceItem.java
new file mode 100644
index 0000000..cd59851
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/model/TestExternalChargeInvoiceItem.java
@@ -0,0 +1,72 @@
+/*
+ * 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.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestExternalChargeInvoiceItem {
+
+ private final Clock clock = new ClockMock();
+
+ @Test(groups = "fast")
+ public void testEquals() throws Exception {
+ final UUID id = UUID.randomUUID();
+ final UUID invoiceId = UUID.randomUUID();
+ final UUID accountId = UUID.randomUUID();
+ final String description = UUID.randomUUID().toString();
+ final LocalDate effectiveDate = clock.getUTCToday();
+ final BigDecimal amount = BigDecimal.TEN;
+ final Currency currency = Currency.GBP;
+ final ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, description,
+ effectiveDate, amount, currency);
+ Assert.assertEquals(item.getAccountId(), accountId);
+ Assert.assertEquals(item.getAmount(), amount);
+ Assert.assertEquals(item.getCurrency(), currency);
+ Assert.assertEquals(item.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
+ Assert.assertEquals(item.getPlanName(), description);
+ Assert.assertNull(item.getBundleId());
+ Assert.assertNull(item.getEndDate());
+ Assert.assertNull(item.getLinkedItemId());
+ Assert.assertNull(item.getPhaseName());
+ Assert.assertNull(item.getRate());
+ Assert.assertNull(item.getSubscriptionId());
+
+ Assert.assertEquals(item, item);
+
+ final ExternalChargeInvoiceItem otherItem = new ExternalChargeInvoiceItem(id, invoiceId, UUID.randomUUID(), description,
+ effectiveDate, amount, currency);
+ Assert.assertNotEquals(otherItem, item);
+
+ // Check comparison (done by start date)
+ final ExternalChargeInvoiceItem itemBefore = new ExternalChargeInvoiceItem(id, invoiceId, accountId, description,
+ effectiveDate.minusDays(1), amount, currency);
+ Assert.assertEquals(itemBefore.compareTo(item), -1);
+ final ExternalChargeInvoiceItem itemAfter = new ExternalChargeInvoiceItem(id, invoiceId, accountId, description,
+ effectiveDate.plusDays(1), amount, currency);
+ Assert.assertEquals(itemAfter.compareTo(item), 1);
+ }
+}
jaxrs/pom.xml 2(+1 -1)
diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 0963879..684e6fc 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-jaxrs</artifactId>
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
index 05f405f..a83e8b7 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
@@ -29,33 +29,25 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class AccountJson extends AccountJsonSimple {
- // STEPH Missing city, locale, postalCode from https://home.ninginc.com:8443/display/REVINFRA/Killbill+1.0+APIs
private final String name;
-
private final Integer length;
-
private final String email;
-
private final BillCycleDayJson billCycleDayJson;
-
private final String currency;
-
private final String paymentMethodId;
-
private final String timeZone;
-
private final String address1;
-
private final String address2;
-
+ private final String postalCode;
private final String company;
-
+ private final String city;
private final String state;
-
private final String country;
-
+ private final String locale;
private final String phone;
+ private final Boolean isMigrated;
+ private final Boolean isNotifiedForInvoices;
public AccountJson(final Account account) {
super(account.getId().toString(), account.getExternalKey());
@@ -68,10 +60,15 @@ public class AccountJson extends AccountJsonSimple {
this.timeZone = account.getTimeZone().toString();
this.address1 = account.getAddress1();
this.address2 = account.getAddress2();
+ this.postalCode = account.getPostalCode();
this.company = account.getCompanyName();
+ this.city = account.getCity();
this.state = account.getStateOrProvince();
this.country = account.getCountry();
+ this.locale = account.getLocale();
this.phone = account.getPhone();
+ this.isMigrated = account.isMigrated();
+ this.isNotifiedForInvoices = account.isNotifiedForInvoices();
}
public AccountData toAccountData() {
@@ -88,7 +85,7 @@ public class AccountJson extends AccountJsonSimple {
@Override
public String getPostalCode() {
- return null;
+ return postalCode;
}
@Override
@@ -98,12 +95,12 @@ public class AccountJson extends AccountJsonSimple {
@Override
public Boolean isMigrated() {
- return false;
+ return isMigrated;
}
@Override
public Boolean isNotifiedForInvoices() {
- return false;
+ return isNotifiedForInvoices;
}
@Override
@@ -118,8 +115,7 @@ public class AccountJson extends AccountJsonSimple {
@Override
public String getLocale() {
- // TODO
- return "en";
+ return locale;
}
@Override
@@ -154,7 +150,7 @@ public class AccountJson extends AccountJsonSimple {
@Override
public String getCity() {
- return null;
+ return city;
}
@Override
@@ -200,10 +196,15 @@ public class AccountJson extends AccountJsonSimple {
@JsonProperty("timezone") final String timeZone,
@JsonProperty("address1") final String address1,
@JsonProperty("address2") final String address2,
+ @JsonProperty("postalCode") final String postalCode,
@JsonProperty("company") final String company,
+ @JsonProperty("city") final String city,
@JsonProperty("state") final String state,
@JsonProperty("country") final String country,
- @JsonProperty("phone") final String phone) {
+ @JsonProperty("locale") final String locale,
+ @JsonProperty("phone") final String phone,
+ @JsonProperty("isMigrated") final Boolean isMigrated,
+ @JsonProperty("isNotifiedForInvoices") final Boolean isNotifiedForInvoices) {
super(accountId, externalKey);
this.name = name;
this.length = length;
@@ -214,10 +215,15 @@ public class AccountJson extends AccountJsonSimple {
this.timeZone = timeZone;
this.address1 = address1;
this.address2 = address2;
+ this.postalCode = postalCode;
this.company = company;
+ this.city = city;
this.state = state;
this.country = country;
+ this.locale = locale;
this.phone = phone;
+ this.isMigrated = isMigrated;
+ this.isNotifiedForInvoices = isNotifiedForInvoices;
}
public String getName() {
@@ -256,10 +262,18 @@ public class AccountJson extends AccountJsonSimple {
return address2;
}
+ public String getPostalCode() {
+ return postalCode;
+ }
+
public String getCompany() {
return company;
}
+ public String getCity() {
+ return city;
+ }
+
public String getState() {
return state;
}
@@ -268,167 +282,152 @@ public class AccountJson extends AccountJsonSimple {
return country;
}
+ public String getLocale() {
+ return locale;
+ }
+
public String getPhone() {
return phone;
}
+ @JsonProperty("isMigrated")
+ public Boolean isMigrated() {
+ return isMigrated;
+ }
+
+ @JsonProperty("isNotifiedForInvoices")
+ public Boolean isNotifiedForInvoices() {
+ return isNotifiedForInvoices;
+ }
+
@Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result
- + ((accountId == null) ? 0 : accountId.hashCode());
- result = prime * result
- + ((address1 == null) ? 0 : address1.hashCode());
- result = prime * result
- + ((address2 == null) ? 0 : address2.hashCode());
- result = prime * result
- + ((billCycleDayJson == null) ? 0 : billCycleDayJson.hashCode());
- result = prime * result + ((company == null) ? 0 : company.hashCode());
- result = prime * result + ((country == null) ? 0 : country.hashCode());
- result = prime * result
- + ((currency == null) ? 0 : currency.hashCode());
- result = prime * result + ((email == null) ? 0 : email.hashCode());
- result = prime * result
- + ((externalKey == null) ? 0 : externalKey.hashCode());
- result = prime * result + ((length == null) ? 0 : length.hashCode());
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- result = prime * result
- + ((paymentMethodId == null) ? 0 : paymentMethodId.hashCode());
- result = prime * result + ((phone == null) ? 0 : phone.hashCode());
- result = prime * result + ((state == null) ? 0 : state.hashCode());
- result = prime * result
- + ((timeZone == null) ? 0 : timeZone.hashCode());
- return result;
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("AccountJson");
+ sb.append("{name='").append(name).append('\'');
+ sb.append(", length=").append(length);
+ sb.append(", email='").append(email).append('\'');
+ sb.append(", billCycleDayJson=").append(billCycleDayJson);
+ sb.append(", currency='").append(currency).append('\'');
+ sb.append(", paymentMethodId='").append(paymentMethodId).append('\'');
+ sb.append(", timeZone='").append(timeZone).append('\'');
+ sb.append(", address1='").append(address1).append('\'');
+ sb.append(", address2='").append(address2).append('\'');
+ sb.append(", postalCode='").append(postalCode).append('\'');
+ sb.append(", company='").append(company).append('\'');
+ sb.append(", city='").append(city).append('\'');
+ sb.append(", state='").append(state).append('\'');
+ sb.append(", country='").append(country).append('\'');
+ sb.append(", locale='").append(locale).append('\'');
+ sb.append(", phone='").append(phone).append('\'');
+ sb.append(", isMigrated=").append(isMigrated);
+ sb.append(", isNotifiedForInvoices=").append(isNotifiedForInvoices);
+ sb.append('}');
+ return sb.toString();
}
- // Used to check POST versus GET
- public boolean equalsNoId(final Object obj) {
- if (this == obj) {
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
return true;
}
- if (obj == null) {
+ if (o == null || getClass() != o.getClass()) {
return false;
}
- if (getClass() != obj.getClass()) {
+
+ final AccountJson that = (AccountJson) o;
+
+ if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
return false;
+ } else {
+ return equalsNoId(that);
}
- final AccountJson other = (AccountJson) obj;
- if (address1 == null) {
- if (other.address1 != null) {
- return false;
- }
- } else if (!address1.equals(other.address1)) {
+ }
+
+ // Used to check POST versus GET
+ public boolean equalsNoId(final AccountJson that) {
+ if (address1 != null ? !address1.equals(that.address1) : that.address1 != null) {
return false;
}
- if (address2 == null) {
- if (other.address2 != null) {
- return false;
- }
- } else if (!address2.equals(other.address2)) {
+ if (address2 != null ? !address2.equals(that.address2) : that.address2 != null) {
return false;
}
- if (billCycleDayJson == null) {
- if (other.billCycleDayJson != null) {
- return false;
- }
- } else if (!billCycleDayJson.equals(other.billCycleDayJson)) {
+ if (billCycleDayJson != null ? !billCycleDayJson.equals(that.billCycleDayJson) : that.billCycleDayJson != null) {
return false;
}
- if (company == null) {
- if (other.company != null) {
- return false;
- }
- } else if (!company.equals(other.company)) {
+ if (city != null ? !city.equals(that.city) : that.city != null) {
return false;
}
- if (country == null) {
- if (other.country != null) {
- return false;
- }
- } else if (!country.equals(other.country)) {
+ if (company != null ? !company.equals(that.company) : that.company != null) {
return false;
}
- if (currency == null) {
- if (other.currency != null) {
- return false;
- }
- } else if (!currency.equals(other.currency)) {
+ if (country != null ? !country.equals(that.country) : that.country != null) {
return false;
}
- if (email == null) {
- if (other.email != null) {
- return false;
- }
- } else if (!email.equals(other.email)) {
+ if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
return false;
}
- if (externalKey == null) {
- if (other.externalKey != null) {
- return false;
- }
- } else if (!externalKey.equals(other.externalKey)) {
+ if (email != null ? !email.equals(that.email) : that.email != null) {
return false;
}
- if (length == null) {
- if (other.length != null) {
- return false;
- }
- } else if (!length.equals(other.length)) {
+ if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
return false;
}
- if (name == null) {
- if (other.name != null) {
- return false;
- }
- } else if (!name.equals(other.name)) {
+ if (isMigrated != null ? !isMigrated.equals(that.isMigrated) : that.isMigrated != null) {
return false;
}
- if (paymentMethodId == null) {
- if (other.paymentMethodId != null) {
- return false;
- }
- } else if (!paymentMethodId.equals(other.paymentMethodId)) {
+ if (isNotifiedForInvoices != null ? !isNotifiedForInvoices.equals(that.isNotifiedForInvoices) : that.isNotifiedForInvoices != null) {
return false;
}
- if (phone == null) {
- if (other.phone != null) {
- return false;
- }
- } else if (!phone.equals(other.phone)) {
+ if (length != null ? !length.equals(that.length) : that.length != null) {
return false;
}
- if (state == null) {
- if (other.state != null) {
- return false;
- }
- } else if (!state.equals(other.state)) {
+ if (locale != null ? !locale.equals(that.locale) : that.locale != null) {
return false;
}
- if (timeZone == null) {
- if (other.timeZone != null) {
- return false;
- }
- } else if (!timeZone.equals(other.timeZone)) {
+ if (name != null ? !name.equals(that.name) : that.name != null) {
+ return false;
+ }
+ if (paymentMethodId != null ? !paymentMethodId.equals(that.paymentMethodId) : that.paymentMethodId != null) {
+ return false;
+ }
+ if (phone != null ? !phone.equals(that.phone) : that.phone != null) {
+ return false;
+ }
+ if (postalCode != null ? !postalCode.equals(that.postalCode) : that.postalCode != null) {
+ return false;
+ }
+ if (state != null ? !state.equals(that.state) : that.state != null) {
+ return false;
+ }
+ if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) {
return false;
}
+
return true;
}
@Override
- public boolean equals(final Object obj) {
- if (!equalsNoId(obj)) {
- return false;
- } else {
- final AccountJson other = (AccountJson) obj;
- if (accountId == null) {
- if (other.accountId != null) {
- return false;
- }
- } else if (!accountId.equals(other.accountId)) {
- return false;
- }
- }
- return true;
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ result = 31 * result + (length != null ? length.hashCode() : 0);
+ result = 31 * result + (email != null ? email.hashCode() : 0);
+ result = 31 * result + (billCycleDayJson != null ? billCycleDayJson.hashCode() : 0);
+ result = 31 * result + (currency != null ? currency.hashCode() : 0);
+ result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
+ result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0);
+ result = 31 * result + (address1 != null ? address1.hashCode() : 0);
+ result = 31 * result + (address2 != null ? address2.hashCode() : 0);
+ result = 31 * result + (postalCode != null ? postalCode.hashCode() : 0);
+ result = 31 * result + (company != null ? company.hashCode() : 0);
+ result = 31 * result + (city != null ? city.hashCode() : 0);
+ result = 31 * result + (state != null ? state.hashCode() : 0);
+ result = 31 * result + (country != null ? country.hashCode() : 0);
+ result = 31 * result + (locale != null ? locale.hashCode() : 0);
+ result = 31 * result + (phone != null ? phone.hashCode() : 0);
+ result = 31 * result + (isMigrated != null ? isMigrated.hashCode() : 0);
+ result = 31 * result + (isNotifiedForInvoices != null ? isNotifiedForInvoices.hashCode() : 0);
+ return result;
}
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
index 01c3bda..e37400c 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
@@ -62,6 +62,10 @@ public class InvoiceApiExceptionMapper extends ExceptionMapperBase implements Ex
return buildBadRequestResponse(exception, uriInfo);
} else if (exception.getCode() == ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID.getCode()) {
return buildBadRequestResponse(exception, uriInfo);
+ } else if (exception.getCode() == ErrorCode.INVOICE_NO_SUCH_EXTERNAL_CHARGE.getCode()) {
+ return buildBadRequestResponse(exception, uriInfo);
+ } else if (exception.getCode() == ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID.getCode()) {
+ return buildBadRequestResponse(exception, uriInfo);
} else {
return buildBadRequestResponse(exception, uriInfo);
}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index 4205b8c..13960aa 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -16,9 +16,6 @@
package com.ning.billing.jaxrs.resources;
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
-import static javax.ws.rs.core.MediaType.TEXT_HTML;
-
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -46,11 +43,11 @@ 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.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.AccountUserApi;
+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;
@@ -74,6 +71,12 @@ import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.dao.ObjectType;
+import com.google.common.base.Objects;
+import com.google.inject.Inject;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.TEXT_HTML;
+
@Path(JaxrsResource.INVOICES_PATH)
public class InvoiceResource extends JaxRsResourceBase {
@@ -222,6 +225,68 @@ public class InvoiceResource extends JaxRsResourceBase {
return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId());
}
+ @POST
+ @Produces(APPLICATION_JSON)
+ @Consumes(APPLICATION_JSON)
+ @Path(CHARGES)
+ public Response createExternalCharge(final InvoiceItemJsonSimple externalChargeJson,
+ @QueryParam(QUERY_REQUESTED_DT) final String requestedDateTimeString,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws AccountApiException, InvoiceApiException {
+ final Account account = accountApi.getAccountById(UUID.fromString(externalChargeJson.getAccountId()));
+ final CallContext callContext = context.createContext(createdBy, reason, comment);
+
+ // Get the effective date of the external charge, in the account timezone
+ final LocalDate requestedDate;
+ if (requestedDateTimeString == null) {
+ requestedDate = clock.getUTCToday();
+ } else {
+ final DateTime requestedDateTime = DATE_TIME_FORMATTER.parseDateTime(requestedDateTimeString);
+ requestedDate = requestedDateTime.toDateTime(account.getTimeZone()).toLocalDate();
+ }
+
+ final Currency currency = Objects.firstNonNull(externalChargeJson.getCurrency(), account.getCurrency());
+ final InvoiceItem externalCharge = invoiceApi.insertExternalCharge(account.getId(), externalChargeJson.getAmount(),
+ externalChargeJson.getDescription(), requestedDate,
+ currency, callContext);
+
+ return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
+ }
+
+ @POST
+ @Produces(APPLICATION_JSON)
+ @Consumes(APPLICATION_JSON)
+ @Path("/{invoiceId:" + UUID_PATTERN + "}/" + CHARGES)
+ public Response createExternalChargeForInvoice(final InvoiceItemJsonSimple externalChargeJson,
+ @PathParam("invoiceId") final String invoiceIdString,
+ @QueryParam(QUERY_REQUESTED_DT) final String requestedDateTimeString,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws AccountApiException, InvoiceApiException {
+ final Account account = accountApi.getAccountById(UUID.fromString(externalChargeJson.getAccountId()));
+ final CallContext callContext = context.createContext(createdBy, reason, comment);
+
+ // Get the effective date of the external charge, in the account timezone
+ final LocalDate requestedDate;
+ if (requestedDateTimeString == null) {
+ requestedDate = clock.getUTCToday();
+ } else {
+ final DateTime requestedDateTime = DATE_TIME_FORMATTER.parseDateTime(requestedDateTimeString);
+ requestedDate = requestedDateTime.toDateTime(account.getTimeZone()).toLocalDate();
+ }
+
+ final UUID invoiceId = UUID.fromString(invoiceIdString);
+ final Currency currency = Objects.firstNonNull(externalChargeJson.getCurrency(), account.getCurrency());
+ final InvoiceItem externalCharge = invoiceApi.insertExternalChargeForInvoice(account.getId(), invoiceId,
+ externalChargeJson.getAmount(), externalChargeJson.getDescription(),
+ requestedDate, currency, callContext);
+
+ return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
+ }
+
@GET
@Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
@Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 17959f9..e52d1c2 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -13,16 +13,17 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
+
package com.ning.billing.jaxrs.resources;
public interface JaxrsResource {
+
public static final String API_PREFIX = "";
public static final String API_VERSION = "/1.0";
public static final String API_POSTFIX = "/kb";
public static final String PREFIX = API_PREFIX + API_VERSION + API_POSTFIX;
-
public static final String TIMELINE = "timeline";
/*
@@ -85,6 +86,9 @@ public interface JaxrsResource {
public static final String INVOICES = "invoices";
public static final String INVOICES_PATH = PREFIX + "/" + INVOICES;
+ public static final String CHARGES = "charges";
+ public static final String CHARGES_PATH = PREFIX + "/" + INVOICES + "/" + CHARGES;
+
public static final String PAYMENTS = "payments";
public static final String PAYMENTS_PATH = PREFIX + "/" + PAYMENTS;
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java
index d787fc8..3c70a20 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java
@@ -28,12 +28,8 @@ import com.ning.billing.jaxrs.JaxrsTestSuite;
import com.ning.billing.mock.MockAccountBuilder;
import com.ning.billing.mock.api.MockBillCycleDay;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
public class TestAccountJson extends JaxrsTestSuite {
- private static final ObjectMapper mapper = new ObjectMapper();
-
@Test(groups = "fast")
public void testJson() throws Exception {
final String accountId = UUID.randomUUID().toString();
@@ -47,15 +43,20 @@ public class TestAccountJson extends JaxrsTestSuite {
final String timeZone = UUID.randomUUID().toString();
final String address1 = UUID.randomUUID().toString();
final String address2 = UUID.randomUUID().toString();
+ final String postalCode = UUID.randomUUID().toString();
final String company = UUID.randomUUID().toString();
+ final String city = UUID.randomUUID().toString();
final String state = UUID.randomUUID().toString();
final String country = UUID.randomUUID().toString();
+ final String locale = UUID.randomUUID().toString();
final String phone = UUID.randomUUID().toString();
+ final Boolean isMigrated = true;
+ final Boolean isNotifiedForInvoice = false;
final AccountJson accountJson = new AccountJson(accountId, name, length, externalKey,
email, billCycleDay, currency, paymentMethodId,
- timeZone, address1, address2, company, state,
- country, phone);
+ timeZone, address1, address2, postalCode, company, city, state,
+ country, locale, phone, isMigrated, isNotifiedForInvoice);
Assert.assertEquals(accountJson.getAccountId(), accountId);
Assert.assertEquals(accountJson.getName(), name);
Assert.assertEquals(accountJson.getLength(), length);
@@ -67,21 +68,17 @@ public class TestAccountJson extends JaxrsTestSuite {
Assert.assertEquals(accountJson.getTimeZone(), timeZone);
Assert.assertEquals(accountJson.getAddress1(), address1);
Assert.assertEquals(accountJson.getAddress2(), address2);
+ Assert.assertEquals(accountJson.getPostalCode(), postalCode);
Assert.assertEquals(accountJson.getCompany(), company);
+ Assert.assertEquals(accountJson.getCity(), city);
Assert.assertEquals(accountJson.getState(), state);
Assert.assertEquals(accountJson.getCountry(), country);
+ Assert.assertEquals(accountJson.getLocale(), locale);
Assert.assertEquals(accountJson.getPhone(), phone);
+ Assert.assertEquals(accountJson.isMigrated(), isMigrated);
+ Assert.assertEquals(accountJson.isNotifiedForInvoices(), isNotifiedForInvoice);
final String asJson = mapper.writeValueAsString(accountJson);
- Assert.assertEquals(asJson, "{\"accountId\":\"" + accountJson.getAccountId() + "\",\"name\":\"" + accountJson.getName() + "\"," +
- "\"externalKey\":\"" + accountJson.getExternalKey() + "\",\"email\":\"" + accountJson.getEmail() + "\"," +
- "\"billCycleDay\":" + accountJson.getBillCycleDay() + "," +
- "\"currency\":\"" + accountJson.getCurrency() + "\",\"paymentMethodId\":\"" + accountJson.getPaymentMethodId() + "\"," +
- "\"address1\":\"" + accountJson.getAddress1() + "\",\"address2\":\"" + accountJson.getAddress2() + "\"," +
- "\"company\":\"" + accountJson.getCompany() + "\",\"state\":\"" + accountJson.getState() + "\"," +
- "\"country\":\"" + accountJson.getCountry() + "\",\"phone\":\"" + accountJson.getPhone() + "\"," +
- "\"length\":" + accountJson.getLength() + ",\"timeZone\":\"" + accountJson.getTimeZone() + "\"}");
-
final AccountJson fromJson = mapper.readValue(asJson, AccountJson.class);
Assert.assertEquals(fromJson, accountJson);
}
@@ -116,13 +113,17 @@ public class TestAccountJson extends JaxrsTestSuite {
Assert.assertEquals(accountJson.getAddress2(), account.getAddress2());
Assert.assertEquals(accountJson.getBillCycleDay().toString(), "{\"dayOfMonthLocal\":" + bcd + ",\"dayOfMonthUTC\":" + bcd + "}");
Assert.assertEquals(accountJson.getCountry(), account.getCountry());
+ Assert.assertEquals(accountJson.getLocale(), account.getLocale());
Assert.assertEquals(accountJson.getCompany(), account.getCompanyName());
+ Assert.assertEquals(accountJson.getCity(), account.getCity());
Assert.assertEquals(accountJson.getCurrency(), account.getCurrency().toString());
Assert.assertEquals(accountJson.getEmail(), account.getEmail());
Assert.assertEquals(accountJson.getExternalKey(), account.getExternalKey());
Assert.assertEquals(accountJson.getName(), account.getName());
Assert.assertEquals(accountJson.getPaymentMethodId(), account.getPaymentMethodId().toString());
Assert.assertEquals(accountJson.getPhone(), account.getPhone());
+ Assert.assertEquals(accountJson.isMigrated(), account.isMigrated());
+ Assert.assertEquals(accountJson.isNotifiedForInvoices(), account.isNotifiedForInvoices());
Assert.assertEquals(accountJson.getState(), account.getStateOrProvince());
Assert.assertEquals(accountJson.getTimeZone(), account.getTimeZone().toString());
}
junction/pom.xml 2(+1 -1)
diff --git a/junction/pom.xml b/junction/pom.xml
index 296b703..617a57e 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-junction</artifactId>
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
index 8ecc101..0580d60 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
@@ -16,6 +16,8 @@
package com.ning.billing.junction.plumbing.billing;
+import java.util.List;
+
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
@@ -92,7 +94,7 @@ public class BillCycleDayCalculator {
case ACCOUNT:
result = account.getBillCycleDay();
if (result == null || result.getDayOfMonthUTC() == 0) {
- result = calculateBcdFromSubscription(subscription, plan, account);
+ result = calculateBcdFromSubscription(subscription, plan, account, catalog);
}
break;
case BUNDLE:
@@ -103,10 +105,10 @@ public class BillCycleDayCalculator {
final EffectiveSubscriptionEvent previousTransition = baseSub.getPreviousTransition();
basePlan = catalog.findPlan(previousTransition.getPreviousPlan(), previousTransition.getEffectiveTransitionTime(), previousTransition.getSubscriptionStartDate());
}
- result = calculateBcdFromSubscription(baseSub, basePlan, account);
+ result = calculateBcdFromSubscription(baseSub, basePlan, account, catalog);
break;
case SUBSCRIPTION:
- result = calculateBcdFromSubscription(subscription, plan, account);
+ result = calculateBcdFromSubscription(subscription, plan, account, catalog);
break;
}
@@ -118,10 +120,30 @@ public class BillCycleDayCalculator {
}
@VisibleForTesting
- BillCycleDay calculateBcdFromSubscription(final Subscription subscription, final Plan plan, final Account account) throws AccountApiException {
+ BillCycleDay calculateBcdFromSubscription(final Subscription subscription, final Plan plan, final Account account, final Catalog catalog)
+ throws AccountApiException, CatalogApiException {
+ // Retrieve the initial phase type for that subscription
+ // TODO - this should be extracted somewhere, along with this code above
+ final PhaseType initialPhaseType;
+ final List<EffectiveSubscriptionEvent> transitions = subscription.getAllTransitions();
+ if (transitions.size() == 0) {
+ initialPhaseType = null;
+ } else {
+ final DateTime requestedDate = subscription.getStartDate();
+ final String initialPhaseString = transitions.get(0).getNextPhase();
+ if (initialPhaseString == null) {
+ initialPhaseType = null;
+ } else {
+ final PlanPhase initialPhase = catalog.findPhase(initialPhaseString, requestedDate, subscription.getStartDate());
+ if (initialPhase == null) {
+ initialPhaseType = null;
+ } else {
+ initialPhaseType = initialPhase.getPhaseType();
+ }
+ }
+ }
- final PhaseType currentPhaseType = subscription.getCurrentPhase() != null ? subscription.getCurrentPhase().getPhaseType() : null;
- final DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate(), currentPhaseType);
+ final DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate(), initialPhaseType);
// There are really two kinds of billCycleDay:
// - a System billingCycleDay which should be computed from UTC time (in order to get the correct notification time at
// the end of each service period)
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
index f243bbf..fc10afb 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
@@ -29,6 +29,7 @@ import com.ning.billing.account.api.AccountApiException;
import com.ning.billing.account.api.BillCycleDay;
import com.ning.billing.catalog.api.BillingAlignment;
import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.Plan;
import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
@@ -133,7 +134,7 @@ public class TestBillCycleDayCalculator {
verifyBCDCalculation(accountTimeZone, startDate, bcdUTC, bcdLocal);
}
- private void verifyBCDCalculation(final DateTimeZone accountTimeZone, final DateTime startDateUTC, final int bcdUTC, final int bcdLocal) throws AccountApiException {
+ private void verifyBCDCalculation(final DateTimeZone accountTimeZone, final DateTime startDateUTC, final int bcdUTC, final int bcdLocal) throws AccountApiException, CatalogApiException {
final BillCycleDayCalculator billCycleDayCalculator = new BillCycleDayCalculator(Mockito.mock(CatalogService.class), Mockito.mock(EntitlementUserApi.class));
final Subscription subscription = Mockito.mock(Subscription.class);
@@ -145,7 +146,7 @@ public class TestBillCycleDayCalculator {
final Account account = Mockito.mock(Account.class);
Mockito.when(account.getTimeZone()).thenReturn(accountTimeZone);
- final BillCycleDay bcd = billCycleDayCalculator.calculateBcdFromSubscription(subscription, plan, account);
+ final BillCycleDay bcd = billCycleDayCalculator.calculateBcdFromSubscription(subscription, plan, account, Mockito.mock(Catalog.class));
Assert.assertEquals(bcd.getDayOfMonthUTC(), bcdUTC);
Assert.assertEquals(bcd.getDayOfMonthLocal(), bcdLocal);
}
overdue/pom.xml 2(+1 -1)
diff --git a/overdue/pom.xml b/overdue/pom.xml
index b9f4c74..78f5d36 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-overdue</artifactId>
payment/pom.xml 2(+1 -1)
diff --git a/payment/pom.xml b/payment/pom.xml
index 45e1f1c..733701b 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-payment</artifactId>
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index a43d535..8f2235e 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.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<name>killbill</name>
<description>Library for managing recurring subscriptions and the associated billing</description>
<url>http://github.com/ning/killbill</url>
server/pom.xml 2(+1 -1)
diff --git a/server/pom.xml b/server/pom.xml
index 3859e33..88b7481 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-server</artifactId>
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
index 9ab06be..fab5627 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
@@ -74,7 +74,8 @@ public class TestAccount extends TestJaxrsBase {
// Update Account
final AccountJson newInput = new AccountJson(objFromJson.getAccountId(),
"zozo", 4, objFromJson.getExternalKey(), "rr@google.com", new BillCycleDayJson(18, 18),
- "USD", null, "UTC", "bl1", "bh2", "", "ca", "usa", "415-255-2991");
+ "USD", null, "UTC", "bl1", "bh2", "", "", "ca", "San Francisco", "usa", "en", "415-255-2991",
+ false, false);
baseJson = mapper.writeValueAsString(newInput);
final String uri = JaxrsResource.ACCOUNTS_PATH + "/" + objFromJson.getAccountId();
response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
index 675159a..66cfb7c 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -186,4 +186,44 @@ public class TestInvoice extends TestJaxrsBase {
final BigDecimal adjustedInvoiceBalance = invoice.getBalance().add(adjustedAmount.negate().setScale(2, RoundingMode.HALF_UP));
assertEquals(adjustedInvoice.getBalance().compareTo(adjustedInvoiceBalance), 0);
}
+
+ @Test(groups = "slow")
+ public void testExternalChargeOnNewInvoice() throws Exception {
+ final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+ // Get the invoices
+ assertEquals(getInvoicesForAccount(accountJson.getAccountId()).size(), 2);
+
+ // Post an external charge
+ final BigDecimal chargeAmount = BigDecimal.TEN;
+ final InvoiceJsonWithItems invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, null, null);
+ assertEquals(invoiceWithItems.getBalance().compareTo(chargeAmount), 0);
+ assertEquals(invoiceWithItems.getItems().size(), 1);
+
+ // Verify the total number of invoices
+ assertEquals(getInvoicesForAccount(accountJson.getAccountId()).size(), 3);
+ }
+
+ @Test(groups = "slow")
+ public void testExternalChargeOnExistingInvoice() throws Exception {
+ final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+ // Get the invoices
+ final List<InvoiceJsonWithItems> invoices = getInvoicesWithItemsForAccount(accountJson.getAccountId());
+ // 2 invoices but look for the non zero dollar one
+ assertEquals(invoices.size(), 2);
+ final String invoiceId = invoices.get(1).getInvoiceId();
+ final BigDecimal originalInvoiceAmount = invoices.get(1).getAmount();
+ final int originalNumberOfItemsForInvoice = invoices.get(1).getItems().size();
+
+ // Post an external charge
+ final BigDecimal chargeAmount = BigDecimal.TEN;
+ final InvoiceJsonWithItems invoiceWithItems = createExternalChargeForInvoice(accountJson.getAccountId(), invoiceId, chargeAmount, null, null);
+ assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
+
+ // Verify the new invoice balance
+ final InvoiceJsonSimple adjustedInvoice = getInvoice(invoiceId);
+ final BigDecimal adjustedInvoiceBalance = originalInvoiceAmount.add(chargeAmount.setScale(2, RoundingMode.HALF_UP));
+ assertEquals(adjustedInvoice.getBalance().compareTo(adjustedInvoiceBalance), 0);
+ }
}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 6708ddf..b682877 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -346,6 +346,19 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
// ACCOUNT UTILITIES
//
+ protected AccountJson getAccountByExternalKey(final String externalKey) throws Exception {
+ final Map<String, String> queryParams = new HashMap<String, String>();
+ queryParams.put(JaxrsResource.QUERY_EXTERNAL_KEY, externalKey);
+ final Response response = doGet(JaxrsResource.ACCOUNTS_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+ final String baseJson = response.getResponseBody();
+ final AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
+ Assert.assertNotNull(objFromJson);
+
+ return objFromJson;
+ }
+
protected AccountJson createAccountWithDefaultPaymentMethod(final String name, final String key, final String email) throws Exception {
final AccountJson input = createAccount(name, key, email);
@@ -565,6 +578,46 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
}
+ protected InvoiceJsonWithItems createExternalCharge(final String accountId, final BigDecimal amount, @Nullable final Currency currency,
+ @Nullable final DateTime requestedDate) throws Exception {
+ return doCreateExternalCharge(accountId, null, amount, currency, requestedDate, JaxrsResource.CHARGES_PATH);
+ }
+
+ protected InvoiceJsonWithItems createExternalChargeForInvoice(final String accountId, final String invoiceId, final BigDecimal amount,
+ @Nullable final Currency currency, @Nullable final DateTime requestedDate) throws Exception {
+ final String uri = JaxrsResource.INVOICES_PATH + "/" + invoiceId + "/" + JaxrsResource.CHARGES;
+ return doCreateExternalCharge(accountId, invoiceId, amount, currency, requestedDate, uri);
+ }
+
+ private InvoiceJsonWithItems doCreateExternalCharge(final String accountId, @Nullable final String invoiceId, @Nullable final BigDecimal amount,
+ @Nullable final Currency currency, final DateTime requestedDate, final String uri) throws IOException {
+ final Map<String, String> queryParams = new HashMap<String, String>();
+ if (requestedDate != null) {
+ queryParams.put(JaxrsResource.QUERY_REQUESTED_DT, requestedDate.toDateTimeISO().toString());
+ }
+
+ final InvoiceItemJsonSimple externalCharge = new InvoiceItemJsonSimple(null, invoiceId, accountId, null, null, null, null,
+ null, null, null, amount, currency, null);
+ final String externalChargeJson = mapper.writeValueAsString(externalCharge);
+ final Response response = doPost(uri, externalChargeJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+ Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+ final String location = response.getHeader("Location");
+ Assert.assertNotNull(location);
+
+ final Map<String, String> queryParamsForInvoice = new HashMap<String, String>();
+ queryParamsForInvoice.put(JaxrsResource.QUERY_ACCOUNT_ID, accountId);
+ queryParamsForInvoice.put(JaxrsResource.QUERY_INVOICE_WITH_ITEMS, "true");
+ final Response invoiceResponse = doGetWithUrl(location, queryParamsForInvoice, DEFAULT_HTTP_TIMEOUT_SEC);
+ assertEquals(invoiceResponse.getStatusCode(), Status.OK.getStatusCode());
+
+ final String invoicesBaseJson = invoiceResponse.getResponseBody();
+ final InvoiceJsonWithItems invoice = mapper.readValue(invoicesBaseJson, new TypeReference<InvoiceJsonWithItems>(){});
+ assertNotNull(invoice);
+
+ return invoice;
+ }
+
//
// PAYMENT UTILITIES
//
@@ -805,13 +858,17 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
final String timeZone = "UTC";
final String address1 = "12 rue des ecoles";
final String address2 = "Poitier";
+ final String postalCode = "44 567";
final String company = "Renault";
+ final String city = "Quelque part";
final String state = "Poitou";
final String country = "France";
+ final String locale = "fr";
final String phone = "81 53 26 56";
// Note: the accountId payload is ignored on account creation
- return new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, null, timeZone, address1, address2, company, state, country, phone);
+ return new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, null, timeZone,
+ address1, address2, postalCode, company, city, state, country, locale, phone, false, false);
}
/**
usage/pom.xml 2(+1 -1)
diff --git a/usage/pom.xml b/usage/pom.xml
index 8d254a4..f674f66 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -13,7 +13,7 @@
<parent>
<groupId>com.ning.billing</groupId>
<artifactId>killbill</artifactId>
- <version>0.1.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-usage</artifactId>
util/pom.xml 2(+1 -1)
diff --git a/util/pom.xml b/util/pom.xml
index d4ae0b6..378717e 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.28-SNAPSHOT</version>
+ <version>0.1.29-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>killbill-util</artifactId>