killbill-uncached

invoice: add API to post external charges It can be useful

8/8/2012 6:16:26 PM

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);
         }