killbill-memoizeit

invoice: fix invoice item adjustments path for invoice plugins Signed-off-by:

2/1/2015 8:11:20 PM

Details

diff --git a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
index cc1c061..c54ef47 100644
--- a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -22,15 +24,10 @@ import java.util.Map;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-
-import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.invoice.api.Invoice;
-import org.killbill.billing.invoice.api.InvoiceApiException;
-import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Currency;
 
 public interface InvoiceInternalApi {
 
@@ -67,15 +64,13 @@ public interface InvoiceInternalApi {
     public InvoicePayment createRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts,
                                        String transactionExternalKey, InternalCallContext context) throws InvoiceApiException;
 
-
     public InvoicePayment createChargeback(UUID paymentId, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
 
     /**
      * Rebalance CBA for account which have credit and unpaid invoices
      *
      * @param accountId account id
-     * @param context the callcontext
+     * @param context   the callcontext
      */
     public void consumeExistingCBAOnAccountWithUnpaidInvoices(final UUID accountId, final InternalCallContext context) throws InvoiceApiException;
-
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
new file mode 100644
index 0000000..91919ac
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/InvoiceApiHelper.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.api;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.invoice.InvoicePluginDispatcher;
+import org.killbill.billing.invoice.dao.InvoiceDao;
+import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
+import org.killbill.billing.invoice.dao.InvoiceModelDao;
+import org.killbill.billing.invoice.model.InvoiceItemFactory;
+import org.killbill.billing.invoice.model.ItemAdjInvoiceItem;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.globallocker.LockerType;
+import org.killbill.commons.locker.GlobalLock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.LockFailedException;
+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.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class InvoiceApiHelper {
+
+    private static final Logger log = LoggerFactory.getLogger(InvoiceApiHelper.class);
+
+    private static final int NB_LOCK_TRY = 5;
+
+    private final InvoicePluginDispatcher invoicePluginDispatcher;
+    private final InvoiceDao dao;
+    private final GlobalLocker locker;
+    private final InternalCallContextFactory internalCallContextFactory;
+
+    @Inject
+    public InvoiceApiHelper(final InvoicePluginDispatcher invoicePluginDispatcher, final InvoiceDao dao, final GlobalLocker locker, final InternalCallContextFactory internalCallContextFactory) {
+        this.invoicePluginDispatcher = invoicePluginDispatcher;
+        this.dao = dao;
+        this.locker = locker;
+        this.internalCallContextFactory = internalCallContextFactory;
+    }
+
+    public List<InvoiceItem> dispatchToInvoicePluginsAndInsertItems(final UUID accountId, final WithAccountLock withAccountLock, final CallContext context) throws InvoiceApiException {
+        GlobalLock lock = null;
+        try {
+            lock = locker.lockWithNumberOfTries(LockerType.ACCOUNT_FOR_INVOICE_PAYMENTS.toString(), accountId.toString(), NB_LOCK_TRY);
+
+            final Iterable<Invoice> invoicesForPlugins = withAccountLock.prepareInvoices();
+
+            final List<InvoiceModelDao> invoiceModelDaos = new LinkedList<InvoiceModelDao>();
+            for (final Invoice invoiceForPlugin : invoicesForPlugins) {
+                // Call plugin
+                final List<InvoiceItem> additionalInvoiceItems = invoicePluginDispatcher.getAdditionalInvoiceItems(invoiceForPlugin, context);
+                invoiceForPlugin.addInvoiceItems(additionalInvoiceItems);
+
+                // Transformation to InvoiceModelDao
+                final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoiceForPlugin);
+                final List<InvoiceItem> invoiceItems = invoiceForPlugin.getInvoiceItems();
+                final List<InvoiceItemModelDao> invoiceItemModelDaos = toInvoiceItemModelDao(invoiceItems);
+                invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
+
+                // Keep track of modified invoices
+                invoiceModelDaos.add(invoiceModelDao);
+            }
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(accountId, context);
+            final List<InvoiceItemModelDao> createdInvoiceItems = dao.createInvoices(invoiceModelDaos, internalCallContext);
+            return fromInvoiceItemModelDao(createdInvoiceItems);
+        } catch (final LockFailedException e) {
+            log.error(String.format("Failed to process invoice items for account %s", accountId.toString()), e);
+            return ImmutableList.<InvoiceItem>of();
+        } finally {
+            if (lock != null) {
+                lock.release();
+            }
+        }
+    }
+
+    /**
+     * Create an adjustment for a given invoice item. This just creates the object in memory, it doesn't write it to disk.
+     *
+     * @param invoiceToBeAdjusted the invoice
+     * @param invoiceItemId       the invoice item id to adjust
+     * @param positiveAdjAmount   the amount to adjust. Pass null to adjust the full amount of the original item
+     * @param currency            the currency of the amount. Pass null to default to the original currency used
+     * @param effectiveDate       adjustment effective date, in the account timezone
+     * @return the adjustment item
+     */
+    public InvoiceItem createAdjustmentItem(final Invoice invoiceToBeAdjusted, final UUID invoiceItemId,
+                                            @Nullable final BigDecimal positiveAdjAmount, @Nullable final Currency currency,
+                                            final LocalDate effectiveDate, final InternalCallContext context) throws InvoiceApiException {
+        final InvoiceItem invoiceItemToBeAdjusted = Iterables.<InvoiceItem>tryFind(invoiceToBeAdjusted.getInvoiceItems(),
+                                                                                   new Predicate<InvoiceItem>() {
+                                                                                       @Override
+                                                                                       public boolean apply(final InvoiceItem input) {
+                                                                                           return input.getId().equals(invoiceItemId);
+                                                                                       }
+                                                                                   }).orNull();
+        if (invoiceItemToBeAdjusted == null) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
+        }
+
+        // Retrieve the amount and currency if needed
+        final BigDecimal amountToAdjust = Objects.firstNonNull(positiveAdjAmount, invoiceItemToBeAdjusted.getAmount());
+        // Check the specified currency matches the one of the existing invoice
+        final Currency currencyForAdjustment = Objects.firstNonNull(currency, invoiceItemToBeAdjusted.getCurrency());
+        if (invoiceItemToBeAdjusted.getCurrency() != currencyForAdjustment) {
+            throw new InvoiceApiException(ErrorCode.CURRENCY_INVALID, currency, invoiceItemToBeAdjusted.getCurrency());
+        }
+
+        // Finally, create the adjustment
+        return new ItemAdjInvoiceItem(UUID.randomUUID(),
+                                      context.getCreatedDate(),
+                                      invoiceItemToBeAdjusted.getInvoiceId(),
+                                      invoiceItemToBeAdjusted.getAccountId(),
+                                      effectiveDate,
+                                      null,
+                                      // Note! The amount is negated here!
+                                      amountToAdjust.negate(),
+                                      currencyForAdjustment,
+                                      invoiceItemToBeAdjusted.getId());
+    }
+
+    private List<InvoiceItem> fromInvoiceItemModelDao(final Collection<InvoiceItemModelDao> invoiceItemModelDaos) {
+        return ImmutableList.<InvoiceItem>copyOf(Collections2.transform(invoiceItemModelDaos,
+                                                                        new Function<InvoiceItemModelDao, InvoiceItem>() {
+                                                                            @Override
+                                                                            public InvoiceItem apply(final InvoiceItemModelDao input) {
+                                                                                return InvoiceItemFactory.fromModelDao(input);
+                                                                            }
+                                                                        }));
+    }
+
+    private List<InvoiceItemModelDao> toInvoiceItemModelDao(final Collection<InvoiceItem> invoiceItems) {
+        return ImmutableList.copyOf(Collections2.transform(invoiceItems,
+                                                           new Function<InvoiceItem, InvoiceItemModelDao>() {
+                                                               @Override
+                                                               public InvoiceItemModelDao apply(final InvoiceItem input) {
+                                                                   return new InvoiceItemModelDao(input);
+                                                               }
+                                                           }));
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
index 3ac0eb6..db51bbb 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -25,37 +27,33 @@ import java.util.UUID;
 import javax.inject.Inject;
 
 import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.clock.Clock;
-import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceApiHelper;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.killbill.billing.invoice.api.WithAccountLock;
 import org.killbill.billing.invoice.dao.InvoiceDao;
 import org.killbill.billing.invoice.dao.InvoiceModelDao;
 import org.killbill.billing.invoice.dao.InvoicePaymentModelDao;
 import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.invoice.model.DefaultInvoicePayment;
-import org.killbill.billing.invoice.notification.NextBillingDatePoster;
-import org.killbill.billing.subscription.api.SubscriptionBase;
-import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
-import org.killbill.billing.util.timezone.DateAndTimeZoneContext;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 
 public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
@@ -63,18 +61,16 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
     private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceInternalApi.class);
 
     private final InvoiceDao dao;
-    private final NextBillingDatePoster nextBillingDatePoster;
-    private final SubscriptionBaseInternalApi subscriptionBaseApi;
-    private final Clock clock;
+    private final InvoiceApiHelper invoiceApiHelper;
+    private final InternalCallContextFactory internalCallContextFactory;
 
     @Inject
-    public DefaultInvoiceInternalApi(final InvoiceDao dao, final SubscriptionBaseInternalApi subscriptionBaseApi,
-                                     final Clock clock,
-                                     final NextBillingDatePoster nextBillingDatePoster) {
+    public DefaultInvoiceInternalApi(final InvoiceDao dao,
+                                     final InvoiceApiHelper invoiceApiHelper,
+                                     final InternalCallContextFactory internalCallContextFactory) {
         this.dao = dao;
-        this.clock = clock;
-        this.subscriptionBaseApi = subscriptionBaseApi;
-        this.nextBillingDatePoster = nextBillingDatePoster;
+        this.invoiceApiHelper = invoiceApiHelper;
+        this.internalCallContextFactory = internalCallContextFactory;
     }
 
     @Override
@@ -134,7 +130,22 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
         if (amount.compareTo(BigDecimal.ZERO) <= 0) {
             throw new InvoiceApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL, paymentId, amount);
         }
-        return new DefaultInvoicePayment(dao.createRefund(paymentId, amount, isInvoiceAdjusted, invoiceItemIdsWithAmounts, transactionExternalKey, context));
+
+        final InvoicePaymentModelDao refund = dao.createRefund(paymentId, amount, isInvoiceAdjusted, invoiceItemIdsWithAmounts, transactionExternalKey, context);
+
+        // See https://github.com/killbill/killbill/issues/265
+        final CallContext callContext = internalCallContextFactory.createCallContext(context);
+        final Invoice invoice = getInvoiceById(refund.getInvoiceId(), context);
+        final UUID accountId = invoice.getAccountId();
+        final WithAccountLock withAccountLock = new WithAccountLock() {
+            @Override
+            public Iterable<Invoice> prepareInvoices() throws InvoiceApiException {
+                return ImmutableList.<Invoice>of(invoice);
+            }
+        };
+        final List<InvoiceItem> createdInvoiceItems = invoiceApiHelper.dispatchToInvoicePluginsAndInsertItems(accountId, withAccountLock, callContext);
+
+        return new DefaultInvoicePayment(refund);
     }
 
     @Override
