killbill-memoizeit

Fix issue with missing CBA use when creating CHARGE Fix Invoice

9/14/2012 11:04:27 PM

Details

diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
index 545e04d..b65cf34 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
@@ -34,9 +34,9 @@ public interface InvoicePaymentApi {
      */
     public List<Invoice> getAllInvoicesByAccount(UUID accountId);
 
-    public Invoice getInvoice(UUID invoiceId);
+    public Invoice getInvoice(UUID invoiceId) throws InvoiceApiException;
 
-    public Invoice getInvoiceForPaymentId(UUID paymentId);
+    public Invoice getInvoiceForPaymentId(UUID paymentId) throws InvoiceApiException;
 
     public List<InvoicePayment> getInvoicePayments(UUID paymentId);
 
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 3b78afe..fa58913 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
@@ -62,7 +62,7 @@ public interface InvoiceUserApi {
      * @param invoiceId invoice id
      * @return the invoice
      */
-    public Invoice getInvoice(UUID invoiceId);
+    public Invoice getInvoice(UUID invoiceId) throws InvoiceApiException;
 
     /**
      * Retrieve an invoice by invoice number.
@@ -70,7 +70,7 @@ public interface InvoiceUserApi {
      * @param number invoice number
      * @return the invoice
      */
-    public Invoice getInvoiceByNumber(Integer number);
+    public Invoice getInvoiceByNumber(Integer number) throws InvoiceApiException;
 
     /**
      * Record a payment for an invoice.
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
index 1cc24d8..e3cca6f 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
@@ -16,6 +16,11 @@
 
 package com.ning.billing.beatrix.util;
 
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
@@ -28,22 +33,17 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Inject;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 
-import com.google.common.collect.ImmutableList;
-import com.google.inject.Inject;
-
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
-
 public class InvoiceChecker {
 
     private static final Logger log = LoggerFactory.getLogger(InvoiceChecker.class);
@@ -57,29 +57,29 @@ public class InvoiceChecker {
         this.entitlementApi = entitlementApi;
     }
 
-    public void checkInvoice(final UUID accountId, final int invoiceOrderingNumber, final ExpectedItemCheck... expected) {
+    public void checkInvoice(final UUID accountId, final int invoiceOrderingNumber, final ExpectedItemCheck... expected) throws InvoiceApiException {
         checkInvoice(accountId, invoiceOrderingNumber, ImmutableList.<ExpectedItemCheck>copyOf(expected));
     }
 
-    public void checkInvoice(final UUID accountId, final int invoiceOrderingNumber, final List<ExpectedItemCheck> expected) {
+    public void checkInvoice(final UUID accountId, final int invoiceOrderingNumber, final List<ExpectedItemCheck> expected) throws InvoiceApiException {
         final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
         Assert.assertEquals(invoices.size(), invoiceOrderingNumber);
         final Invoice invoice = invoices.get(invoiceOrderingNumber - 1);
         checkInvoice(invoice.getId(), expected);
     }
 
-    public void checkRepairedInvoice(final UUID accountId, final int invoiceNb, final ExpectedItemCheck... expected) {
+    public void checkRepairedInvoice(final UUID accountId, final int invoiceNb, final ExpectedItemCheck... expected) throws InvoiceApiException {
         checkRepairedInvoice(accountId, invoiceNb, ImmutableList.<ExpectedItemCheck>copyOf(expected));
     }
 
-    public void checkRepairedInvoice(final UUID accountId, final int invoiceNb, final List<ExpectedItemCheck> expected) {
+    public void checkRepairedInvoice(final UUID accountId, final int invoiceNb, final List<ExpectedItemCheck> expected) throws InvoiceApiException {
         final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
         Assert.assertTrue(invoices.size() > invoiceNb);
         final Invoice invoice = invoices.get(invoiceNb - 1);
         checkInvoice(invoice.getId(), expected);
     }
 
-    public void checkInvoice(final UUID invoiceId, final List<ExpectedItemCheck> expected) {
+    public void checkInvoice(final UUID invoiceId, final List<ExpectedItemCheck> expected) throws InvoiceApiException {
         final Invoice invoice = invoiceUserApi.getInvoice(invoiceId);
         Assert.assertNotNull(invoice);
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index e6bb878..ab7cccc 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -18,7 +18,6 @@
 package com.ning.billing.invoice.api.invoice;
 
 import java.math.BigDecimal;
-import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -26,6 +25,9 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.skife.jdbi.v2.exceptions.TransactionFailedException;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
@@ -37,10 +39,6 @@ import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.DefaultInvoicePayment;
 import com.ning.billing.util.callcontext.CallContext;
 
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.inject.Inject;
-
 public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
 
     private static final WithInvoiceApiException<InvoicePayment> invoicePaymentWithException = new WithInvoiceApiException<InvoicePayment>();
@@ -63,12 +61,12 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     }
 
     @Override
-    public Invoice getInvoice(final UUID invoiceId) {
+    public Invoice getInvoice(final UUID invoiceId) throws InvoiceApiException {
         return dao.getById(invoiceId);
     }
 
     @Override
-    public Invoice getInvoiceForPaymentId(final UUID paymentId) {
+    public Invoice getInvoiceForPaymentId(final UUID paymentId) throws InvoiceApiException {
         final UUID invoiceIdStr = dao.getInvoiceIdByPaymentId(paymentId);
         return invoiceIdStr == null ? null : dao.getById(invoiceIdStr);
     }
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 bd524b2..11efa03 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,6 +27,7 @@ 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;
@@ -49,8 +50,6 @@ 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;
@@ -91,12 +90,12 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
-    public Invoice getInvoice(final UUID invoiceId) {
+    public Invoice getInvoice(final UUID invoiceId) throws InvoiceApiException {
         return dao.getById(invoiceId);
     }
 
     @Override
-    public Invoice getInvoiceByNumber(final Integer number) {
+    public Invoice getInvoiceByNumber(final Integer number) throws InvoiceApiException {
         return dao.getByNumber(number);
     }
 
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 ba3ec0c..b03626e 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
@@ -30,10 +30,17 @@ import org.joda.time.LocalDate;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.exceptions.TransactionFailedException;
 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;
@@ -65,13 +72,6 @@ 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);
@@ -150,25 +150,36 @@ public class AuditedInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public Invoice getById(final UUID invoiceId) {
-        return invoiceSqlDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
-            @Override
-            public Invoice inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
-                final Invoice invoice = invoiceDao.getById(invoiceId.toString());
-
-                if (invoice != null) {
+    public Invoice getById(final UUID invoiceId) throws InvoiceApiException {
+        try {
+            return invoiceSqlDao.inTransaction(new Transaction<Invoice, InvoiceSqlDao>() {
+                @Override
+                public Invoice inTransaction(final InvoiceSqlDao invoiceDao, final TransactionStatus status) throws Exception {
+                    final Invoice invoice = invoiceDao.getById(invoiceId.toString());
+                    if (invoice == null) {
+                        throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
+                    }
                     populateChildren(invoice, invoiceDao);
+                    return invoice;
                 }
-
-                return invoice;
+            });
+        } catch (TransactionFailedException e) {
+            if (e.getCause() instanceof InvoiceApiException) {
+                throw (InvoiceApiException) e.getCause();
+            } else {
+                throw e;
             }
-        });
+        }
     }
 
     @Override
-    public Invoice getByNumber(final Integer number) {
+    public Invoice getByNumber(final Integer number) throws InvoiceApiException {
         // The invoice number is just the record id
-        return invoiceSqlDao.getByRecordId(number.longValue());
+        final Invoice result = invoiceSqlDao.getByRecordId(number.longValue());
+        if (result == null) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, number);
+        }
+        return result;
     }
 
     @Override
@@ -576,7 +587,8 @@ 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) {
+                                            final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context)
+                                                    throws InvoiceApiException {
         return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
             @Override
             public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -595,9 +607,22 @@ public class AuditedInvoiceDao implements InvoiceDao {
                 final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
                 transInvoiceItemDao.create(externalCharge, context);
 
+                // At this point, reread the invoice and figure out if we need to consume some of the CBA
+                final Invoice invoice = transactional.getById(invoiceIdForExternalCharge.toString());
+                if (invoice == null) {
+                    throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceIdForExternalCharge);
+                }
+                populateChildren(invoice, transactional);
+
+                final BigDecimal accountCbaAvailable = getAccountCBAFromTransaction(invoice.getAccountId(), transactional);
+                if (accountCbaAvailable.compareTo(BigDecimal.ZERO) > 0 && invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
+                    final BigDecimal cbaAmountToConsume = accountCbaAvailable.compareTo(invoice.getBalance()) > 0 ? invoice.getBalance().negate() : accountCbaAvailable.negate();
+                    final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(), cbaAmountToConsume, invoice.getCurrency());
+                    transInvoiceItemDao.create(cbaAdjItem, context);
+                }
+
                 // Notify the bus since the balance of the invoice changed
                 notifyBusOfInvoiceAdjustment(transactional, invoiceId, accountId, context.getUserToken());
-
                 return externalCharge;
             }
         });
@@ -742,7 +767,6 @@ public class AuditedInvoiceDao implements InvoiceDao {
         for (final Invoice cur : invoices) {
             cba = cba.add(cur.getCBAAmount());
         }
-
         return cba;
     }
 
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 dbf2edb..207c1de 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
@@ -37,9 +37,9 @@ public interface InvoiceDao {
 
     void create(final Invoice invoice, final int billCycleDayUTC, final boolean isRealInvoice, final CallContext context);
 
-    Invoice getById(final UUID id);
+    Invoice getById(final UUID id) throws InvoiceApiException;
 
-    Invoice getByNumber(final Integer number);
+    Invoice getByNumber(final Integer number) throws InvoiceApiException;
 
     List<Invoice> get();
 
@@ -119,7 +119,7 @@ public interface InvoiceDao {
      * @return the newly created external charge invoice item
      */
     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);
+                                     final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context) throws InvoiceApiException;
 
     /**
      * Retrieve a credit by id.
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index 007f2f6..bb65134 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -32,11 +32,7 @@ import org.testng.annotations.Test;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceMigrationApi;
-import com.ning.billing.invoice.api.InvoicePaymentApi;
-import com.ning.billing.invoice.api.InvoiceUserApi;
-
-import com.google.inject.Inject;
+import com.ning.billing.invoice.api.InvoiceApiException;
 
 public class TestDefaultInvoiceMigrationApi extends InvoiceApiTestBase {
 
@@ -63,7 +59,7 @@ public class TestDefaultInvoiceMigrationApi extends InvoiceApiTestBase {
         regularInvoiceId = generateRegularInvoice(account, date_regular);
     }
 
-    private UUID createAndCheckMigrationInvoice(final UUID accountId) {
+    private UUID createAndCheckMigrationInvoice(final UUID accountId) throws InvoiceApiException {
         final UUID migrationInvoiceId = migrationApi.createMigrationInvoice(accountId, date_migrated, MIGRATION_INVOICE_AMOUNT, MIGRATION_INVOICE_CURRENCY);
         Assert.assertNotNull(migrationInvoiceId);
         //Double check it was created and values are correct
@@ -108,7 +104,7 @@ public class TestDefaultInvoiceMigrationApi extends InvoiceApiTestBase {
 
     // ACCOUNT balance should reflect total of migration and non-migration invoices
     @Test(groups = "slow")
-    public void testBalance() {
+    public void testBalance() throws InvoiceApiException{
         final Invoice migrationInvoice = invoiceDao.getById(migrationInvoiceId);
         final Invoice regularInvoice = invoiceDao.getById(regularInvoiceId);
         final BigDecimal balanceOfAllInvoices = migrationInvoice.getBalance().add(regularInvoice.getBalance());
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 39ee3ec..c245e29 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
@@ -78,7 +78,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
     }
 
     private void verifyExternalChargeOnNewInvoice(final BigDecimal initialAccountBalance, @Nullable final UUID bundleId,
-                                                  final BigDecimal externalChargeAmount, final InvoiceItem externalChargeInvoiceItem) {
+                                                  final BigDecimal externalChargeAmount, final InvoiceItem externalChargeInvoiceItem) throws InvoiceApiException {
         Assert.assertNotNull(externalChargeInvoiceItem.getInvoiceId());
         Assert.assertNotEquals(externalChargeInvoiceItem.getInvoiceId(), invoiceId);
         Assert.assertEquals(externalChargeInvoiceItem.getBundleId(), bundleId);
@@ -135,7 +135,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
     }
 
     private void verifyExternalChargeOnExistingInvoice(final BigDecimal initialInvoiceBalance, @Nullable final UUID bundleId,
-                                                       final BigDecimal externalChargeAmount, final InvoiceItem externalChargeInvoiceItem) {
+                                                       final BigDecimal externalChargeAmount, final InvoiceItem externalChargeInvoiceItem) throws InvoiceApiException {
         Assert.assertEquals(externalChargeInvoiceItem.getInvoiceId(), invoiceId);
         Assert.assertEquals(externalChargeInvoiceItem.getBundleId(), bundleId);
         Assert.assertEquals(externalChargeInvoiceItem.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java
index ab3b14d..0427972 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java
@@ -29,6 +29,7 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.google.common.collect.ImmutableMap;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.invoice.InvoiceTestSuite;
 import com.ning.billing.invoice.api.Invoice;
@@ -48,8 +49,6 @@ import com.ning.billing.util.tag.dao.MockTagDefinitionDao;
 import com.ning.billing.util.tag.dao.TagDao;
 import com.ning.billing.util.tag.dao.TagDefinitionDao;
 
-import com.google.common.collect.ImmutableMap;
-
 public class TestDefaultInvoiceDao extends InvoiceTestSuite {
 
     private InvoiceSqlDao invoiceSqlDao;
@@ -63,6 +62,7 @@ public class TestDefaultInvoiceDao extends InvoiceTestSuite {
         Mockito.when(idbi.onDemand(InvoiceSqlDao.class)).thenReturn(invoiceSqlDao);
         Mockito.when(invoiceSqlDao.getById(Mockito.anyString())).thenReturn(Mockito.mock(Invoice.class));
         Mockito.when(invoiceSqlDao.inTransaction(Mockito.<Transaction<Void, InvoiceSqlDao>>any())).thenAnswer(new Answer() {
+            @Override
             public Object answer(final InvocationOnMock invocation) {
                 final Object[] args = invocation.getArguments();
                 try {
@@ -125,7 +125,11 @@ public class TestDefaultInvoiceDao extends InvoiceTestSuite {
         Mockito.when(invoiceSqlDao.getByRecordId(number.longValue())).thenReturn(invoice);
 
         Assert.assertEquals(dao.getByNumber(number), invoice);
-        Assert.assertNull(dao.getByNumber(Integer.MIN_VALUE));
+        try {
+            dao.getByNumber(Integer.MIN_VALUE);
+            Assert.fail();
+        } catch (InvoiceApiException e) {
+        }
     }
 
     @Test(groups = "fast")
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 ee27de3..f7ba009 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
@@ -33,9 +33,11 @@ import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.mockito.Mockito;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableMap;
+import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.DefaultPrice;
 import com.ning.billing.catalog.MockInternationalPrice;
 import com.ning.billing.catalog.MockPlan;
@@ -95,7 +97,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
     }
 
     @Test(groups = "slow")
-    public void testInvoicePayment() {
+    public void testInvoicePayment() throws InvoiceApiException {
         final UUID accountId = UUID.randomUUID();
         final Invoice invoice = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
         final UUID invoiceId = invoice.getId();
@@ -129,9 +131,15 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
     }
 
     @Test(groups = "slow")
-    public void testRetrievalForNonExistentInvoiceId() {
-        final Invoice invoice = invoiceDao.getById(UUID.randomUUID());
-        assertNull(invoice);
+    public void testRetrievalForNonExistentInvoiceId()  throws InvoiceApiException {
+        try {
+            invoiceDao.getById(UUID.randomUUID());
+            Assert.fail();
+        } catch (InvoiceApiException e) {
+            if (e.getCode() != ErrorCode.INVOICE_NOT_FOUND.getCode()) {
+                Assert.fail();
+            }
+        }
     }
 
     @Test(groups = "slow")
@@ -656,6 +664,33 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
     }
 
     @Test(groups = "slow")
+    public void testExternalChargeWithCBA() throws InvoiceApiException {
+
+        final UUID accountId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
+        final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+
+        // CREATE INVOICE WITH A (just) CBA. Should not happen, but that does not matter for that test
+        final CreditBalanceAdjInvoiceItem cbaItem = new CreditBalanceAdjInvoiceItem(invoice1.getId(), accountId, new LocalDate(), new BigDecimal("20.0"), Currency.USD);
+        invoiceItemSqlDao.create(cbaItem, context);
+
+        final InvoiceItem charge =  invoiceDao.insertExternalCharge(accountId, null, bundleId, "bla", new BigDecimal("15.0"), clock.getUTCNow().toLocalDate(), Currency.USD, context);
+
+        final Invoice newInvoice = invoiceDao.getById(charge.getInvoiceId());
+        List<InvoiceItem> items = newInvoice.getInvoiceItems();
+        assertEquals(items.size(), 2);
+        for (InvoiceItem cur : items) {
+            if (!cur.getId().equals(charge.getId())) {
+                assertEquals(cur.getInvoiceItemType(), InvoiceItemType.CBA_ADJ);
+                assertTrue(cur.getAmount().compareTo(new BigDecimal("-15.00")) == 0);
+                break;
+            }
+        }
+    }
+
+    @Test(groups = "slow")
     public void testAccountBalanceWithAllSortsOfThings() {
         final UUID accountId = UUID.randomUUID();
         final UUID bundleId = UUID.randomUUID();
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
index e76e890..1fac6f7 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
@@ -101,7 +101,7 @@ public class TestInvoiceDaoForItemAdjustment extends InvoiceDaoTestBase {
         Assert.assertEquals(adjustedInvoiceItem.getAmount().compareTo(BigDecimal.TEN.negate()), 0);
     }
 
-    private InvoiceItem createAndCheckAdjustment(final Invoice invoice, final InvoiceItem invoiceItem, final BigDecimal amount) {
+    private InvoiceItem createAndCheckAdjustment(final Invoice invoice, final InvoiceItem invoiceItem, final BigDecimal amount) throws InvoiceApiException {
         final LocalDate effectiveDate = new LocalDate(2010, 1, 1);
         final InvoiceItem adjustedInvoiceItem = invoiceDao.insertInvoiceItemAdjustment(invoice.getAccountId(), invoice.getId(), invoiceItem.getId(),
                                                                                        effectiveDate, amount, null, context);
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
index 55b852f..241027b 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
@@ -15,24 +15,24 @@
  */
 package com.ning.billing.payment.core;
 
