killbill-memoizeit

invoice: implement soft delete of CBA Signed-off-by: Pierre-Alexandre

9/19/2012 10:13:02 PM

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 fa58913..c99f160 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
@@ -260,6 +260,17 @@ public interface InvoiceUserApi {
                                                    BigDecimal amount, Currency currency, CallContext context) throws InvoiceApiException;
 
     /**
+     * Delete a CBA item.
+     *
+     * @param accountId     account id
+     * @param invoiceId     invoice id
+     * @param invoiceItemId invoice item id (must be of type CBA_ADJ)
+     * @param context       the call context
+     * @throws InvoiceApiException
+     */
+    public void deleteCBA(UUID accountId, UUID invoiceId, UUID invoiceItemId, CallContext context) throws InvoiceApiException;
+
+    /**
      * Retrieve the invoice formatted in HTML.
      *
      * @param invoiceId invoice 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 11efa03..d9c855c 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
@@ -27,7 +27,6 @@ import javax.annotation.Nullable;
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 
-import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
@@ -50,6 +49,8 @@ import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.ControlTagType;
 import com.ning.billing.util.tag.Tag;
 
+import com.google.inject.Inject;
+
 public class DefaultInvoiceUserApi implements InvoiceUserApi {
 
     private final InvoiceDao dao;
@@ -219,6 +220,11 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
+    public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException {
+        dao.deleteCBA(accountId, invoiceId, invoiceItemId, context);
+    }
+
+    @Override
     public String getInvoiceAsHTML(final UUID invoiceId) throws AccountApiException, IOException, InvoiceApiException {
         final Invoice invoice = getInvoice(invoiceId);
         if (invoice == null) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
index b03626e..a048f24 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/AuditedInvoiceDao.java
@@ -35,12 +35,6 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableMap.Builder;
-import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
@@ -72,6 +66,13 @@ import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.dao.TableName;
 import com.ning.billing.util.tag.ControlTagType;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.inject.Inject;
+
 public class AuditedInvoiceDao implements InvoiceDao {
 
     private static final Logger log = LoggerFactory.getLogger(AuditedInvoiceDao.class);
@@ -588,7 +589,7 @@ public class AuditedInvoiceDao implements InvoiceDao {
     @Override
     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)
-                                                    throws InvoiceApiException {
+            throws InvoiceApiException {
         return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
             @Override
             public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -682,6 +683,89 @@ public class AuditedInvoiceDao implements InvoiceDao {
     }
 
     @Override
+    public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException {
+        invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
+            @Override
+            public Void inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+                // Retrieve the invoice and make sure it belongs to the right account
+                final Invoice invoice = transactional.getById(invoiceId.toString());
+                if (invoice == null || !invoice.getAccountId().equals(accountId)) {
+                    throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
+                }
+
+                // Retrieve the invoice item and make sure it belongs to the right invoice
+                final InvoiceItemSqlDao invoiceItemSqlDao = transactional.become(InvoiceItemSqlDao.class);
+                final InvoiceItem cbaItem = invoiceItemSqlDao.getById(invoiceItemId.toString());
+                if (cbaItem == null || !cbaItem.getInvoiceId().equals(invoice.getId())) {
+                    throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
+                }
+
+                // First, adjust the same invoice with the CBA amount to "delete"
+                final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
+                                                                               cbaItem.getAmount().negate(), cbaItem.getCurrency());
+                invoiceItemSqlDao.create(cbaAdjItem, context);
+
+                // If there is more account credit than CBA we adjusted, we're done.
+                // Otherwise, we need to find further invoices on which this credit was consumed
+                final BigDecimal accountCBA = getAccountCBAFromTransaction(accountId, invoiceSqlDao);
+                if (accountCBA.compareTo(BigDecimal.ZERO) < 0) {
+                    if (accountCBA.compareTo(cbaItem.getAmount().negate()) < 0) {
+                        throw new IllegalStateException("The account balance can't be lower than the amount adjusted");
+                    }
+                    final List<Invoice> invoicesFollowing = transactional.getInvoicesByAccountAfterDate(accountId.toString(),
+                                                                                                        invoice.getInvoiceDate().toDateTimeAtStartOfDay().toDate());
+                    populateChildren(invoicesFollowing, transactional);
+
+                    // The remaining amount to adjust (i.e. the amount of credits used on following invoices)
+                    // is the current account CBA balance (minus the sign)
+                    BigDecimal positiveRemainderToAdjust = accountCBA.negate();
+                    for (final Invoice invoiceFollowing : invoicesFollowing) {
+                        if (invoiceFollowing.getId().equals(invoice.getId())) {
+                            continue;
+                        }
+
+                        // Add a single adjustment per invoice
+                        BigDecimal positiveCBAAdjItemAmount = BigDecimal.ZERO;
+
+                        for (final InvoiceItem cbaUsed : invoiceFollowing.getInvoiceItems()) {
+                            // Ignore non CBA items or credits (CBA >= 0)
+                            if (!InvoiceItemType.CBA_ADJ.equals(cbaUsed.getInvoiceItemType()) ||
+                                cbaUsed.getAmount().compareTo(BigDecimal.ZERO) >= 0) {
+                                continue;
+                            }
+
+                            final BigDecimal positiveCBAUsedAmount = cbaUsed.getAmount().negate();
+                            final BigDecimal positiveNextCBAAdjItemAmount;
+                            if (positiveCBAUsedAmount.compareTo(positiveRemainderToAdjust) < 0) {
+                                positiveNextCBAAdjItemAmount = positiveCBAUsedAmount;
+                                positiveRemainderToAdjust = positiveRemainderToAdjust.min(positiveNextCBAAdjItemAmount);
+                            } else {
+                                positiveNextCBAAdjItemAmount = positiveRemainderToAdjust;
+                                positiveRemainderToAdjust = BigDecimal.ZERO;
+                            }
+                            positiveCBAAdjItemAmount = positiveCBAAdjItemAmount.add(positiveNextCBAAdjItemAmount);
+
+                            if (positiveRemainderToAdjust.compareTo(BigDecimal.ZERO) == 0) {
+                                break;
+                            }
+                        }
+
+                        // Add the adjustment on that invoice
+                        final InvoiceItem nextCBAAdjItem = new CreditBalanceAdjInvoiceItem(invoiceFollowing.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
+                                                                                           positiveCBAAdjItemAmount, cbaItem.getCurrency());
+                        invoiceItemSqlDao.create(nextCBAAdjItem, context);
+                        if (positiveRemainderToAdjust.compareTo(BigDecimal.ZERO) == 0) {
+                            break;
+                        }
+                    }
+                }
+
+                return null;
+            }
+        });
+    }
+
+    @Override
     public void test() {
         invoiceSqlDao.test();
     }
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 207c1de..936fc83 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
@@ -158,4 +158,13 @@ public interface InvoiceDao {
      */
     InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final LocalDate effectiveDate,
                                             @Nullable final BigDecimal amount, @Nullable final Currency currency, final CallContext context);