@@ -154,7 +165,7 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
                 return new DefaultInvoicePayment(input);
             }
         });
-        if (invoicePayments.size() == 0) {
+        if (invoicePayments.isEmpty()) {
             return null;
         }
         return Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index ad59ef2..b62ab84 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -22,7 +22,6 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -40,14 +39,14 @@ import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.InvoiceDispatcher;
-import org.killbill.billing.invoice.InvoicePluginDispatcher;
 import org.killbill.billing.invoice.api.DryRunArguments;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceApiHelper;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
+import org.killbill.billing.invoice.api.WithAccountLock;
 import org.killbill.billing.invoice.dao.InvoiceDao;
-import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
 import org.killbill.billing.invoice.dao.InvoiceModelDao;
 import org.killbill.billing.invoice.model.CreditAdjInvoiceItem;
 import org.killbill.billing.invoice.model.DefaultInvoice;
@@ -62,14 +61,10 @@ import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
-import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.bus.api.PersistentBus.EventBusException;
-import org.killbill.commons.locker.GlobalLock;
-import org.killbill.commons.locker.GlobalLocker;
-import org.killbill.commons.locker.LockFailedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -86,30 +81,31 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
 
     private static final Logger log = LoggerFactory.getLogger(DefaultInvoiceUserApi.class);
 
-    private static final int NB_LOCK_TRY = 5;
-
     private final InvoiceDao dao;
     private final InvoiceDispatcher dispatcher;
-    private final InvoicePluginDispatcher invoicePluginDispatcher;
     private final AccountInternalApi accountUserApi;
     private final TagInternalApi tagApi;
+    private final InvoiceApiHelper invoiceApiHelper;
     private final HtmlInvoiceGenerator generator;
     private final InternalCallContextFactory internalCallContextFactory;
-    private final GlobalLocker locker;
     private final PersistentBus eventBus;
 
     @Inject
-    public DefaultInvoiceUserApi(final InvoiceDao dao, final InvoiceDispatcher dispatcher, final InvoicePluginDispatcher invoicePluginDispatcher, final AccountInternalApi accountUserApi,
-                                 final GlobalLocker locker, final PersistentBus eventBus,
-                                 final TagInternalApi tagApi, final HtmlInvoiceGenerator generator, final InternalCallContextFactory internalCallContextFactory) {
+    public DefaultInvoiceUserApi(final InvoiceDao dao,
+                                 final InvoiceDispatcher dispatcher,
+                                 final AccountInternalApi accountUserApi,
+                                 final PersistentBus eventBus,
+                                 final TagInternalApi tagApi,
+                                 final InvoiceApiHelper invoiceApiHelper,
+                                 final HtmlInvoiceGenerator generator,
+                                 final InternalCallContextFactory internalCallContextFactory) {
         this.dao = dao;
         this.dispatcher = dispatcher;
-        this.invoicePluginDispatcher = invoicePluginDispatcher;
         this.accountUserApi = accountUserApi;
         this.tagApi = tagApi;
+        this.invoiceApiHelper = invoiceApiHelper;
         this.generator = generator;
         this.internalCallContextFactory = internalCallContextFactory;
-        this.locker = locker;
         this.eventBus = eventBus;
     }
 
@@ -311,7 +307,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
             }
         };
 
