killbill-memoizeit

Details

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 c8dd494..3b78afe 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
@@ -144,6 +144,22 @@ public interface InvoiceUserApi {
                                             Currency currency, CallContext context) throws InvoiceApiException;
 
     /**
+     * Add an external charge to an account tied to a particular bundle.
+     *
+     * @param accountId     account id
+     * @param bundleId      bundle 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 insertExternalChargeForBundle(UUID accountId, UUID bundleId, BigDecimal amount, String description, LocalDate effectiveDate,
+                                                     Currency currency, CallContext context) throws InvoiceApiException;
+
+    /**
      * Add an external charge to an invoice.
      *
      * @param accountId     account id
@@ -160,6 +176,23 @@ public interface InvoiceUserApi {
                                                       LocalDate effectiveDate, Currency currency, CallContext context) throws InvoiceApiException;
 
     /**
+     * Add an external charge to an invoice tied to a particular bundle.
+     *
+     * @param accountId     account id
+     * @param invoiceId     invoice id
+     * @param bundleId      bundle 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 insertExternalChargeForInvoiceAndBundle(UUID accountId, UUID invoiceId, UUID bundleId, 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 012bdf2..bd524b2 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
@@ -149,17 +149,30 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     @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);
+        return insertExternalChargeForInvoiceAndBundle(accountId, null, null, amount, description, effectiveDate, currency, context);
+    }
+
+    @Override
+    public InvoiceItem insertExternalChargeForBundle(final UUID accountId, final UUID bundleId, final BigDecimal amount, @Nullable final String description,
+                                                     final LocalDate effectiveDate, final Currency currency, final CallContext context) throws InvoiceApiException {
+        return insertExternalChargeForInvoiceAndBundle(accountId, null, bundleId, 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 {
+        return insertExternalChargeForInvoiceAndBundle(accountId, invoiceId, null, amount, description, effectiveDate, currency, context);
+    }
+
+    @Override
+    public InvoiceItem insertExternalChargeForInvoiceAndBundle(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId,
+                                                               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);
+        return dao.insertExternalCharge(accountId, invoiceId, bundleId, description, amount, effectiveDate, currency, context);
     }
 
     @Override
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 f6a0e95..c0fc0b4 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
@@ -531,7 +531,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, final String description,
+    public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId, final String description,
                                             final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context) {
         return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
             @Override
@@ -544,7 +544,8 @@ public class DefaultInvoiceDao implements InvoiceDao {
                     invoiceIdForExternalCharge = invoiceForExternalCharge.getId();
                 }
 
-                final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceIdForExternalCharge, accountId, description,
+                final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceIdForExternalCharge, accountId,
+                                                                                 bundleId, description,
                                                                                  effectiveDate, amount, currency);
 
                 final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
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 0c9b60e..ecbda77 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
@@ -110,6 +110,7 @@ public interface InvoiceDao {
      *
      * @param accountId     the account id
      * @param invoiceId     the invoice id
+     * @param bundleId      the bundle 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
@@ -117,7 +118,7 @@ public interface InvoiceDao {
      * @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,
+    InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId, @Nullable final String description,
                                      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 d3758c4..2391bc9 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
@@ -134,7 +134,7 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
             InvoiceItem item = null;
             switch (type) {
                 case EXTERNAL_CHARGE:
-                    item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, planName, startDate, amount, currency);
+                    item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, planName, startDate, amount, currency);
                     break;
                 case FIXED:
                     item = new FixedPriceInvoiceItem(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, amount, currency);
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 1c45243..997bb7a 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, external charged etc.
+                    if (item.getSubscriptionId() == null || // Always include migration invoices, credits, external charges 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
index ae5dbae..c8f0245 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -19,6 +19,8 @@ package com.ning.billing.invoice.model;
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.joda.time.LocalDate;
 
 import com.ning.billing.catalog.api.Currency;
@@ -27,14 +29,14 @@ 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 invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final String description,
+                                     final LocalDate date, final BigDecimal amount, final Currency currency) {
+        super(invoiceId, accountId, bundleId, 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);
+    public ExternalChargeInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
+                                     @Nullable final String description, final LocalDate date, final BigDecimal amount, final Currency currency) {
+        super(id, invoiceId, accountId, bundleId, (UUID) null, description, null, date, null, amount, currency);
     }
 
     @Override
@@ -77,16 +79,10 @@ public class ExternalChargeInvoiceItem extends InvoiceItemBase {
         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("bundleId = ").append(bundleId == null ? "null" : bundleId.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("amount = ").append(amount == null ? "null" : amount.toString()).append(", ");
         sb.append("}");
         return sb.toString();
     }
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 23a3445..ee3e5a4 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
@@ -19,6 +19,8 @@ package com.ning.billing.invoice.api.user;
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -58,8 +60,28 @@ public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
         final BigDecimal externalChargeAmount = BigDecimal.TEN;
         final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharge(accountId, externalChargeAmount, UUID.randomUUID().toString(),
                                                                                           clock.getUTCToday(), accountCurrency, context);
+        verifyExternalChargeOnNewInvoice(accountBalance, null, externalChargeAmount, externalChargeInvoiceItem);
+    }
+
+    @Test(groups = "slow")
+    public void testPostExternalChargeForBundleOnNewInvoice() throws Exception {
+        // Initial account balance
+        final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+
+        // Post an external charge
+        final BigDecimal externalChargeAmount = BigDecimal.TEN;
+        final UUID bundleId = UUID.randomUUID();
+        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalChargeForBundle(accountId, bundleId, externalChargeAmount,
+                                                                                                   UUID.randomUUID().toString(), clock.getUTCToday(),
+                                                                                                   accountCurrency, context);
+        verifyExternalChargeOnNewInvoice(accountBalance, bundleId, externalChargeAmount, externalChargeInvoiceItem);
+    }
+
+    private void verifyExternalChargeOnNewInvoice(final BigDecimal initialAccountBalance, @Nullable final UUID bundleId,
+                                                  final BigDecimal externalChargeAmount, final InvoiceItem externalChargeInvoiceItem) {
         Assert.assertNotNull(externalChargeInvoiceItem.getInvoiceId());
         Assert.assertNotEquals(externalChargeInvoiceItem.getInvoiceId(), invoiceId);
+        Assert.assertEquals(externalChargeInvoiceItem.getBundleId(), bundleId);
         Assert.assertEquals(externalChargeInvoiceItem.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
         Assert.assertEquals(externalChargeInvoiceItem.getAccountId(), accountId);
         Assert.assertEquals(externalChargeInvoiceItem.getAmount(), externalChargeAmount);
@@ -72,7 +94,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
 
         // Verify the adjusted account balance
         final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
-        Assert.assertEquals(adjustedAccountBalance, accountBalance.add(externalChargeAmount));
+        Assert.assertEquals(adjustedAccountBalance, initialAccountBalance.add(externalChargeAmount));
     }
 
     @Test(groups = "slow")
@@ -90,7 +112,32 @@ public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
         final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalChargeForInvoice(accountId, invoiceId,
                                                                                                     externalChargeAmount, UUID.randomUUID().toString(),
                                                                                                     clock.getUTCToday(), accountCurrency, context);
+        verifyExternalChargeOnExistingInvoice(invoiceBalance, null, externalChargeAmount, externalChargeInvoiceItem);
+    }
+
+    @Test(groups = "slow")
+    public void testPostExternalChargeForBundleOnExistingInvoice() 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 UUID bundleId = UUID.randomUUID();
+        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalChargeForInvoiceAndBundle(accountId, invoiceId, bundleId,
+                                                                                                             externalChargeAmount, UUID.randomUUID().toString(),
+                                                                                                             clock.getUTCToday(), accountCurrency, context);
+        verifyExternalChargeOnExistingInvoice(invoiceBalance, bundleId, externalChargeAmount, externalChargeInvoiceItem);
+    }
+
+    private void verifyExternalChargeOnExistingInvoice(final BigDecimal initialInvoiceBalance, @Nullable final UUID bundleId,
+                                                       final BigDecimal externalChargeAmount, final InvoiceItem externalChargeInvoiceItem) {
         Assert.assertEquals(externalChargeInvoiceItem.getInvoiceId(), invoiceId);
+        Assert.assertEquals(externalChargeInvoiceItem.getBundleId(), bundleId);
         Assert.assertEquals(externalChargeInvoiceItem.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
         Assert.assertEquals(externalChargeInvoiceItem.getAccountId(), accountId);
         Assert.assertEquals(externalChargeInvoiceItem.getAmount(), externalChargeAmount);
@@ -99,7 +146,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
 
         // Verify the adjusted invoice balance
         final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
-        Assert.assertEquals(adjustedInvoiceBalance.compareTo(invoiceBalance.add(externalChargeAmount)), 0);
+        Assert.assertEquals(adjustedInvoiceBalance.compareTo(initialInvoiceBalance.add(externalChargeAmount)), 0);
 
         // Verify the adjusted account balance
         final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
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 a7884b5..8ea4553 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
@@ -27,8 +27,6 @@ import javax.annotation.Nullable;
 
 import org.joda.time.LocalDate;
 
-import com.google.inject.Inject;
-
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
@@ -38,7 +36,10 @@ import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.callcontext.CallContext;
 
+import com.google.inject.Inject;
+
 public class MockInvoiceDao implements InvoiceDao {
+
     private final Bus eventBus;
     private final Object monitor = new Object();
     private final Map<UUID, Invoice> invoices = new LinkedHashMap<UUID, Invoice>();
@@ -264,7 +265,9 @@ public class MockInvoiceDao implements InvoiceDao {
     }
 
     @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) {
+    public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId,
+                                            @Nullable final String description, final BigDecimal amount, final LocalDate effectiveDate,
+                                            final Currency currency, final CallContext context) {
         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 4bf050e..acf4a79 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
@@ -163,9 +163,10 @@ public class TestInvoiceItemDao extends InvoiceDaoTestBase {
     public void testExternalChargeInvoiceSqlDao() throws Exception {
         final UUID invoiceId = UUID.randomUUID();
         final UUID accountId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
         final String description = UUID.randomUUID().toString();
         final LocalDate startDate = new LocalDate(2012, 4, 1);
-        final InvoiceItem externalChargeInvoiceItem = new ExternalChargeInvoiceItem(invoiceId, accountId, description,
+        final InvoiceItem externalChargeInvoiceItem = new ExternalChargeInvoiceItem(invoiceId, accountId, bundleId, description,
                                                                                     startDate, TEN, Currency.USD);
         invoiceItemSqlDao.create(externalChargeInvoiceItem, context);
 
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
index cd59851..c525627 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/model/TestExternalChargeInvoiceItem.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/model/TestExternalChargeInvoiceItem.java
@@ -37,18 +37,19 @@ public class TestExternalChargeInvoiceItem {
         final UUID id = UUID.randomUUID();
         final UUID invoiceId = UUID.randomUUID();
         final UUID accountId = UUID.randomUUID();
+        final UUID bundleId = 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,
+        final ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
                                                                              effectiveDate, amount, currency);
         Assert.assertEquals(item.getAccountId(), accountId);
         Assert.assertEquals(item.getAmount(), amount);
+        Assert.assertEquals(item.getBundleId(), bundleId);
         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());
@@ -57,15 +58,15 @@ public class TestExternalChargeInvoiceItem {
 
         Assert.assertEquals(item, item);
 
-        final ExternalChargeInvoiceItem otherItem = new ExternalChargeInvoiceItem(id, invoiceId, UUID.randomUUID(), description,
-                                                                                  effectiveDate, amount, currency);
+        final ExternalChargeInvoiceItem otherItem = new ExternalChargeInvoiceItem(id, invoiceId, UUID.randomUUID(), bundleId,
+                                                                                  description, effectiveDate, amount, currency);
         Assert.assertNotEquals(otherItem, item);
 
         // Check comparison (done by start date)
-        final ExternalChargeInvoiceItem itemBefore = new ExternalChargeInvoiceItem(id, invoiceId, accountId, description,
+        final ExternalChargeInvoiceItem itemBefore = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
                                                                                    effectiveDate.minusDays(1), amount, currency);
         Assert.assertEquals(itemBefore.compareTo(item), -1);
-        final ExternalChargeInvoiceItem itemAfter = new ExternalChargeInvoiceItem(id, invoiceId, accountId, description,
+        final ExternalChargeInvoiceItem itemAfter = new ExternalChargeInvoiceItem(id, invoiceId, accountId, bundleId, description,
                                                                                   effectiveDate.plusDays(1), amount, currency);
         Assert.assertEquals(itemAfter.compareTo(item), 1);
     }
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 13960aa..816eaf3 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
@@ -248,9 +248,16 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
 
         final Currency currency = Objects.firstNonNull(externalChargeJson.getCurrency(), account.getCurrency());
-        final InvoiceItem externalCharge = invoiceApi.insertExternalCharge(account.getId(), externalChargeJson.getAmount(),
-                                                                           externalChargeJson.getDescription(), requestedDate,
-                                                                           currency, callContext);
+        final InvoiceItem externalCharge;
+        if (externalChargeJson.getBundleId() != null) {
+            externalCharge = invoiceApi.insertExternalChargeForBundle(account.getId(), UUID.fromString(externalChargeJson.getBundleId()),
+                                                                      externalChargeJson.getAmount(), externalChargeJson.getDescription(),
+                                                                      requestedDate, currency, callContext);
+        } else {
+            externalCharge = invoiceApi.insertExternalCharge(account.getId(), externalChargeJson.getAmount(),
+                                                             externalChargeJson.getDescription(), requestedDate,
+                                                             currency, callContext);
+        }
 
         return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
     }
@@ -280,9 +287,16 @@ public class InvoiceResource extends JaxRsResourceBase {
 
         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);
+        final InvoiceItem externalCharge;
+        if (externalChargeJson.getBundleId() != null) {
+            externalCharge = invoiceApi.insertExternalChargeForInvoiceAndBundle(account.getId(), invoiceId, UUID.fromString(externalChargeJson.getBundleId()),
+                                                                                externalChargeJson.getAmount(), externalChargeJson.getDescription(),
+                                                                                requestedDate, currency, callContext);
+        } else {
+            externalCharge = invoiceApi.insertExternalChargeForInvoice(account.getId(), invoiceId,
+                                                                       externalChargeJson.getAmount(), externalChargeJson.getDescription(),
+                                                                       requestedDate, currency, callContext);
+        }
 
         return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
     }
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 66cfb7c..712baeb 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -19,6 +19,7 @@ package com.ning.billing.jaxrs;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.List;
+import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.testng.annotations.Test;
@@ -196,9 +197,29 @@ public class TestInvoice extends TestJaxrsBase {
 
         // Post an external charge
         final BigDecimal chargeAmount = BigDecimal.TEN;
-        final InvoiceJsonWithItems invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, null, null);
+        final InvoiceJsonWithItems invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, null, null, null);
         assertEquals(invoiceWithItems.getBalance().compareTo(chargeAmount), 0);
         assertEquals(invoiceWithItems.getItems().size(), 1);
+        assertNull(invoiceWithItems.getItems().get(0).getBundleId());
+
+        // Verify the total number of invoices
+        assertEquals(getInvoicesForAccount(accountJson.getAccountId()).size(), 3);
+    }
+
+    @Test(groups = "slow")
+    public void testExternalChargeForBundleOnNewInvoice() 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 String bundleId = UUID.randomUUID().toString();
+        final InvoiceJsonWithItems invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, bundleId, null, null);
+        assertEquals(invoiceWithItems.getBalance().compareTo(chargeAmount), 0);
+        assertEquals(invoiceWithItems.getItems().size(), 1);
+        assertEquals(invoiceWithItems.getItems().get(0).getBundleId(), bundleId);
 
         // Verify the total number of invoices
         assertEquals(getInvoicesForAccount(accountJson.getAccountId()).size(), 3);
@@ -218,8 +239,36 @@ public class TestInvoice extends TestJaxrsBase {
 
         // Post an external charge
         final BigDecimal chargeAmount = BigDecimal.TEN;
-        final InvoiceJsonWithItems invoiceWithItems = createExternalChargeForInvoice(accountJson.getAccountId(), invoiceId, chargeAmount, null, null);
+        final InvoiceJsonWithItems invoiceWithItems = createExternalChargeForInvoice(accountJson.getAccountId(), invoiceId,
+                                                                                     null, chargeAmount, null, null);
+        assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
+        assertNull(invoiceWithItems.getItems().get(originalNumberOfItemsForInvoice).getBundleId());
+
+        // 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);
+    }
+
+    @Test(groups = "slow")
+    public void testExternalChargeForBundleOnExistingInvoice() 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 String bundleId = UUID.randomUUID().toString();
+        final InvoiceJsonWithItems invoiceWithItems = createExternalChargeForInvoice(accountJson.getAccountId(), invoiceId,
+                                                                                     bundleId, chargeAmount, null, null);
         assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
+        assertEquals(invoiceWithItems.getItems().get(originalNumberOfItemsForInvoice).getBundleId(), bundleId);
 
         // Verify the new invoice balance
         final InvoiceJsonSimple adjustedInvoice = getInvoice(invoiceId);
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 b682877..1ab8f18 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -578,25 +578,25 @@ 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 createExternalCharge(final String accountId, final BigDecimal amount, @Nullable final String bundleId,
+                                                        @Nullable final Currency currency, @Nullable final DateTime requestedDate) throws Exception {
+        return doCreateExternalCharge(accountId, null, bundleId, 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 {
+    protected InvoiceJsonWithItems createExternalChargeForInvoice(final String accountId, final String invoiceId, @Nullable final String bundleId, 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);
+        return doCreateExternalCharge(accountId, invoiceId, bundleId, amount, currency, requestedDate, uri);
     }
 
-    private InvoiceJsonWithItems doCreateExternalCharge(final String accountId, @Nullable final String invoiceId, @Nullable final BigDecimal amount,
+    private InvoiceJsonWithItems doCreateExternalCharge(final String accountId, @Nullable final String invoiceId, @Nullable final String bundleId, @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,
+        final InvoiceItemJsonSimple externalCharge = new InvoiceItemJsonSimple(null, invoiceId, accountId, bundleId, 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);