+
+    /**
+     * Delete a CBA item.
+     *
+     * @param accountId     the account id
+     * @param invoiceId     the invoice id
+     * @param invoiceItemId the invoice item id of the cba item to delete
+     */
+    void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException;
 }
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 5a6b3a4..b45869d 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
@@ -300,4 +300,9 @@ public class MockInvoiceDao implements InvoiceDao {
             throws InvoiceApiException {
         return null;
     }
+
+    @Override
+    public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final CallContext context) throws InvoiceApiException {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
index f7ba009..8e1a32a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
@@ -1322,4 +1322,169 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         tags = tagDao.loadEntities(invoice.getId(), ObjectType.INVOICE);
         assertEquals(tags.size(), 0);
     }
+
+    @Test(groups = "slow")
+    public void testDeleteCBANotConsumed() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+
+        // Create invoice 1
+        // Scenario: single item with payment
+        // * $10 item
+        // Then, a repair occur:
+        // * $-10 repair
+        // * $10 generated CBA due to the repair (assume previous payment)
+        final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+        final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                   fixedItem1.getStartDate(), fixedItem1.getEndDate(),
+                                                                                   fixedItem1.getAmount().negate(), fixedItem1.getCurrency(),
+                                                                                   fixedItem1.getId());
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem1 = new CreditBalanceAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                                         fixedItem1.getStartDate(), fixedItem1.getAmount(),
+                                                                                                         fixedItem1.getCurrency());
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem1, context);
+        invoiceItemSqlDao.create(repairAdjInvoiceItem, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem1, context);
+
+        // Verify scenario - no CBA should have been used
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 10.00);
+        verifyInvoice(invoice1.getId(), 10.00, 10.00);
+
+        // Delete the CBA on invoice 1
+        invoiceDao.deleteCBA(accountId, invoice1.getId(), creditBalanceAdjInvoiceItem1.getId(), context);
+
+        // Verify the result
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+        verifyInvoice(invoice1.getId(), 0.00, 0.00);
+    }
+
+    @Test(groups = "slow")
+    public void testDeleteCBAPartiallyConsumed() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+
+        // Create invoice 1
+        // Scenario: single item with payment
+        // * $10 item
+        // Then, a repair occur:
+        // * $-10 repair
+        // * $10 generated CBA due to the repair (assume previous payment)
+        final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+        final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                   fixedItem1.getStartDate(), fixedItem1.getEndDate(),
+                                                                                   fixedItem1.getAmount().negate(), fixedItem1.getCurrency(),
+                                                                                   fixedItem1.getId());
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem1 = new CreditBalanceAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                                         fixedItem1.getStartDate(), fixedItem1.getAmount(),
+                                                                                                         fixedItem1.getCurrency());
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem1, context);
+        invoiceItemSqlDao.create(repairAdjInvoiceItem, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem1, context);
+
+        // Create invoice 2
+        // Scenario: single item
+        // * $5 item
+        // * $-5 CBA used
+        final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
+                                                                                                         fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
+                                                                                                         fixedItem2.getCurrency());
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem2, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem2, context);
+
+        // Verify scenario - half of the CBA should have been used
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 5.00);
+        verifyInvoice(invoice1.getId(), 10.00, 10.00);
+        verifyInvoice(invoice2.getId(), 0.00, -5.00);
+
+        // Delete the CBA on invoice 1
+        invoiceDao.deleteCBA(accountId, invoice1.getId(), creditBalanceAdjInvoiceItem1.getId(), context);
+
+        // Verify all three invoices were affected
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+        verifyInvoice(invoice1.getId(), 0.00, 0.00);
+        verifyInvoice(invoice2.getId(), 5.00, 0.00);
+    }
+
+    @Test(groups = "slow")
+    public void testDeleteCBAFullyConsumedTwice() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+
+        // Create invoice 1
+        // Scenario: single item with payment
+        // * $10 item
+        // Then, a repair occur:
+        // * $-10 repair
+        // * $10 generated CBA due to the repair (assume previous payment)
+        final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem1 = new FixedPriceInvoiceItem(invoice1.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+        final RepairAdjInvoiceItem repairAdjInvoiceItem = new RepairAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                   fixedItem1.getStartDate(), fixedItem1.getEndDate(),
+                                                                                   fixedItem1.getAmount().negate(), fixedItem1.getCurrency(),
+                                                                                   fixedItem1.getId());
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem1 = new CreditBalanceAdjInvoiceItem(fixedItem1.getInvoiceId(), fixedItem1.getAccountId(),
+                                                                                                         fixedItem1.getStartDate(), fixedItem1.getAmount(),
+                                                                                                         fixedItem1.getCurrency());
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem1, context);
+        invoiceItemSqlDao.create(repairAdjInvoiceItem, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem1, context);
+
+        // Create invoice 2
+        // Scenario: single item
+        // * $5 item
+        // * $-5 CBA used
+        final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem2 = new FixedPriceInvoiceItem(invoice2.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem2 = new CreditBalanceAdjInvoiceItem(fixedItem2.getInvoiceId(), fixedItem2.getAccountId(),
+                                                                                                         fixedItem2.getStartDate(), fixedItem2.getAmount().negate(),
+                                                                                                         fixedItem2.getCurrency());
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem2, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem2, context);
+
+        // Create invoice 3
+        // Scenario: single item
+        // * $5 item
+        // * $-5 CBA used
+        final DefaultInvoice invoice3 = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem fixedItem3 = new FixedPriceInvoiceItem(invoice3.getId(), invoice1.getAccountId(), null, null, UUID.randomUUID().toString(),
+                                                                 UUID.randomUUID().toString(), clock.getUTCToday(), new BigDecimal("5"), Currency.USD);
+        final CreditBalanceAdjInvoiceItem creditBalanceAdjInvoiceItem3 = new CreditBalanceAdjInvoiceItem(fixedItem3.getInvoiceId(), fixedItem3.getAccountId(),
+                                                                                                         fixedItem3.getStartDate(), fixedItem3.getAmount().negate(),
+                                                                                                         fixedItem3.getCurrency());
+        invoiceDao.create(invoice3, invoice3.getTargetDate().getDayOfMonth(), true, context);
+        invoiceItemSqlDao.create(fixedItem3, context);
+        invoiceItemSqlDao.create(creditBalanceAdjInvoiceItem3, context);
+
+        // Verify scenario - all CBA should have been used
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+        verifyInvoice(invoice1.getId(), 10.00, 10.00);
+        verifyInvoice(invoice2.getId(), 0.00, -5.00);
+        verifyInvoice(invoice3.getId(), 0.00, -5.00);
+
+        // Delete the CBA on invoice 1
+        invoiceDao.deleteCBA(accountId, invoice1.getId(), creditBalanceAdjInvoiceItem1.getId(), context);
+
+        // Verify all three invoices were affected
+        Assert.assertEquals(invoiceDao.getAccountCBA(accountId).doubleValue(), 0.00);
+        verifyInvoice(invoice1.getId(), 0.00, 0.00);
+        verifyInvoice(invoice2.getId(), 5.00, 0.00);
+        verifyInvoice(invoice3.getId(), 5.00, 0.00);
+    }
+
+    private void verifyInvoice(final UUID invoiceId, final double balance, final double cbaAmount) throws InvoiceApiException {
+        final Invoice invoice = invoiceDao.getById(invoiceId);
+        Assert.assertEquals(invoice.getBalance().doubleValue(), balance);
+        Assert.assertEquals(invoice.getCBAAmount().doubleValue(), cbaAmount);
+    }
 }
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 dbf22b2..2fcfdbd 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
@@ -181,6 +181,24 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
     }
 
+    @DELETE
+    @Path("/{invoiceId:" + UUID_PATTERN + "}" + "/{invoiceItemId:" + UUID_PATTERN + "/cba")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response deleteCBA(@PathParam("invoiceId") final String invoiceId,
+                              @PathParam("invoiceItemId") final String invoiceItemId,
+                              @QueryParam(QUERY_ACCOUNT_ID) final String accountId,
+                              @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                              @HeaderParam(HDR_REASON) final String reason,
+                              @HeaderParam(HDR_COMMENT) final String comment) throws AccountApiException, InvoiceApiException {
+        final Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+        invoiceApi.deleteCBA(account.getId(), UUID.fromString(invoiceId), UUID.fromString(invoiceItemId),
+                             context.createContext(createdBy, reason, comment));
+
+        return Response.status(Status.OK).build();
+    }
+
     @POST
     @Path("/{invoiceId:" + UUID_PATTERN + "}")
     @Consumes(APPLICATION_JSON)