-        return dispatchToInvoicePluginsAndInsertItems(accountId, withAccountLock, context);
+        return invoiceApiHelper.dispatchToInvoicePluginsAndInsertItems(accountId, withAccountLock, context);
     }
 
     @Override
@@ -349,11 +345,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
                 if (invoiceId == null) {
                     invoiceForCredit = new DefaultInvoice(accountId, effectiveDate, effectiveDate, currency);
                 } else {
-                    invoiceForCredit = getInvoice(invoiceId, context);
-                    // Check the specified currency matches the one of the existing invoice
-                    if (invoiceForCredit.getCurrency() != currency) {
-                        throw new InvoiceApiException(ErrorCode.CURRENCY_INVALID, currency, invoiceForCredit.getCurrency());
-                    }
+                    invoiceForCredit = getInvoiceAndCheckCurrency(invoiceId, currency, context);
                 }
 
                 // Create the new credit
@@ -371,7 +363,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
             }
         };
 
-        final List<InvoiceItem> creditInvoiceItems = dispatchToInvoicePluginsAndInsertItems(accountId, withAccountLock, context);
+        final List<InvoiceItem> creditInvoiceItems = invoiceApiHelper.dispatchToInvoicePluginsAndInsertItems(accountId, withAccountLock, context);
         Preconditions.checkState(creditInvoiceItems.size() == 1, "Should have created a single credit invoice item: " + creditInvoiceItems);
 
         return creditInvoiceItems.get(0);
