Details
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 189d61e..bb9751c 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -185,11 +185,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
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);
+ }
+}
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);
}