-import javax.annotation.Nullable;
-import javax.inject.Inject;
+import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeoutException;
 
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.name.Named;
@@ -42,6 +42,7 @@ import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.config.PaymentConfig;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.payment.api.DefaultPayment;
 import com.ning.billing.payment.api.DefaultPaymentErrorEvent;
@@ -53,7 +54,6 @@ import com.ning.billing.payment.api.PaymentStatus;
 import com.ning.billing.payment.dao.PaymentAttemptModelDao;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.dao.PaymentModelDao;
-import com.ning.billing.payment.dao.PaymentSqlDao;
 import com.ning.billing.payment.dao.RefundModelDao;
 import com.ning.billing.payment.dispatcher.PluginDispatcher;
 import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
@@ -63,7 +63,6 @@ import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
 import com.ning.billing.payment.retry.AutoPayRetryService.AutoPayRetryServiceScheduler;
 import com.ning.billing.payment.retry.FailedPaymentRetryService.FailedPaymentRetryServiceScheduler;
 import com.ning.billing.payment.retry.PluginFailureRetryService.PluginFailureRetryServiceScheduler;
-import com.ning.billing.util.api.TagApiException;
 import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.BusEvent;
@@ -72,12 +71,7 @@ import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.globallocker.GlobalLocker;
-import com.ning.billing.util.tag.ControlTagType;
-import com.ning.billing.util.tag.Tag;
-
-import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 
 public class PaymentProcessor extends ProcessorBase {
 
@@ -237,21 +231,25 @@ public class PaymentProcessor extends ProcessorBase {
                 public Payment doOperation() throws PaymentApiException {
 
 
-                    final Invoice invoice = invoicePaymentApi.getInvoice(invoiceId);
+                    try {
+                        final Invoice invoice = invoicePaymentApi.getInvoice(invoiceId);
 
-                    if (invoice.isMigrationInvoice()) {
-                        log.error("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
-                        return null;
-                    }
+                        if (invoice.isMigrationInvoice()) {
+                            log.error("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
+                            return null;
+                        }
 
-                    final boolean isAccountAutoPayOff = isAccountAutoPayOff(account.getId());
-                    setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(account.getId(), paymentMethodId, isAccountAutoPayOff, context, isInstantPayment);
+                        final boolean isAccountAutoPayOff = isAccountAutoPayOff(account.getId());
+                        setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(account.getId(), paymentMethodId, isAccountAutoPayOff, context, isInstantPayment);
 
-                    final BigDecimal requestedAmount = getAndValidatePaymentAmount(invoice, inputAmount, isInstantPayment);
-                    if (!isInstantPayment && isAccountAutoPayOff) {
-                        return processNewPaymentForAutoPayOffWithAccountLocked(paymentMethodId, account, invoice, requestedAmount, context);
-                    } else {
-                        return processNewPaymentWithAccountLocked(paymentMethodId, plugin, account, invoice, requestedAmount, isInstantPayment, context);
+                        final BigDecimal requestedAmount = getAndValidatePaymentAmount(invoice, inputAmount, isInstantPayment);
+                        if (!isInstantPayment && isAccountAutoPayOff) {
+                            return processNewPaymentForAutoPayOffWithAccountLocked(paymentMethodId, account, invoice, requestedAmount, context);
+                        } else {
+                            return processNewPaymentWithAccountLocked(paymentMethodId, plugin, account, invoice, requestedAmount, isInstantPayment, context);
+                        }
+                    } catch (InvoiceApiException e) {
+                        throw new PaymentApiException(e);
                     }
                 }
             }));
@@ -269,10 +267,6 @@ public class PaymentProcessor extends ProcessorBase {
         }
     }
 
-
-
-
-
     private void setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(final UUID accountId, final UUID paymentMethodId, final boolean isAccountAutoPayOff, final CallContext context, final boolean isInstantPayment)
     throws PaymentApiException  {
 
@@ -347,32 +341,34 @@ public class PaymentProcessor extends ProcessorBase {
 
                 @Override
                 public Void doOperation() throws PaymentApiException {
-
-                    // Fetch again with account lock this time
-                    final PaymentModelDao payment = paymentDao.getPayment(paymentId);
-                    boolean foundExpectedState = false;
-                    for (final PaymentStatus cur : expectedPaymentStates) {
-                        if (payment.getPaymentStatus() == cur) {
-                            foundExpectedState = true;
-                            break;
+                    try {
+                        // Fetch again with account lock this time
+                        final PaymentModelDao payment = paymentDao.getPayment(paymentId);
+                        boolean foundExpectedState = false;
+                        for (final PaymentStatus cur : expectedPaymentStates) {
+                            if (payment.getPaymentStatus() == cur) {
+                                foundExpectedState = true;
+                                break;
+                            }
+                        }
+                        if (!foundExpectedState) {
+                            log.info("Aborted retry for payment {} because it is {} state", paymentId, payment.getPaymentStatus());
+                            return null;
                         }
-                    }
-                    if (!foundExpectedState) {
-                        log.info("Aborted retry for payment {} because it is {} state", paymentId, payment.getPaymentStatus());
-                        return null;
-                    }
 
-                    final Invoice invoice = invoicePaymentApi.getInvoice(payment.getInvoiceId());
-                    if (invoice.isMigrationInvoice()) {
-                        return null;
-                    }
-                    if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
-                        log.info("Aborted retry for payment {} because invoice has been paid", paymentId);
+                        final Invoice invoice = invoicePaymentApi.getInvoice(payment.getInvoiceId());
+                        if (invoice.isMigrationInvoice()) {
+                            return null;
+                        }
+                        if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
+                            log.info("Aborted retry for payment {} because invoice has been paid", paymentId);
+                            return null;
+                        }
+                        processRetryPaymentWithAccountLocked(plugin, account, invoice, payment, invoice.getBalance(), context);
                         return null;
+                    } catch (InvoiceApiException e) {
+                        throw new PaymentApiException(e);
                     }
-                    processRetryPaymentWithAccountLocked(plugin, account, invoice, payment, invoice.getBalance(), context);
-                    return null;
-
                 }
             }));
         } catch (AccountApiException e) {
diff --git a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
index a254ff2..3184081 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.payment.core;
 
+import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -31,6 +33,12 @@ import javax.inject.Inject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.name.Named;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
@@ -56,15 +64,6 @@ import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.globallocker.GlobalLocker;
 
-import com.google.common.base.Function;
-import com.google.common.base.Objects;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableMap;
-import com.google.inject.name.Named;
-
-import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
-
 public class RefundProcessor extends ProcessorBase {
 
     private static final Logger log = LoggerFactory.getLogger(RefundProcessor.class);
@@ -188,21 +187,26 @@ public class RefundProcessor extends ProcessorBase {
      * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
      * @return the refund amount
      */
-    private BigDecimal computeRefundAmount(final UUID paymentId, @Nullable final BigDecimal specifiedRefundAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) {
-        final List<InvoiceItem> items = invoicePaymentApi.getInvoiceForPaymentId(paymentId).getInvoiceItems();
+    private BigDecimal computeRefundAmount(final UUID paymentId, @Nullable final BigDecimal specifiedRefundAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts)
+            throws PaymentApiException {
+        try {
+            final List<InvoiceItem> items = invoicePaymentApi.getInvoiceForPaymentId(paymentId).getInvoiceItems();
 
-        BigDecimal amountFromItems = BigDecimal.ZERO;
-        for (final UUID itemId : invoiceItemIdsWithAmounts.keySet()) {
-            amountFromItems = amountFromItems.add(Objects.firstNonNull(invoiceItemIdsWithAmounts.get(itemId),
-                                                                       getAmountFromItem(items, itemId)));
-        }
+            BigDecimal amountFromItems = BigDecimal.ZERO;
+            for (final UUID itemId : invoiceItemIdsWithAmounts.keySet()) {
+                amountFromItems = amountFromItems.add(Objects.firstNonNull(invoiceItemIdsWithAmounts.get(itemId),
+                        getAmountFromItem(items, itemId)));
+            }
 
-        // Sanity check: if some items were specified, then the sum should be equal to specified refund amount, if specified
-        if (amountFromItems.compareTo(BigDecimal.ZERO) != 0 && specifiedRefundAmount != null && specifiedRefundAmount.compareTo(amountFromItems) != 0) {
-            throw new IllegalArgumentException("You can't specify a refund amount that doesn't match the invoice items amounts");
-        }
+            // Sanity check: if some items were specified, then the sum should be equal to specified refund amount, if specified
+            if (amountFromItems.compareTo(BigDecimal.ZERO) != 0 && specifiedRefundAmount != null && specifiedRefundAmount.compareTo(amountFromItems) != 0) {
+                throw new IllegalArgumentException("You can't specify a refund amount that doesn't match the invoice items amounts");
+            }
 
-        return Objects.firstNonNull(specifiedRefundAmount, amountFromItems);
+            return Objects.firstNonNull(specifiedRefundAmount, amountFromItems);
+        } catch (InvoiceApiException e) {
+            throw new PaymentApiException(e);
+        }
     }
 
     private BigDecimal getAmountFromItem(final List<InvoiceItem> items, final UUID itemId) {
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
index ed4c905..568e45d 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestHelper.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -18,7 +18,6 @@ package com.ning.billing.payment;
 
 import java.util.UUID;
 
-import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.mockito.Mockito;
 
@@ -27,6 +26,7 @@ import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoiceCreationEvent;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
@@ -65,7 +65,7 @@ public class TestHelper {
     public Invoice createTestInvoice(final Account account,
                                      final LocalDate targetDate,
                                      final Currency currency,
-                                     final InvoiceItem... items) throws EventBusException {
+                                     final InvoiceItem... items) throws EventBusException, InvoiceApiException {
         final Invoice invoice = new MockInvoice(account.getId(), clock.getUTCToday(), targetDate, currency);
 
         for (final InvoiceItem item : items) {