@@ -391,9 +383,26 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
             throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_SHOULD_BE_POSITIVE, amount);
         }
 
-        // TODO
-        final InvoiceItemModelDao adjustment = dao.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, amount, currency, internalCallContextFactory.createInternalCallContext(accountId, context));
-        return InvoiceItemFactory.fromModelDao(adjustment);
+        final WithAccountLock withAccountLock = new WithAccountLock() {
+            @Override
+            public Iterable<Invoice> prepareInvoices() throws InvoiceApiException {
+                final Invoice invoice = getInvoiceAndCheckCurrency(invoiceId, currency, context);
+                final InvoiceItem adjustmentItem = invoiceApiHelper.createAdjustmentItem(invoice,
+                                                                                         invoiceItemId,
+                                                                                         amount,
+                                                                                         currency,
+                                                                                         effectiveDate,
+                                                                                         internalCallContextFactory.createInternalCallContext(context));
+                invoice.addInvoiceItem(adjustmentItem);
+
+                return ImmutableList.<Invoice>of(invoice);
+            }
+        };
+
+        final List<InvoiceItem> adjustmentInvoiceItems = invoiceApiHelper.dispatchToInvoicePluginsAndInsertItems(accountId, withAccountLock, context);
+        Preconditions.checkState(adjustmentInvoiceItems.size() == 1, "Should have created a single adjustment item: " + adjustmentInvoiceItems);
+
+        return adjustmentInvoiceItems.get(0);
     }
 
     @Override
@@ -448,64 +457,12 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
                                                                     }));
     }
 
-    private List<InvoiceItem> fromInvoiceItemModelDao(final Collection<InvoiceItemModelDao> invoiceItemModelDaos) {
-        return ImmutableList.<InvoiceItem>copyOf(Collections2.transform(invoiceItemModelDaos,
-                                                                        new Function<InvoiceItemModelDao, InvoiceItem>() {
-                                                                            @Override
-                                                                            public InvoiceItem apply(final InvoiceItemModelDao input) {
-                                                                                return InvoiceItemFactory.fromModelDao(input);
-                                                                            }
-                                                                        }));
-    }
-
-    private List<InvoiceItemModelDao> toInvoiceItemModelDao(final Collection<InvoiceItem> invoiceItems) {
-        return ImmutableList.copyOf(Collections2.transform(invoiceItems,
-                                                           new Function<InvoiceItem, InvoiceItemModelDao>() {
-                                                               @Override
-                                                               public InvoiceItemModelDao apply(final InvoiceItem input) {
-                                                                   return new InvoiceItemModelDao(input);
-                                                               }
-                                                           }));
-    }
-
-    public interface WithAccountLock {
-
-        public Iterable<Invoice> prepareInvoices() throws InvoiceApiException;
-    }
-
-    private List<InvoiceItem> dispatchToInvoicePluginsAndInsertItems(final UUID accountId, final WithAccountLock withAccountLock, final CallContext context) throws InvoiceApiException {
-        GlobalLock lock = null;
-        try {
-            lock = locker.lockWithNumberOfTries(LockerType.ACCOUNT_FOR_INVOICE_PAYMENTS.toString(), accountId.toString(), NB_LOCK_TRY);
-
-            final Iterable<Invoice> invoicesForPlugins = withAccountLock.prepareInvoices();
-
-            final List<InvoiceModelDao> invoiceModelDaos = new LinkedList<InvoiceModelDao>();
-            for (final Invoice invoiceForPlugin : invoicesForPlugins) {
-                // Call plugin
-                final List<InvoiceItem> additionalInvoiceItems = invoicePluginDispatcher.getAdditionalInvoiceItems(invoiceForPlugin, context);
-                invoiceForPlugin.addInvoiceItems(additionalInvoiceItems);
-
-                // Transformation to InvoiceModelDao
-                final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoiceForPlugin);
-                final List<InvoiceItem> invoiceItems = invoiceForPlugin.getInvoiceItems();
-                final List<InvoiceItemModelDao> invoiceItemModelDaos = toInvoiceItemModelDao(invoiceItems);
-                invoiceModelDao.addInvoiceItems(invoiceItemModelDaos);
-
-                // Keep track of modified invoices
-                invoiceModelDaos.add(invoiceModelDao);
-            }
-
-            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(accountId, context);
-            final List<InvoiceItemModelDao> createdInvoiceItems = dao.createInvoices(invoiceModelDaos, internalCallContext);
-            return fromInvoiceItemModelDao(createdInvoiceItems);
-        } catch (final LockFailedException e) {
-            log.error(String.format("Failed to process invoice items for account %s", accountId.toString()), e);
-            return ImmutableList.<InvoiceItem>of();
-        } finally {
-            if (lock != null) {
-                lock.release();
-            }
+    private Invoice getInvoiceAndCheckCurrency(final UUID invoiceId, @Nullable final Currency currency, final TenantContext context) throws InvoiceApiException {
+        final Invoice invoice = getInvoice(invoiceId, context);
+        // Check the specified currency matches the one of the existing invoice
+        if (currency != null && invoice.getCurrency() != currency) {
+            throw new InvoiceApiException(ErrorCode.CURRENCY_INVALID, currency, invoice.getCurrency());
         }
+        return invoice;
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/WithAccountLock.java b/invoice/src/main/java/org/killbill/billing/invoice/api/WithAccountLock.java
new file mode 100644
index 0000000..165c100
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/WithAccountLock.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.api;
+
+public interface WithAccountLock {
+
+    public Iterable<Invoice> prepareInvoices() throws InvoiceApiException;
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index e325360..23e49be 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -243,9 +243,12 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 final List<InvoiceItemModelDao> createdInvoiceItems = new LinkedList<InvoiceItemModelDao>();
                 for (final InvoiceModelDao invoiceModelDao : invoices) {
+                    boolean madeChanges = false;
+
                     // Create the invoice if needed
                     if (invoiceSqlDao.getById(invoiceModelDao.getId().toString(), context) == null) {
                         invoiceSqlDao.create(invoiceModelDao, context);
+                        madeChanges = true;
                     }
 
                     // Create the invoice items if needed
@@ -253,14 +256,17 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                         if (transInvoiceItemSqlDao.getById(invoiceItemModelDao.getId().toString(), context) == null) {
                             transInvoiceItemSqlDao.create(invoiceItemModelDao, context);
                             createdInvoiceItems.add(transInvoiceItemSqlDao.getById(invoiceItemModelDao.getId().toString(), context));
+                            madeChanges = true;
                         }
                     }
 
-                    cbaDao.addCBAComplexityFromTransaction(invoiceModelDao.getId(), entitySqlDaoWrapperFactory, context);
+                    if (madeChanges) {
+                        cbaDao.addCBAComplexityFromTransaction(invoiceModelDao.getId(), entitySqlDaoWrapperFactory, context);
 
-                    // Notify the bus since the balance of the invoice changed
-                    // TODO should we post an InvoiceCreationInternalEvent event instead? Note! This will trigger a payment (see InvoiceHandler)
-                    notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceModelDao.getId(), invoiceModelDao.getAccountId(), context.getUserToken(), context);
+                        // Notify the bus since the balance of the invoice changed
+                        // TODO should we post an InvoiceCreationInternalEvent event instead? Note! This will trigger a payment (see InvoiceHandler)
+                        notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceModelDao.getId(), invoiceModelDao.getAccountId(), context.getUserToken(), context);
+                    }
                 }
 
                 return createdInvoiceItems;
@@ -650,26 +656,6 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     }
 
     @Override
-    public InvoiceItemModelDao insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
-                                                           final LocalDate effectiveDate, @Nullable final BigDecimal positiveAdjAmount,
-                                                           @Nullable final Currency currency, final InternalCallContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<InvoiceItemModelDao>() {
-            @Override
-            public InvoiceItemModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
-                final InvoiceItemModelDao invoiceItemAdjustment = invoiceDaoHelper.createAdjustmentItem(entitySqlDaoWrapperFactory, invoiceId, invoiceItemId, positiveAdjAmount,
-                                                                                                        currency, effectiveDate, context);
-                invoiceDaoHelper.insertItem(entitySqlDaoWrapperFactory, invoiceItemAdjustment, context);
-
-                cbaDao.addCBAComplexityFromTransaction(invoiceId, entitySqlDaoWrapperFactory, context);
-
-                notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceId, accountId, context.getUserToken(), context);
-
-                return invoiceItemAdjustment;
-            }
-        });
-    }
-
-    @Override
     public void deleteCBA(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final InternalCallContext context) throws InvoiceApiException {
         transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
index ad63d71..7f2ed99 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -114,22 +116,6 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
     InvoiceItemModelDao getCreditById(UUID creditId, InternalTenantContext context) throws InvoiceApiException;
 
     /**
-     * Adjust an invoice item.
-     *
-     * @param accountId     the account id
-     * @param invoiceId     the invoice id
-     * @param invoiceItemId the invoice item id to adjust
-     * @param effectiveDate adjustment effective date, in the account timezone
-     * @param amount        the amount to adjust. Pass null to adjust the full amount of the original item
-     * @param currency      the currency of the amount. Pass null to default to the original currency used
-     * @param context       the call callcontext
-     * @return the newly created adjustment item
-     */
-    // TODO Needed?
-    InvoiceItemModelDao insertInvoiceItemAdjustment(UUID accountId, UUID invoiceId, UUID invoiceItemId, LocalDate effectiveDate,
-                                                    @Nullable BigDecimal amount, @Nullable Currency currency, InternalCallContext context);
-
-    /**
      * Delete a CBA item.
      *
      * @param accountId     the account id
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
index e885607..56beb81 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -178,13 +180,12 @@ public class InvoiceDaoHelper {
      * @param currency          the currency of the amount. Pass null to default to the original currency used
      * @return the adjustment item
      */
-    // TODO change refund path too to call the plugin
-    public InvoiceItemModelDao createAdjustmentItem(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID invoiceId, final UUID invoiceItemId,
+    public InvoiceItemModelDao createAdjustmentItem(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID invoiceId, final UUID invoiceItemId,
                                                     final BigDecimal positiveAdjAmount, final Currency currency,
                                                     final LocalDate effectiveDate, final InternalCallContext context) throws InvoiceApiException {
         // First, retrieve the invoice item in question
-        final InvoiceItemSqlDao invoiceItemSqlDaoX = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-        final InvoiceItemModelDao invoiceItemToBeAXdjusted = invoiceItemSqlDao.getById(invoiceItemId.toString(), context);
+        final InvoiceItemSqlDao invoiceItemSqlDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+        final InvoiceItemModelDao invoiceItemToBeAdjusted = invoiceItemSqlDao.getById(invoiceItemId.toString(), context);
         if (invoiceItemToBeAdjusted == null) {
             throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
         }
@@ -205,21 +206,6 @@ public class InvoiceDaoHelper {
                                        null, null, null, null, null, null, effectiveDate, effectiveDate, amountToAdjust.negate(), null, currencyForAdjustment, invoiceItemToBeAdjusted.getId());
     }
 
-    /**
-     * Create an invoice item
-     *
-     * @param entitySqlDaoWrapperFactory the EntitySqlDaoWrapperFactory from the current transaction
-     * @param item                       the invoice item to create
-     * @param context                    the call callcontext
-     */
-    public InvoiceItemModelDao insertItem(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory,
-                                          final InvoiceItemModelDao item,
-                                          final InternalCallContext context) throws EntityPersistenceException {
-        final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-        transInvoiceItemDao.create(item, context);
-        return transInvoiceItemDao.getById(item.getId().toString(), context);
-    }
-
     public void populateChildren(final InvoiceModelDao invoice, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final InternalTenantContext context) {
         getInvoiceItemsWithinTransaction(ImmutableList.<InvoiceModelDao>of(invoice), entitySqlDaoWrapperFactory, context);
         getInvoicePaymentsWithinTransaction(ImmutableList.<InvoiceModelDao>of(invoice), entitySqlDaoWrapperFactory, context);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
index 1b56ec7..f790741 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -22,6 +22,7 @@ import org.killbill.billing.glue.InvoiceModule;
 import org.killbill.billing.invoice.InvoiceListener;
 import org.killbill.billing.invoice.InvoiceTagHandler;
 import org.killbill.billing.invoice.api.DefaultInvoiceService;
+import org.killbill.billing.invoice.api.InvoiceApiHelper;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.invoice.api.InvoiceMigrationApi;
 import org.killbill.billing.invoice.api.InvoiceNotifier;
@@ -29,6 +30,7 @@ import org.killbill.billing.invoice.api.InvoicePaymentApi;
 import org.killbill.billing.invoice.api.InvoiceService;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
 import org.killbill.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory;
 import org.killbill.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
 import org.killbill.billing.invoice.api.migration.DefaultInvoiceMigrationApi;
 import org.killbill.billing.invoice.api.svcs.DefaultInvoiceInternalApi;
@@ -45,7 +47,6 @@ import org.killbill.billing.invoice.notification.NextBillingDatePoster;
 import org.killbill.billing.invoice.notification.NullInvoiceNotifier;
 import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
 import org.killbill.billing.invoice.template.bundles.DefaultResourceBundleFactory;
-import org.killbill.billing.invoice.api.formatters.ResourceBundleFactory;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.config.InvoiceConfig;
@@ -149,5 +150,7 @@ public class DefaultInvoiceModule extends KillBillModule implements InvoiceModul
         installInvoicePaymentApi();
         installInvoiceMigrationApi();
         installResourceBundleFactory();
+
+        bind(InvoiceApiHelper.class).asEagerSingleton();
     }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
index 197f811..5f9477d 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
@@ -64,7 +64,7 @@ public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB
     @Test(groups = "slow")
     public void testFullRefundWithBothInvoiceItemAdjustments() throws Exception {
         // Create an invoice with two items (30 \u20ac and 10 \u20ac)
-        final Invoice invoice = createAndPersistInvoice(invoiceDao, clock,
+        final Invoice invoice = createAndPersistInvoice(invoiceUtil, invoiceDao, clock,
                                                         ImmutableList.<BigDecimal>of(THIRTY, BigDecimal.TEN), CURRENCY, internalCallContext);
 
         // Fully adjust both items
@@ -78,7 +78,7 @@ public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB
     @Test(groups = "slow")
     public void testPartialRefundWithSingleInvoiceItemAdjustment() throws Exception {
         // Create an invoice with two items (30 \u20ac and 10 \u20ac)
-        final Invoice invoice = createAndPersistInvoice(invoiceDao, clock,
+        final Invoice invoice = createAndPersistInvoice(invoiceUtil, invoiceDao, clock,
                                                         ImmutableList.<BigDecimal>of(THIRTY, BigDecimal.TEN), CURRENCY, internalCallContext);
 
         // Fully adjust both items
@@ -91,7 +91,7 @@ public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB
     @Test(groups = "slow")
     public void testPartialRefundWithTwoInvoiceItemAdjustment() throws Exception {
         // Create an invoice with two items (30 \u20ac and 10 \u20ac)
-        final Invoice invoice = createAndPersistInvoice(invoiceDao, clock,
+        final Invoice invoice = createAndPersistInvoice(invoiceUtil, invoiceDao, clock,
                                                         ImmutableList.<BigDecimal>of(THIRTY, BigDecimal.TEN), CURRENCY, internalCallContext);
         // Adjust partially both items: the invoice posted was 40 \u20ac, but we should really just have charged you 2 \u20ac
         final ImmutableMap<UUID, BigDecimal> adjustments = ImmutableMap.<UUID, BigDecimal>of(invoice.getInvoiceItems().get(0).getId(), new BigDecimal("29"),
@@ -101,7 +101,7 @@ public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB
 
     private void verifyRefund(final BigDecimal invoiceAmount, final BigDecimal refundAmount, final BigDecimal finalInvoiceAmount,
                               final boolean adjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) throws InvoiceApiException {
-        final Invoice invoice = createAndPersistInvoice(invoiceDao, clock, invoiceAmount, CURRENCY, internalCallContext);
+        final Invoice invoice = createAndPersistInvoice(invoiceUtil, invoiceDao, clock, invoiceAmount, CURRENCY, internalCallContext);
         verifyRefund(invoice, invoiceAmount, refundAmount, finalInvoiceAmount, adjusted, invoiceItemIdsWithAmounts);
     }
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
index fe64af0..cf40438 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -25,8 +27,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import javax.annotation.Nullable;
-
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.killbill.billing.callcontext.InternalCallContext;
@@ -333,13 +333,6 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     }
 
     @Override
-    public InvoiceItemModelDao insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
-                                                           final LocalDate effectiveDate, @Nullable final BigDecimal amount,
-                                                           @Nullable final Currency currency, final InternalCallContext context) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public BigDecimal getAccountCBA(final UUID accountId, final InternalTenantContext context) {
         return null;
     }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
index 695c1c0..1070c2b 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
@@ -821,7 +821,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
         final String description = UUID.randomUUID().toString();
         final InvoiceModelDao invoiceForExternalCharge = new InvoiceModelDao(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
-        final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(null, accountId, bundleId, description, clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
+        final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(invoiceForExternalCharge.getId(), accountId, bundleId, description, clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
         invoiceForExternalCharge.addInvoiceItem(externalCharge);
         final InvoiceItemModelDao charge = invoiceDao.createInvoices(ImmutableList.<InvoiceModelDao>of(invoiceForExternalCharge), context).get(0);
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java
index 313ffac..c66af70 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -22,12 +24,15 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.mockito.Mockito;
-import org.testng.Assert;
-
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.entity.EntityPersistenceException;
+import org.killbill.billing.invoice.TestInvoiceHelper;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
@@ -35,10 +40,9 @@ import org.killbill.billing.invoice.dao.InvoiceDao;
 import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
 import org.killbill.billing.invoice.dao.InvoiceModelDao;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
-import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.clock.Clock;
-import org.killbill.billing.entity.EntityPersistenceException;
-import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.mockito.Mockito;
+import org.testng.Assert;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -47,28 +51,41 @@ public class InvoiceTestUtils {
 
     private InvoiceTestUtils() {}
 
-    public static Invoice createAndPersistInvoice(final InvoiceDao invoiceDao,
+    public static Invoice createAndPersistInvoice(final TestInvoiceHelper testInvoiceHelper,
+                                                  final InvoiceDao invoiceDao,
                                                   final Clock clock,
                                                   final BigDecimal amount,
                                                   final Currency currency,
                                                   final InternalCallContext internalCallContext) {
         try {
-            return createAndPersistInvoice(invoiceDao, clock, ImmutableList.<BigDecimal>of(amount),
-                                           currency, internalCallContext);
-        } catch (EntityPersistenceException e) {
+            return createAndPersistInvoice(testInvoiceHelper,
+                                           invoiceDao,
+                                           clock,
+                                           ImmutableList.<BigDecimal>of(amount),
+                                           currency,
+                                           internalCallContext);
+        } catch (final EntityPersistenceException e) {
             Assert.fail(e.getMessage());
             return null;
         }
     }
 
-    public static Invoice createAndPersistInvoice(final InvoiceDao invoiceDao,
+    public static Invoice createAndPersistInvoice(final TestInvoiceHelper testInvoiceHelper,
+                                                  final InvoiceDao invoiceDao,
                                                   final Clock clock,
-                                                  final List<BigDecimal> amounts,
+                                                  final Iterable<BigDecimal> amounts,
                                                   final Currency currency,
                                                   final InternalCallContext internalCallContext) throws EntityPersistenceException {
         final Invoice invoice = Mockito.mock(Invoice.class);
         final UUID invoiceId = UUID.randomUUID();
-        final UUID accountId = UUID.randomUUID();
+        final UUID accountId;
+        try {
+            final Account account = testInvoiceHelper.createAccount(internalCallContext.toCallContext(null));
+            accountId = account.getId();
+        } catch (final AccountApiException e) {
+            Assert.fail(e.getMessage());
+            return null;
+        }
 
         Mockito.when(invoice.getId()).thenReturn(invoiceId);
         Mockito.when(invoice.getAccountId()).thenReturn(accountId);