killbill-memoizeit

Changes

Details

diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 97f8824..8e000da 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -186,6 +186,10 @@ public enum ErrorCode {
     INVOICE_NOT_FOUND(4006, "No invoice could be found for id %s."),
     INVOICE_NOTHING_TO_DO(4007, "No invoice to generate for account %s and date %s"),
     INVOICE_NO_SUCH_CREDIT(4008, "Credit Item for id %s does not exist"),
+    CREDIT_AMOUNT_INVALID(4009, "Credit amount %s should be strictly positive"),
+    INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID(4010, "Invoice adjustment amount %s should be strictly positive"),
+    INVOICE_ITEM_NOT_FOUND(4011, "No invoice item could be found for id %s."),
+    INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT(4012, "Invoice item %s doesn't belong to invoice %s."),
 
     /*
      *
@@ -199,7 +203,6 @@ public enum ErrorCode {
     CHARGE_BACK_DOES_NOT_EXIST(4004, "Could not find chargeback for id %s."),
     INVOICE_PAYMENT_BY_ATTEMPT_NOT_FOUND(4905, "No invoice payment could be found for paymentAttempt id %s."),
     REFUND_AMOUNT_TOO_HIGH(4906, "Tried to refund %s of a %s payment."),
-    CREDIT_AMOUNT_INVALID(4907, "Credit amount %s should be striclty positive"),
 
     /*
      *
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
index 93afad8..548bfd5 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
@@ -17,10 +17,19 @@
 package com.ning.billing.invoice.api;
 
 public enum InvoiceItemType {
+    // Fixed (one-time) charge
     FIXED,
+    // Recurring charge
     RECURRING,
+    // Internal adjustment, used for repair
     REPAIR_ADJ,
+    // Internal adjustment, used as rollover credits
     CBA_ADJ,
+    // Credit adjustment, either at the account level (on its own invoice) or against an existing invoice
+    // (invoice level adjustment)
     CREDIT_ADJ,
+    // Invoice item adjustment
+    ITEM_ADJ,
+    // Refund adjustment (against a posted payment)
     REFUND_ADJ
 }
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 4a3b43c..74fa8e3 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
@@ -143,7 +143,7 @@ public interface InvoiceUserApi {
                                     Currency currency, CallContext context) throws InvoiceApiException;
 
     /**
-     * Add a credit to an invoice.
+     * Add a credit to an invoice. This can be used to adjust invoices.
      *
      * @param accountId     account id
      * @param invoiceId     invoice id
@@ -158,6 +158,35 @@ public interface InvoiceUserApi {
                                               Currency currency, CallContext context) throws InvoiceApiException;
 
     /**
+     * Adjust fully a given invoice item.
+     *
+     * @param accountId     account id
+     * @param invoiceId     invoice id
+     * @param invoiceItemId invoice item id
+     * @param effectiveDate the effective date for this adjustment invoice item (in the account timezone)
+     * @param context       the call context
+     * @return the adjustment invoice item
+     * @throws InvoiceApiException
+     */
+    public InvoiceItem insertInvoiceItemAdjustment(UUID accountId, UUID invoiceId, UUID invoiceItemId, LocalDate effectiveDate, CallContext context) throws InvoiceApiException;
+
+    /**
+     * Adjust partially a given invoice item.
+     *
+     * @param accountId     account id
+     * @param invoiceId     invoice id
+     * @param invoiceItemId invoice item id
+     * @param effectiveDate the effective date for this adjustment invoice item (in the account timezone)
+     * @param amount        the adjustment amount
+     * @param currency      adjustment currency
+     * @param context       the call context
+     * @return the adjustment invoice item
+     * @throws InvoiceApiException
+     */
+    public InvoiceItem insertInvoiceItemAdjustment(UUID accountId, UUID invoiceId, UUID invoiceItemId, LocalDate effectiveDate,
+                                                   BigDecimal amount, Currency currency, CallContext context) throws InvoiceApiException;
+
+    /**
      * Retrieve the invoice formatted in HTML.
      *
      * @param invoiceId invoice id
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index ea8e17a..927db0b 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
@@ -22,6 +22,8 @@ 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;
 
@@ -156,6 +158,21 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
+    public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final LocalDate effectiveDate, final CallContext context) throws InvoiceApiException {
+        return insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, null, null, context);
+    }
+
+    @Override
+    public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
+                                                   final LocalDate effectiveDate, @Nullable final BigDecimal amount,
+                                                   @Nullable final Currency currency, final CallContext context) throws InvoiceApiException {
+        if (amount != null && amount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID, amount);
+        }
+        return dao.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, amount, currency, context);
+    }
+
+    @Override
     public String getInvoiceAsHTML(final UUID invoiceId) throws AccountApiException, IOException, InvoiceApiException {
         final Invoice invoice = getInvoice(invoiceId);
         if (invoice == null) {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index 4391451..b3c81c9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -43,6 +43,7 @@ import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
 import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.invoice.model.RefundAdjInvoiceItem;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
@@ -56,6 +57,7 @@ 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.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
@@ -446,40 +448,114 @@ public class DefaultInvoiceDao implements InvoiceDao {
         return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
             @Override
             public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
-                UUID invoiceIdForRefund = invoiceId;
-                if (invoiceIdForRefund == null) {
-                    final Invoice invoiceForRefund = new DefaultInvoice(accountId, effectiveDate, effectiveDate, currency);
-                    transactional.create(invoiceForRefund, context);
-                    invoiceIdForRefund = invoiceForRefund.getId();
+                UUID invoiceIdForCredit = invoiceId;
+                // Create an invoice for that credit if it doesn't exist
+                if (invoiceIdForCredit == null) {
+                    final Invoice invoiceForCredit = new DefaultInvoice(accountId, effectiveDate, effectiveDate, currency);
+                    transactional.create(invoiceForCredit, context);
+                    invoiceIdForCredit = invoiceForCredit.getId();
                 }
 
-                final InvoiceItem credit = new CreditAdjInvoiceItem(invoiceIdForRefund, accountId, effectiveDate, positiveCreditAmount.negate(), currency);
-                final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
-                transInvoiceItemDao.create(credit, context);
-
-                final Invoice invoice = transactional.getById(invoiceIdForRefund.toString());
-                if (invoice != null) {
-                    populateChildren(invoice, transactional);
-                } else {
-                    throw new IllegalStateException("Invoice shouldn't be null for credit at this stage " + invoiceIdForRefund);
-                }
-                // If invoice balance becomes negative we add some CBA item
-                if (invoice.getBalance().compareTo(BigDecimal.ZERO) < 0) {
-                    final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
-                                                                                   invoice.getBalance().negate(), invoice.getCurrency());
-                    transInvoiceItemDao.create(cbaAdjItem, context);
-
-                }
+                // Note! The amount is negated here!
+                final InvoiceItem credit = new CreditAdjInvoiceItem(invoiceIdForCredit, accountId, effectiveDate, positiveCreditAmount.negate(), currency);
+                insertItemAndAddCBAIfNeeded(transactional, credit, context);
                 return credit;
             }
         });
     }
 
     @Override
+    public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
+                                                   final LocalDate effectiveDate, @Nullable final BigDecimal positiveAdjAmount,
+                                                   @Nullable final Currency currency, final CallContext context) {
+        return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
+            @Override
+            public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+                final InvoiceItem invoiceItemAdjustment = createAdjustmentItem(transactional, invoiceId, invoiceItemId, positiveAdjAmount,
+                                                                               currency, effectiveDate);
+                insertItemAndAddCBAIfNeeded(transactional, invoiceItemAdjustment, context);
+                return invoiceItemAdjustment;
+            }
+        });
+    }
+
+    @Override
     public void test() {
         invoiceSqlDao.test();
     }
 
+    /**
+     * Create an adjustment for a given invoice item. This just creates the object in memory, it doesn't write it to disk.
+     *
+     * @param invoiceId         the invoice id
+     * @param invoiceItemId     the invoice item id to adjust
+     * @param effectiveDate     adjustment effective date, in the account timezone
+     * @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
+     * @return the adjustment item
+     */
+    private InvoiceItem createAdjustmentItem(final InvoiceSqlDao transactional, final UUID invoiceId, final UUID invoiceItemId,
+                                             final BigDecimal positiveAdjAmount, final Currency currency, final LocalDate effectiveDate) throws InvoiceApiException {// First, retrieve the invoice item in question
+        final InvoiceItemSqlDao invoiceItemSqlDao = transactional.become(InvoiceItemSqlDao.class);
+        final InvoiceItem invoiceItemToBeAdjusted = invoiceItemSqlDao.getById(invoiceItemId.toString());
+        if (invoiceItemToBeAdjusted == null) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
+        }
+
+        // Validate the invoice it belongs to
+        if (!invoiceItemToBeAdjusted.getInvoiceId().equals(invoiceId)) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT, invoiceItemId, invoiceId);
+        }
+
+        // Retrieve the amount and currency if needed
+        final BigDecimal amountToRefund = Objects.firstNonNull(positiveAdjAmount, invoiceItemToBeAdjusted.getAmount());
+        // TODO - should we enforce the currency (and respect the original one) here if the amount passed was null?
+        final Currency currencyForAdjustment = Objects.firstNonNull(currency, invoiceItemToBeAdjusted.getCurrency());
+
+        // Finally, create the adjustment
+        // Note! The amount is negated here!
+        return new ItemAdjInvoiceItem(invoiceItemToBeAdjusted, effectiveDate, amountToRefund.negate(), currencyForAdjustment);
+    }
+
+    /**
+     * Create an invoice item and adjust the invoice with a CBA item if the new invoice balance is negative.
+     *
+     * @param transactional the InvoiceSqlDao
+     * @param item          the invoice item to create
+     * @param context       the call context
+     */
+    private void insertItemAndAddCBAIfNeeded(final InvoiceSqlDao transactional, final InvoiceItem item, final CallContext context) {
+        final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
+        transInvoiceItemDao.create(item, context);
+
+        addCBAIfNeeded(transactional, item.getInvoiceId(), context);
+    }
+
+    /**
+     * Adjust the invoice with a CBA item if the new invoice balance is negative.
+     *
+     * @param transactional the InvoiceSqlDao
+     * @param invoiceId     the invoice id to adjust
+     * @param context       the call context
+     */
+    private void addCBAIfNeeded(final InvoiceSqlDao transactional, final UUID invoiceId, final CallContext context) {
+        final Invoice invoice = transactional.getById(invoiceId.toString());
+        if (invoice != null) {
+            populateChildren(invoice, transactional);
+        } else {
+            throw new IllegalStateException("Invoice shouldn't be null for this item at this stage " + invoiceId);
+        }
+
+        // If invoice balance becomes negative we add some CBA item
+        if (invoice.getBalance().compareTo(BigDecimal.ZERO) < 0) {
+            final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
+            final InvoiceItem cbaAdjItem = new CreditBalanceAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(),
+                                                                           invoice.getBalance().negate(), invoice.getCurrency());
+            transInvoiceItemDao.create(cbaAdjItem, context);
+
+        }
+    }
+
     private BigDecimal getAccountCBAFromTransaction(final UUID accountId, final InvoiceSqlDao transactional) {
         BigDecimal cba = BigDecimal.ZERO;
         final List<Invoice> invoices = getAllInvoicesByAccountFromTransaction(accountId, transactional);
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 888ccb7..7c93aba 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
@@ -24,7 +24,6 @@ import javax.annotation.Nullable;
 
 import org.joda.time.LocalDate;
 
-import com.ning.billing.account.api.BillCycleDay;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceApiException;
@@ -88,4 +87,18 @@ public interface InvoiceDao {
     InvoiceItem insertCredit(final UUID accountId, final UUID invoiceId, final BigDecimal amount,
                              final LocalDate effectiveDate, final Currency currency, final CallContext context);
 
+    /**
+     * 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 context
+     * @return the newly created adjustment item
+     */
+    InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final LocalDate effectiveDate,
+                                            @Nullable final BigDecimal amount, @Nullable final Currency currency, final CallContext context);
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index e7ddfb8..8156f30 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -47,6 +47,7 @@ import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
 import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.invoice.model.RefundAdjInvoiceItem;
 import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
@@ -149,8 +150,11 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
                 case REPAIR_ADJ:
                     item = new RepairAdjInvoiceItem(id, invoiceId, accountId, startDate, endDate, amount, currency, linkedItemId);
                     break;
+                case ITEM_ADJ:
+                    item = new ItemAdjInvoiceItem(id, invoiceId, accountId, startDate, amount, currency, linkedItemId);
+                    break;
                 default:
-                    throw new RuntimeException("Unexpected type of event item " + item);
+                    throw new RuntimeException("Unexpected type of event item " + type);
             }
             return item;
         }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
index 3b495a4..616e935 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
@@ -41,7 +41,7 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
     }
 
     public BigDecimal getTotalAdjAmount() {
-        return getAmoutForItems(InvoiceItemType.CREDIT_ADJ, InvoiceItemType.REFUND_ADJ);
+        return getAmoutForItems(InvoiceItemType.CREDIT_ADJ, InvoiceItemType.REFUND_ADJ, InvoiceItemType.ITEM_ADJ);
     }
 
     public BigDecimal getCreditAdjAmount() {
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
new file mode 100644
index 0000000..cbfe229
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ItemAdjInvoiceItem.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+
+public class ItemAdjInvoiceItem extends AdjInvoiceItem {
+
+    public ItemAdjInvoiceItem(final InvoiceItem invoiceItem, final LocalDate effectiveDate,
+                              final BigDecimal amount, final Currency currency) {
+        super(invoiceItem.getInvoiceId(), invoiceItem.getAccountId(), effectiveDate, effectiveDate,
+              amount, currency, invoiceItem.getId());
+    }
+
+    public ItemAdjInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, final LocalDate startDate,
+                              final BigDecimal amount, final Currency currency, final UUID linkedItemId) {
+        super(id, invoiceId, accountId, startDate, startDate, amount, currency, linkedItemId);
+    }
+
+    @Override
+    public InvoiceItemType getInvoiceItemType() {
+        return InvoiceItemType.ITEM_ADJ;
+    }
+
+    @Override
+    public String getDescription() {
+        return "item-adj";
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/migration/InvoiceApiTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/api/migration/InvoiceApiTestBase.java
new file mode 100644
index 0000000..4d243c1
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/migration/InvoiceApiTestBase.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api.migration;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Guice;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.catalog.MockPlan;
+import com.ning.billing.catalog.MockPlanPhase;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
+import com.ning.billing.entitlement.api.billing.BillingModeType;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.invoice.InvoiceDispatcher;
+import com.ning.billing.invoice.MockBillingEventSet;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceMigrationApi;
+import com.ning.billing.invoice.api.InvoiceNotifier;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.generator.InvoiceGenerator;
+import com.ning.billing.invoice.notification.NullInvoiceNotifier;
+import com.ning.billing.invoice.tests.InvoicingTestBase;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.junction.api.BillingEventSet;
+import com.ning.billing.mock.api.MockBillCycleDay;
+import com.ning.billing.util.bus.BusService;
+import com.ning.billing.util.bus.DefaultBusService;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+
+import com.google.inject.Inject;
+
+@Guice(modules = {MockModuleNoEntitlement.class})
+public abstract class InvoiceApiTestBase extends InvoicingTestBase {
+
+    protected static final Currency accountCurrency = Currency.USD;
+
+    @Inject
+    protected InvoiceUserApi invoiceUserApi;
+
+    @Inject
+    protected InvoicePaymentApi invoicePaymentApi;
+
+    @Inject
+    protected InvoiceMigrationApi migrationApi;
+
+    @Inject
+    protected InvoiceGenerator generator;
+
+    @Inject
+    protected BillingApi billingApi;
+
+    @Inject
+    protected AccountUserApi accountUserApi;
+
+    @Inject
+    protected BusService busService;
+
+    @Inject
+    protected InvoiceDao invoiceDao;
+
+    @Inject
+    protected GlobalLocker locker;
+
+    @Inject
+    protected Clock clock;
+
+    @BeforeSuite(groups = "slow")
+    public void setup() throws Exception {
+        busService.getBus().start();
+    }
+
+    @AfterSuite(groups = "slow")
+    public void tearDown() {
+        try {
+            ((DefaultBusService) busService).stopBus();
+        } catch (Exception e) {
+            log.warn("Failed to tearDown test properly ", e);
+        }
+    }
+
+    protected UUID generateRegularInvoice(final Account account, final DateTime targetDate) throws Exception {
+        final Subscription subscription = Mockito.mock(Subscription.class);
+        Mockito.when(subscription.getId()).thenReturn(UUID.randomUUID());
+        Mockito.when(subscription.getBundleId()).thenReturn(new UUID(0L, 0L));
+        final BillingEventSet events = new MockBillingEventSet();
+        final Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
+        final PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
+        final DateTime effectiveDate = new DateTime().minusDays(1);
+        final Currency currency = Currency.USD;
+        final BigDecimal fixedPrice = null;
+        events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
+                                          fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
+                                          BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
+
+        Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(account.getId())).thenReturn(events);
+
+        final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
+        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, billingApi,
+                                                                   invoiceDao, invoiceNotifier, locker, busService.getBus(), clock);
+
+        final CallContext context = new DefaultCallContextFactory(clock).createCallContext("Unit test", CallOrigin.TEST, UserType.TEST);
+        Invoice invoice = dispatcher.processAccount(account.getId(), targetDate, true, context);
+        Assert.assertNotNull(invoice);
+
+        List<Invoice> invoices = invoiceDao.getInvoicesByAccount(account.getId());
+        Assert.assertEquals(invoices.size(), 0);
+
+        invoice = dispatcher.processAccount(account.getId(), targetDate, false, context);
+        Assert.assertNotNull(invoice);
+
+        invoices = invoiceDao.getInvoicesByAccount(account.getId());
+        Assert.assertEquals(invoices.size(), 1);
+
+        return invoice.getId();
+    }
+
+    protected Account createAccount() throws AccountApiException {
+        final UUID accountId = UUID.randomUUID();
+        final Account account = Mockito.mock(Account.class);
+        Mockito.when(accountUserApi.getAccountById(accountId)).thenReturn(account);
+        Mockito.when(account.getCurrency()).thenReturn(accountCurrency);
+        Mockito.when(account.getId()).thenReturn(accountId);
+        Mockito.when(account.isNotifiedForInvoices()).thenReturn(true);
+        Mockito.when(account.getBillCycleDay()).thenReturn(new MockBillCycleDay(31));
+
+        return account;
+    }
+}
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 c8c3e71..007f2f6 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
@@ -23,130 +23,47 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-import org.mockito.Mockito;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
-import org.testng.annotations.AfterSuite;
 import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeSuite;
-import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
-import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.catalog.MockPlan;
-import com.ning.billing.catalog.MockPlanPhase;
-import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.catalog.api.Plan;
-import com.ning.billing.catalog.api.PlanPhase;
-import com.ning.billing.entitlement.api.SubscriptionTransitionType;
-import com.ning.billing.entitlement.api.billing.BillingModeType;
-import com.ning.billing.entitlement.api.user.Subscription;
-import com.ning.billing.invoice.InvoiceDispatcher;
-import com.ning.billing.invoice.MockBillingEventSet;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceMigrationApi;
-import com.ning.billing.invoice.api.InvoiceNotifier;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.invoice.api.InvoiceUserApi;
-import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.invoice.generator.InvoiceGenerator;
-import com.ning.billing.invoice.notification.NullInvoiceNotifier;
-import com.ning.billing.invoice.tests.InvoicingTestBase;
-import com.ning.billing.junction.api.BillingApi;
-import com.ning.billing.junction.api.BillingEventSet;
-import com.ning.billing.mock.api.MockBillCycleDay;
-import com.ning.billing.util.bus.BusService;
-import com.ning.billing.util.bus.DefaultBusService;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.util.callcontext.DefaultCallContextFactory;
-import com.ning.billing.util.callcontext.UserType;
-import com.ning.billing.util.clock.Clock;
-import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.globallocker.GlobalLocker;
-
-@Guice(modules = {MockModuleNoEntitlement.class})
-public class TestDefaultInvoiceMigrationApi extends InvoicingTestBase {
-    private final Logger log = LoggerFactory.getLogger(TestDefaultInvoiceMigrationApi.class);
-
-    @Inject
-    InvoiceUserApi invoiceUserApi;
-
-    @Inject
-    InvoicePaymentApi invoicePaymentApi;
-
-    @Inject
-    private InvoiceGenerator generator;
-
-    @Inject
-    private InvoiceDao invoiceDao;
-
-    @Inject
-    private GlobalLocker locker;
 
-    @Inject
-    private BusService busService;
-
-    @Inject
-    private InvoiceMigrationApi migrationApi;
+import com.google.inject.Inject;
 
-    @Inject
-    private BillingApi billingApi;
+public class TestDefaultInvoiceMigrationApi extends InvoiceApiTestBase {
 
-    @Inject
-    private AccountUserApi accountUserApi;
+    private final Logger log = LoggerFactory.getLogger(TestDefaultInvoiceMigrationApi.class);
 
-    private Account account;
-    private UUID accountId;
-    private UUID subscriptionId;
     private LocalDate date_migrated;
     private DateTime date_regular;
 
+    private UUID accountId;
     private UUID migrationInvoiceId;
     private UUID regularInvoiceId;
 
     private static final BigDecimal MIGRATION_INVOICE_AMOUNT = new BigDecimal("100.00");
     private static final Currency MIGRATION_INVOICE_CURRENCY = Currency.USD;
 
-    private final Clock clock = new ClockMock();
-
-    @BeforeSuite(groups = "slow")
-    public void setup() throws Exception {
-        busService.getBus().start();
-    }
-
     @BeforeMethod(groups = "slow")
     public void setupMethod() throws Exception {
-        accountId = UUID.randomUUID();
-        subscriptionId = UUID.randomUUID();
         date_migrated = clock.getUTCToday().minusYears(1);
         date_regular = clock.getUTCNow();
 
-        account = Mockito.mock(Account.class);
-        Mockito.when(accountUserApi.getAccountById(accountId)).thenReturn(account);
-        Mockito.when(account.getCurrency()).thenReturn(Currency.USD);
-        Mockito.when(account.getId()).thenReturn(accountId);
-        Mockito.when(account.isNotifiedForInvoices()).thenReturn(true);
-        Mockito.when(account.getBillCycleDay()).thenReturn(new MockBillCycleDay(31));
-
-        migrationInvoiceId = createAndCheckMigrationInvoice();
-        regularInvoiceId = generateRegularInvoice();
+        final Account account = createAccount();
+        accountId = account.getId();
+        migrationInvoiceId = createAndCheckMigrationInvoice(accountId);
+        regularInvoiceId = generateRegularInvoice(account, date_regular);
     }
 
-    @AfterSuite(groups = "slow")
-    public void tearDown() {
-        try {
-            ((DefaultBusService) busService).stopBus();
-        } catch (Exception e) {
-            log.warn("Failed to tearDown test properly ", e);
-        }
-    }
-
-    private UUID createAndCheckMigrationInvoice() {
+    private UUID createAndCheckMigrationInvoice(final UUID accountId) {
         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
@@ -166,42 +83,6 @@ public class TestDefaultInvoiceMigrationApi extends InvoicingTestBase {
         return migrationInvoiceId;
     }
 
-    private UUID generateRegularInvoice() throws Exception {
-        final Subscription subscription = Mockito.mock(Subscription.class);
-        Mockito.when(subscription.getId()).thenReturn(subscriptionId);
-        Mockito.when(subscription.getBundleId()).thenReturn(new UUID(0L, 0L));
-        final BillingEventSet events = new MockBillingEventSet();
-        final Plan plan = MockPlan.createBicycleNoTrialEvergreen1USD();
-        final PlanPhase planPhase = MockPlanPhase.create1USDMonthlyEvergreen();
-        final DateTime effectiveDate = new DateTime().minusDays(1);
-        final Currency currency = Currency.USD;
-        final BigDecimal fixedPrice = null;
-        events.add(createMockBillingEvent(account, subscription, effectiveDate, plan, planPhase,
-                                          fixedPrice, BigDecimal.ONE, currency, BillingPeriod.MONTHLY, 1,
-                                          BillingModeType.IN_ADVANCE, "", 1L, SubscriptionTransitionType.CREATE));
-
-        Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(accountId)).thenReturn(events);
-
-        final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
-        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountUserApi, billingApi,
-                                                                   invoiceDao, invoiceNotifier, locker, busService.getBus(), clock);
-
-        final CallContext context = new DefaultCallContextFactory(clock).createCallContext("Migration test", CallOrigin.TEST, UserType.TEST);
-        Invoice invoice = dispatcher.processAccount(accountId, date_regular, true, context);
-        Assert.assertNotNull(invoice);
-
-        List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
-        Assert.assertEquals(invoices.size(), 0);
-
-        invoice = dispatcher.processAccount(accountId, date_regular, false, context);
-        Assert.assertNotNull(invoice);
-
-        invoices = invoiceDao.getInvoicesByAccount(accountId);
-        Assert.assertEquals(invoices.size(), 1);
-
-        return invoice.getId();
-    }
-
     @Test(groups = "slow")
     public void testUserApiAccess() {
         final List<Invoice> byAccount = invoiceUserApi.getInvoicesByAccount(accountId);
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
new file mode 100644
index 0000000..2de15d7
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.api.user;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+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.migration.InvoiceApiTestBase;
+import com.ning.billing.invoice.model.InvoicingConfiguration;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContextFactory;
+import com.ning.billing.util.callcontext.UserType;
+
+public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
+
+    private UUID accountId;
+    private UUID invoiceId;
+    private CallContext context;
+
+    @BeforeMethod(groups = "slow")
+    public void setupMethod() throws Exception {
+        final Account account = createAccount();
+        accountId = account.getId();
+        invoiceId = generateRegularInvoice(account, clock.getUTCNow());
+        context = new DefaultCallContextFactory(clock).createCallContext("Unit test", CallOrigin.TEST, UserType.TEST);
+    }
+
+    @Test(groups = "slow")
+    public void testAdjustFullInvoice() throws Exception {
+        // Verify the initial invoice balance
+        final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+        // Verify the initial account balance
+        final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(accountBalance, invoiceBalance);
+
+        // Adjust the invoice for the full amount
+        final InvoiceItem creditInvoiceItem = invoiceUserApi.insertCreditForInvoice(accountId, invoiceId, invoiceBalance,
+                                                                                    clock.getUTCToday(), accountCurrency, context);
+        Assert.assertEquals(creditInvoiceItem.getInvoiceId(), invoiceId);
+        Assert.assertEquals(creditInvoiceItem.getInvoiceItemType(), InvoiceItemType.CREDIT_ADJ);
+        Assert.assertEquals(creditInvoiceItem.getAccountId(), accountId);
+        Assert.assertEquals(creditInvoiceItem.getAmount(), invoiceBalance.negate());
+        Assert.assertEquals(creditInvoiceItem.getCurrency(), accountCurrency);
+        Assert.assertNull(creditInvoiceItem.getLinkedItemId());
+
+        // Verify the adjusted invoice balance
+        final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        Assert.assertEquals(adjustedInvoiceBalance.compareTo(BigDecimal.ZERO), 0);
+
+        // Verify the adjusted account balance
+        final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+    }
+
+    @Test(groups = "slow")
+    public void testAdjustPartialInvoice() throws Exception {
+        // Verify the initial invoice balance
+        final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+        // Verify the initial account balance
+        final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(accountBalance, invoiceBalance);
+
+        // Adjust the invoice for a fraction of the balance
+        final BigDecimal creditAmount = invoiceBalance.divide(BigDecimal.TEN);
+        final InvoiceItem creditInvoiceItem = invoiceUserApi.insertCreditForInvoice(accountId, invoiceId, creditAmount,
+                                                                                    clock.getUTCToday(), accountCurrency, context);
+        Assert.assertEquals(creditInvoiceItem.getInvoiceId(), invoiceId);
+        Assert.assertEquals(creditInvoiceItem.getInvoiceItemType(), InvoiceItemType.CREDIT_ADJ);
+        Assert.assertEquals(creditInvoiceItem.getAccountId(), accountId);
+        Assert.assertEquals(creditInvoiceItem.getAmount(), creditAmount.negate());
+        Assert.assertEquals(creditInvoiceItem.getCurrency(), accountCurrency);
+        Assert.assertNull(creditInvoiceItem.getLinkedItemId());
+
+        // Verify the adjusted invoice balance
+        final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        // Note! The invoice code will round (see InvoiceItemList)
+        Assert.assertEquals(adjustedInvoiceBalance, invoiceBalance.add(creditAmount.negate()).setScale(InvoicingConfiguration.getNumberOfDecimals(),
+                                                                                                       InvoicingConfiguration.getRoundingMode()));
+
+        // Verify the adjusted account balance
+        final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+    }
+
+    @Test(groups = "slow")
+    public void testCantAdjustInvoiceWithNegativeAmount() throws Exception {
+        try {
+            invoiceUserApi.insertCreditForInvoice(accountId, invoiceId, BigDecimal.TEN.negate(), clock.getUTCToday(), accountCurrency, context);
+            Assert.fail("Should not have been able to adjust an invoice with a negative amount");
+        } catch (InvoiceApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.CREDIT_AMOUNT_INVALID.getCode());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testAdjustFullInvoiceItem() throws Exception {
+        final InvoiceItem invoiceItem = invoiceUserApi.getInvoice(invoiceId).getInvoiceItems().get(0);
+        // Verify we picked a non zero item
+        Assert.assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
+
+        // Verify the initial invoice balance
+        final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+        // Verify the initial account balance
+        final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(accountBalance, invoiceBalance);
+
+        // Adjust the invoice for the full amount
+        final InvoiceItem adjInvoiceItem = invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(),
+                                                                                      clock.getUTCToday(), context);
+        Assert.assertEquals(adjInvoiceItem.getInvoiceId(), invoiceId);
+        Assert.assertEquals(adjInvoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
+        Assert.assertEquals(adjInvoiceItem.getAccountId(), accountId);
+        Assert.assertEquals(adjInvoiceItem.getAmount(), invoiceItem.getAmount().negate());
+        Assert.assertEquals(adjInvoiceItem.getCurrency(), accountCurrency);
+        Assert.assertEquals(adjInvoiceItem.getLinkedItemId(), invoiceItem.getId());
+
+        // Verify the adjusted invoice balance
+        final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        // Note! The invoice code will round (see InvoiceItemList)
+        Assert.assertEquals(adjustedInvoiceBalance, invoiceBalance.add(invoiceItem.getAmount().negate()).setScale(InvoicingConfiguration.getNumberOfDecimals(),
+                                                                                                                  InvoicingConfiguration.getRoundingMode()));
+
+        // Verify the adjusted account balance
+        final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+    }
+
+    @Test(groups = "slow")
+    public void testAdjustPartialInvoiceItem() throws Exception {
+        final InvoiceItem invoiceItem = invoiceUserApi.getInvoice(invoiceId).getInvoiceItems().get(0);
+        // Verify we picked a non zero item
+        Assert.assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
+
+        // Verify the initial invoice balance
+        final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+        // Verify the initial account balance
+        final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(accountBalance, invoiceBalance);
+
+        // Adjust the invoice for a fraction of the balance
+        final BigDecimal adjAmount = invoiceItem.getAmount().divide(BigDecimal.TEN);
+        final InvoiceItem adjInvoiceItem = invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(),
+                                                                                      clock.getUTCToday(), adjAmount, accountCurrency,
+                                                                                      context);
+        Assert.assertEquals(adjInvoiceItem.getInvoiceId(), invoiceId);
+        Assert.assertEquals(adjInvoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
+        Assert.assertEquals(adjInvoiceItem.getAccountId(), accountId);
+        Assert.assertEquals(adjInvoiceItem.getAmount(), adjAmount.negate());
+        Assert.assertEquals(adjInvoiceItem.getCurrency(), accountCurrency);
+        Assert.assertEquals(adjInvoiceItem.getLinkedItemId(), invoiceItem.getId());
+
+        // Verify the adjusted invoice balance
+        final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        // Note! The invoice code will round (see InvoiceItemList)
+        Assert.assertEquals(adjustedInvoiceBalance, invoiceBalance.add(adjAmount.negate()).setScale(InvoicingConfiguration.getNumberOfDecimals(),
+                                                                                                    InvoicingConfiguration.getRoundingMode()));
+
+        // Verify the adjusted account balance
+        final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+    }
+
+    @Test(groups = "slow")
+    public void testCantAdjustInvoiceItemWithNegativeAmount() throws Exception {
+        final InvoiceItem invoiceItem = invoiceUserApi.getInvoice(invoiceId).getInvoiceItems().get(0);
+
+        try {
+            invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(), clock.getUTCToday(),
+                                                       BigDecimal.TEN.negate(), accountCurrency, context);
+            Assert.fail("Should not have been able to adjust an item with a negative amount");
+        } catch (InvoiceApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID.getCode());
+        }
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index 2e5e4ce..1a26152 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -23,6 +23,8 @@ 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;
 
@@ -269,6 +271,12 @@ public class MockInvoiceDao implements InvoiceDao {
     }
 
     @Override
+    public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
+                                                   final LocalDate effectiveDate, @Nullable final BigDecimal amount, @Nullable final Currency currency, final CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public BigDecimal getAccountCBA(UUID accountId) {
         return null;
     }
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
new file mode 100644
index 0000000..5e82f30
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.dao;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ErrorCode;
+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.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.util.callcontext.CallContext;
+
+public class TestInvoiceDaoForItemAdjustment extends InvoiceDaoTestBase {
+
+    private static final BigDecimal INVOICE_ITEM_AMOUNT = new BigDecimal("21.00");
+
+    @Test(groups = "slow")
+    public void testAddInvoiceItemAdjustmentForNonExistingInvoiceItemId() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+        final UUID invoiceId = UUID.randomUUID();
+        final UUID invoiceItemId = UUID.randomUUID();
+        final LocalDate effectiveDate = new LocalDate();
+        final CallContext context = Mockito.mock(CallContext.class);
+
+        try {
+            invoiceDao.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, null, null, context);
+            Assert.fail("Should not have been able to adjust a non existing invoice item");
+        } catch (Exception e) {
+            Assert.assertEquals(((InvoiceApiException) e.getCause()).getCode(), ErrorCode.INVOICE_ITEM_NOT_FOUND.getCode());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testAddInvoiceItemAdjustmentForWrongInvoice() throws Exception {
+        final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoice.getId(), invoice.getAccountId(), UUID.randomUUID(),
+                                                                 UUID.randomUUID(), "test plan", "test phase",
+                                                                 new LocalDate(2010, 1, 1), new LocalDate(2010, 4, 1),
+                                                                 INVOICE_ITEM_AMOUNT, new BigDecimal("7.00"), Currency.USD);
+        invoice.addInvoiceItem(invoiceItem);
+        invoiceDao.create(invoice, 1, context);
+
+        try {
+            invoiceDao.insertInvoiceItemAdjustment(invoice.getAccountId(), UUID.randomUUID(), invoiceItem.getId(), new LocalDate(2010, 1, 1), null, null, context);
+            Assert.fail("Should not have been able to adjust an item on a non existing invoice");
+        } catch (Exception e) {
+            Assert.assertEquals(((InvoiceApiException) e.getCause()).getCode(), ErrorCode.INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT.getCode());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testAddInvoiceItemAdjustmentForFullAmount() throws Exception {
+        final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoice.getId(), invoice.getAccountId(), UUID.randomUUID(),
+                                                                 UUID.randomUUID(), "test plan", "test phase",
+                                                                 new LocalDate(2010, 1, 1), new LocalDate(2010, 4, 1),
+                                                                 INVOICE_ITEM_AMOUNT, new BigDecimal("7.00"), Currency.USD);
+        invoice.addInvoiceItem(invoiceItem);
+        invoiceDao.create(invoice, 1, context);
+
+        final InvoiceItem adjustedInvoiceItem = createAndCheckAdjustment(invoice, invoiceItem, null);
+        Assert.assertEquals(adjustedInvoiceItem.getAmount().compareTo(invoiceItem.getAmount().negate()), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testAddInvoiceItemAdjustmentForPartialAmount() throws Exception {
+        final Invoice invoice = new DefaultInvoice(UUID.randomUUID(), clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
+        final InvoiceItem invoiceItem = new RecurringInvoiceItem(invoice.getId(), invoice.getAccountId(), UUID.randomUUID(),
+                                                                 UUID.randomUUID(), "test plan", "test phase",
+                                                                 new LocalDate(2010, 1, 1), new LocalDate(2010, 4, 1),
+                                                                 INVOICE_ITEM_AMOUNT, new BigDecimal("7.00"), Currency.USD);
+        invoice.addInvoiceItem(invoiceItem);
+        invoiceDao.create(invoice, 1, context);
+
+        final InvoiceItem adjustedInvoiceItem = createAndCheckAdjustment(invoice, invoiceItem, BigDecimal.TEN);
+        Assert.assertEquals(adjustedInvoiceItem.getAmount().compareTo(BigDecimal.TEN.negate()), 0);
+    }
+
+    private InvoiceItem createAndCheckAdjustment(final Invoice invoice, final InvoiceItem invoiceItem, final BigDecimal amount) {
+        final LocalDate effectiveDate = new LocalDate(2010, 1, 1);
+        final InvoiceItem adjustedInvoiceItem = invoiceDao.insertInvoiceItemAdjustment(invoice.getAccountId(), invoice.getId(), invoiceItem.getId(),
+                                                                                       effectiveDate, amount, null, context);
+        Assert.assertEquals(adjustedInvoiceItem.getAccountId(), invoiceItem.getAccountId());
+        Assert.assertNull(adjustedInvoiceItem.getBundleId());
+        Assert.assertEquals(adjustedInvoiceItem.getCurrency(), invoiceItem.getCurrency());
+        Assert.assertEquals(adjustedInvoiceItem.getDescription(), "item-adj");
+        Assert.assertEquals(adjustedInvoiceItem.getEndDate(), effectiveDate);
+        Assert.assertEquals(adjustedInvoiceItem.getInvoiceId(), invoiceItem.getInvoiceId());
+        Assert.assertEquals(adjustedInvoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
+        Assert.assertEquals(adjustedInvoiceItem.getLinkedItemId(), invoiceItem.getId());
+        Assert.assertNull(adjustedInvoiceItem.getPhaseName());
+        Assert.assertNull(adjustedInvoiceItem.getPlanName());
+        Assert.assertNull(adjustedInvoiceItem.getRate());
+        Assert.assertEquals(adjustedInvoiceItem.getStartDate(), effectiveDate);
+        Assert.assertNull(adjustedInvoiceItem.getSubscriptionId());
+
+        // Retrieve the item by id
+        final InvoiceItem retrievedInvoiceItem = invoiceItemSqlDao.getById(adjustedInvoiceItem.getId().toString());
+        Assert.assertEquals(retrievedInvoiceItem, adjustedInvoiceItem);
+
+        // Retrieve the item by invoice id
+        final Invoice retrievedInvoice = invoiceDao.getById(adjustedInvoiceItem.getInvoiceId());
+        final List<InvoiceItem> invoiceItems = retrievedInvoice.getInvoiceItems();
+        Assert.assertEquals(invoiceItems.size(), 2);
+        final InvoiceItem retrievedByInvoiceInvoiceItem;
+        if (invoiceItems.get(0).getId().equals(adjustedInvoiceItem.getId())) {
+            retrievedByInvoiceInvoiceItem = invoiceItems.get(0);
+        } else {
+            retrievedByInvoiceInvoiceItem = invoiceItems.get(1);
+        }
+        Assert.assertEquals(retrievedByInvoiceInvoiceItem, adjustedInvoiceItem);
+
+        // Verify the invoice balance
+        if (amount == null) {
+            Assert.assertEquals(retrievedInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+        } else {
+            Assert.assertEquals(retrievedInvoice.getBalance().compareTo(INVOICE_ITEM_AMOUNT.add(amount.negate())), 0);
+        }
+
+        return adjustedInvoiceItem;
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/model/TestItemAdjInvoiceItem.java b/invoice/src/test/java/com/ning/billing/invoice/model/TestItemAdjInvoiceItem.java
new file mode 100644
index 0000000..316b613
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/model/TestItemAdjInvoiceItem.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+
+public class TestItemAdjInvoiceItem {
+
+    @Test(groups = "fast")
+    public void testType() throws Exception {
+        final InvoiceItem invoiceItem = new ItemAdjInvoiceItem(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(),
+                                                               new LocalDate(2010, 1, 1), new BigDecimal("7.00"), Currency.USD,
+                                                               UUID.randomUUID());
+        Assert.assertEquals(invoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
index 736fba2..a8e346c 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
@@ -188,25 +188,6 @@ public class AccountJson extends AccountJsonSimple {
         };
     }
 
-    // Seems like Jackson (JacksonJsonProvider.readFrom(Class<Object>, Type, Annotation[], MediaType, MultivaluedMap<String,String>, InputStream) line: 443)
-    // needs us to define a default CTOR to instantiate the class first.
-    public AccountJson() {
-        super();
-        this.name = null;
-        this.length = null;
-        this.email = null;
-        this.billCycleDayJson = null;
-        this.currency = null;
-        this.paymentMethodId = null;
-        this.timeZone = null;
-        this.address1 = null;
-        this.address2 = null;
-        this.company = null;
-        this.state = null;
-        this.country = null;
-        this.phone = null;
-    }
-
     @JsonCreator
     public AccountJson(@JsonProperty("accountId") final String accountId,
                        @JsonProperty("name") final String name,
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java
index d1755cd..40d17ae 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJsonSimple.java
@@ -24,11 +24,6 @@ public class AccountJsonSimple {
 
     protected final String externalKey;
 
-    public AccountJsonSimple() {
-        this.accountId = null;
-        this.externalKey = null;
-    }
-
     @JsonCreator
     public AccountJsonSimple(@JsonProperty("accountId") final String accountId,
                              @JsonProperty("externalKey") final String externalKey) {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
index 29b1d29..9d889d4 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
@@ -125,7 +125,8 @@ public class AccountTimelineJson {
             final List<RefundJson> refunds = new ArrayList<RefundJson>();
             for (final Refund refund : refundsByPayment.get(payment.getId())) {
                 final List<AuditLog> auditLogs = refundsAuditLogs.get(refund.getId());
-                refunds.add(new RefundJson(refund, auditLogs));
+                // TODO add adjusted invoice items?
+                refunds.add(new RefundJson(refund, null, auditLogs));
             }
 
             final List<ChargebackJson> chargebacks = new ArrayList<ChargebackJson>();
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CreditCollectionJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CreditCollectionJson.java
index 2840e4d..5bb99dc 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CreditCollectionJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CreditCollectionJson.java
@@ -23,17 +23,17 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class CreditCollectionJson {
-    private final UUID accountId;
+    private final String accountId;
     private final List<CreditJson> credits;
 
     @JsonCreator
-    public CreditCollectionJson(@JsonProperty("accountId") final UUID accountId,
+    public CreditCollectionJson(@JsonProperty("accountId") final String accountId,
                                 @JsonProperty("credits") final List<CreditJson> credits) {
         this.accountId = accountId;
         this.credits = credits;
     }
 
-    public UUID getAccountId() {
+    public String getAccountId() {
         return accountId;
     }
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CreditJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CreditJson.java
index 458e277..e8c8f44 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CreditJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CreditJson.java
@@ -18,7 +18,6 @@ package com.ning.billing.jaxrs.json;
 
 import java.math.BigDecimal;
 import java.util.List;
-import java.util.UUID;
 
 import javax.annotation.Nullable;
 
@@ -34,21 +33,21 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 public class CreditJson extends JsonBase {
 
     private final BigDecimal creditAmount;
-    private final UUID invoiceId;
+    private final String invoiceId;
     private final String invoiceNumber;
     private final DateTime requestedDate;
     private final DateTime effectiveDate;
     private final String reason;
-    private final UUID accountId;
+    private final String accountId;
 
     @JsonCreator
     public CreditJson(@JsonProperty("creditAmount") final BigDecimal creditAmount,
-                      @JsonProperty("invoiceId") final UUID invoiceId,
+                      @JsonProperty("invoiceId") final String invoiceId,
                       @JsonProperty("invoiceNumber") final String invoiceNumber,
                       @JsonProperty("requestedDate") final DateTime requestedDate,
                       @JsonProperty("effectiveDate") final DateTime effectiveDate,
                       @JsonProperty("reason") final String reason,
-                      @JsonProperty("accountId") final UUID accountId,
+                      @JsonProperty("accountId") final String accountId,
                       @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
         super(auditLogs);
         this.creditAmount = creditAmount;
@@ -63,12 +62,12 @@ public class CreditJson extends JsonBase {
     public CreditJson(final InvoiceItem credit, final DateTimeZone accountTimeZone, final List<AuditLog> auditLogs) {
         super(toAuditLogJson(auditLogs));
         this.creditAmount = credit.getAmount();
-        this.invoiceId = credit.getInvoiceId();
+        this.invoiceId = toString(credit.getInvoiceId());
         this.invoiceNumber = null;
         this.requestedDate = null;
         this.effectiveDate = credit.getStartDate().toDateTimeAtStartOfDay(accountTimeZone);
         this.reason = null;
-        this.accountId = credit.getAccountId();
+        this.accountId = toString(credit.getAccountId());
     }
 
     public CreditJson(final InvoiceItem credit, final DateTimeZone timeZone) {
@@ -79,7 +78,7 @@ public class CreditJson extends JsonBase {
         return creditAmount;
     }
 
-    public UUID getInvoiceId() {
+    public String getInvoiceId() {
         return invoiceId;
     }
 
@@ -99,7 +98,7 @@ public class CreditJson extends JsonBase {
         return reason;
     }
 
-    public UUID getAccountId() {
+    public String getAccountId() {
         return accountId;
     }
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceItemJsonSimple.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceItemJsonSimple.java
index 5c2991c..bf09acf 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceItemJsonSimple.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceItemJsonSimple.java
@@ -17,20 +17,24 @@
 package com.ning.billing.jaxrs.json;
 
 import java.math.BigDecimal;
-import java.util.UUID;
+import java.util.List;
+
+import javax.annotation.Nullable;
 
-import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
 
-public class InvoiceItemJsonSimple {
-    private final UUID invoiceId;
-    private final UUID accountId;
-    private final UUID bundleId;
-    private final UUID subscriptionId;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class InvoiceItemJsonSimple extends JsonBase {
+
+    private final String invoiceItemId;
+    private final String invoiceId;
+    private final String accountId;
+    private final String bundleId;
+    private final String subscriptionId;
     private final String planName;
     private final String phaseName;
     private final String description;
@@ -39,17 +43,21 @@ public class InvoiceItemJsonSimple {
     private final BigDecimal amount;
     private final Currency currency;
 
-    public InvoiceItemJsonSimple(@JsonProperty("invoiceId") final UUID invoiceId,
-                                 @JsonProperty("accountId") final UUID accountId,
-                                 @JsonProperty("bundleId") final UUID bundleId,
-                                 @JsonProperty("subscriptionId") final UUID subscriptionId,
+    public InvoiceItemJsonSimple(@JsonProperty("invoiceItemId") final String invoiceItemId,
+                                 @JsonProperty("invoiceId") final String invoiceId,
+                                 @JsonProperty("accountId") final String accountId,
+                                 @JsonProperty("bundleId") final String bundleId,
+                                 @JsonProperty("subscriptionId") final String subscriptionId,
                                  @JsonProperty("planName") final String planName,
                                  @JsonProperty("phaseName") final String phaseName,
                                  @JsonProperty("description") final String description,
                                  @JsonProperty("startDate") final LocalDate startDate,
                                  @JsonProperty("endDate") final LocalDate endDate,
                                  @JsonProperty("amount") final BigDecimal amount,
-                                 @JsonProperty("currency") final Currency currency) {
+                                 @JsonProperty("currency") final Currency currency,
+                                 @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
+        super(auditLogs);
+        this.invoiceItemId = invoiceItemId;
         this.invoiceId = invoiceId;
         this.accountId = accountId;
         this.bundleId = bundleId;
@@ -64,24 +72,29 @@ public class InvoiceItemJsonSimple {
     }
 
     public InvoiceItemJsonSimple(final InvoiceItem item) {
-        this(item.getInvoiceId(), item.getAccountId(), item.getBundleId(), item.getSubscriptionId(),
+        this(toString(item.getId()), toString(item.getInvoiceId()), toString(item.getAccountId()),
+             toString(item.getBundleId()), toString(item.getSubscriptionId()),
              item.getPlanName(), item.getPhaseName(), item.getDescription(), item.getStartDate(), item.getEndDate(),
-             item.getAmount(), item.getCurrency());
+             item.getAmount(), item.getCurrency(), null);
     }
 
-    public UUID getInvoiceId() {
+    public String getInvoiceItemId() {
+        return invoiceItemId;
+    }
+
+    public String getInvoiceId() {
         return invoiceId;
     }
 
-    public UUID getAccountId() {
+    public String getAccountId() {
         return accountId;
     }
 
-    public UUID getBundleId() {
+    public String getBundleId() {
         return bundleId;
     }
 
-    public UUID getSubscriptionId() {
+    public String getSubscriptionId() {
         return subscriptionId;
     }
 
@@ -128,7 +141,7 @@ public class InvoiceItemJsonSimple {
             return false;
         }
         if (!((amount == null && that.amount == null) ||
-                (amount != null && that.amount != null && amount.compareTo(that.amount) == 0))) {
+              (amount != null && that.amount != null && amount.compareTo(that.amount) == 0))) {
             return false;
         }
         if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
@@ -141,7 +154,10 @@ public class InvoiceItemJsonSimple {
             return false;
         }
         if (!((endDate == null && that.endDate == null) ||
-                (endDate != null && that.endDate != null && endDate.compareTo(that.endDate) == 0))) {
+              (endDate != null && that.endDate != null && endDate.compareTo(that.endDate) == 0))) {
+            return false;
+        }
+        if (invoiceItemId != null ? !invoiceItemId.equals(that.invoiceItemId) : that.invoiceItemId != null) {
             return false;
         }
         if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
@@ -154,7 +170,7 @@ public class InvoiceItemJsonSimple {
             return false;
         }
         if (!((startDate == null && that.startDate == null) ||
-                (startDate != null && that.startDate != null && startDate.compareTo(that.startDate) == 0))) {
+              (startDate != null && that.startDate != null && startDate.compareTo(that.startDate) == 0))) {
             return false;
         }
         if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
@@ -167,6 +183,7 @@ public class InvoiceItemJsonSimple {
     @Override
     public int hashCode() {
         int result = invoiceId != null ? invoiceId.hashCode() : 0;
+        result = 31 * result + (invoiceItemId != null ? invoiceItemId.hashCode() : 0);
         result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
         result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
         result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/JsonBase.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/JsonBase.java
index b3e078c..315e74d 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/JsonBase.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/JsonBase.java
@@ -17,6 +17,7 @@
 package com.ning.billing.jaxrs.json;
 
 import java.util.List;
+import java.util.UUID;
 
 import javax.annotation.Nullable;
 
@@ -55,6 +56,10 @@ public abstract class JsonBase {
         return auditLogs.get(0).getReasonCode();
     }
 
+    protected static String toString(@Nullable final UUID id) {
+        return id == null ? null : id.toString();
+    }
+
     public List<AuditLogJson> getAuditLogs() {
         return auditLogs;
     }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
index 390837b..b2e1e58 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/RefundJson.java
@@ -23,11 +23,15 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 
+import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.payment.api.Refund;
 import com.ning.billing.util.audit.AuditLog;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 
 public class RefundJson extends JsonBase {
 
@@ -37,6 +41,7 @@ public class RefundJson extends JsonBase {
     private final Boolean isAdjusted;
     private final DateTime requestedDate;
     private final DateTime effectiveDate;
+    private final List<InvoiceItemJsonSimple> adjustments;
 
     @JsonCreator
     public RefundJson(@JsonProperty("refund_id") final String refundId,
@@ -45,6 +50,7 @@ public class RefundJson extends JsonBase {
                       @JsonProperty("adjusted") final Boolean isAdjusted,
                       @JsonProperty("requestedDate") final DateTime requestedDate,
                       @JsonProperty("effectiveDate") final DateTime effectiveDate,
+                      @JsonProperty("adjustments") @Nullable final List<InvoiceItemJsonSimple> adjustments,
                       @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
         super(auditLogs);
         this.refundId = refundId;
@@ -53,15 +59,19 @@ public class RefundJson extends JsonBase {
         this.isAdjusted = isAdjusted;
         this.requestedDate = requestedDate;
         this.effectiveDate = effectiveDate;
+        this.adjustments = adjustments;
     }
 
-    public RefundJson(final Refund refund) {
-        this(refund, null);
-    }
-
-    public RefundJson(final Refund refund, final List<AuditLog> auditLogs) {
+    public RefundJson(final Refund refund, @Nullable final List<InvoiceItem> adjustments, @Nullable final List<AuditLog> auditLogs) {
         this(refund.getId().toString(), refund.getPaymentId().toString(), refund.getRefundAmount(), refund.isAdjusted(),
-             refund.getEffectiveDate(), refund.getEffectiveDate(), toAuditLogJson(auditLogs));
+             refund.getEffectiveDate(), refund.getEffectiveDate(),
+             adjustments == null ? null : ImmutableList.<InvoiceItemJsonSimple>copyOf(Collections2.transform(adjustments, new Function<InvoiceItem, InvoiceItemJsonSimple>() {
+                 @Override
+                 public InvoiceItemJsonSimple apply(@Nullable final InvoiceItem input) {
+                     return new InvoiceItemJsonSimple(input);
+                 }
+             })),
+             toAuditLogJson(auditLogs));
     }
 
     public String getRefundId() {
@@ -88,6 +98,10 @@ public class RefundJson extends JsonBase {
         return effectiveDate;
     }
 
+    public List<InvoiceItemJsonSimple> getAdjustments() {
+        return adjustments;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
@@ -98,6 +112,7 @@ public class RefundJson extends JsonBase {
         sb.append(", isAdjusted=").append(isAdjusted);
         sb.append(", requestedDate=").append(requestedDate);
         sb.append(", effectiveDate=").append(effectiveDate);
+        sb.append(", adjustments=").append(adjustments);
         sb.append('}');
         return sb.toString();
     }
@@ -110,6 +125,7 @@ public class RefundJson extends JsonBase {
         result = 31 * result + (isAdjusted != null ? isAdjusted.hashCode() : 0);
         result = 31 * result + (requestedDate != null ? requestedDate.hashCode() : 0);
         result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+        result = 31 * result + (adjustments != null ? adjustments.hashCode() : 0);
         return result;
     }
 
@@ -185,6 +201,14 @@ public class RefundJson extends JsonBase {
             return false;
         }
 
+        if (adjustments == null) {
+            if (other.adjustments != null) {
+                return false;
+            }
+        } else if (!adjustments.equals(other.adjustments)) {
+            return false;
+        }
+
         return true;
     }
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
index 85c3bb2..01c3bda 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
@@ -58,6 +58,10 @@ public class InvoiceApiExceptionMapper extends ExceptionMapperBase implements Ex
             return buildNotFoundResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.INVOICE_TARGET_DATE_TOO_FAR_IN_THE_FUTURE.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
+        } else if (exception.getCode() == ErrorCode.CREDIT_AMOUNT_INVALID.getCode()) {
+            return buildBadRequestResponse(exception, uriInfo);
+        } else if (exception.getCode() == ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID.getCode()) {
+            return buildBadRequestResponse(exception, uriInfo);
         } else {
             return buildBadRequestResponse(exception, uriInfo);
         }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index ff6a5fe..47e8e64 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -397,7 +397,8 @@ public class AccountResource extends JaxRsResourceBase {
         final List<RefundJson> result = new ArrayList<RefundJson>(Collections2.transform(refunds, new Function<Refund, RefundJson>() {
             @Override
             public RefundJson apply(Refund input) {
-                return new RefundJson(input);
+                // TODO Return adjusted items and audits
+                return new RefundJson(input, null, null);
             }
         }));
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/CreditResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/CreditResource.java
index 25a1775..6ce38af 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/CreditResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/CreditResource.java
@@ -85,13 +85,13 @@ public class CreditResource extends JaxRsResourceBase {
                                  @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                  @HeaderParam(HDR_REASON) final String reason,
                                  @HeaderParam(HDR_COMMENT) final String comment) throws AccountApiException, InvoiceApiException {
-        final Account account = accountUserApi.getAccountById(json.getAccountId());
+        final Account account = accountUserApi.getAccountById(UUID.fromString(json.getAccountId()));
         final LocalDate effectiveDate = json.getEffectiveDate().toDateTime(account.getTimeZone()).toLocalDate();
 
         final InvoiceItem credit;
         if (json.getInvoiceId() != null) {
             // Apply an invoice level credit
-            credit = invoiceUserApi.insertCreditForInvoice(account.getId(), json.getInvoiceId(), json.getCreditAmount(),
+            credit = invoiceUserApi.insertCreditForInvoice(account.getId(), UUID.fromString(json.getInvoiceId()), json.getCreditAmount(),
                                                            effectiveDate, account.getCurrency(), context.createContext(createdBy, reason, comment));
         } else {
             // Apply a account level credit
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index a824149..3e5676e 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -49,9 +49,11 @@ import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountUserApi;
 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.InvoiceNotifier;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.json.InvoiceItemJsonSimple;
 import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
 import com.ning.billing.jaxrs.json.InvoiceJsonWithItems;
 import com.ning.billing.jaxrs.json.PaymentJsonSimple;
@@ -65,6 +67,7 @@ import com.ning.billing.util.api.TagApiException;
 import com.ning.billing.util.api.TagDefinitionApiException;
 import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.dao.ObjectType;
 
 import com.google.inject.Inject;
@@ -88,6 +91,7 @@ public class InvoiceResource extends JaxRsResourceBase {
     private final Context context;
     private final JaxrsUriBuilder uriBuilder;
     private final InvoiceNotifier invoiceNotifier;
+    private final Clock clock;
 
     @Inject
     public InvoiceResource(final AccountUserApi accountApi,
@@ -97,7 +101,8 @@ public class InvoiceResource extends JaxRsResourceBase {
                            final JaxrsUriBuilder uriBuilder,
                            final TagUserApi tagUserApi,
                            final CustomFieldUserApi customFieldUserApi,
-                           final InvoiceNotifier invoiceNotifier) {
+                           final InvoiceNotifier invoiceNotifier,
+                           final Clock clock) {
         super(uriBuilder, tagUserApi, customFieldUserApi);
         this.accountApi = accountApi;
         this.invoiceApi = invoiceApi;
@@ -105,28 +110,39 @@ public class InvoiceResource extends JaxRsResourceBase {
         this.context = context;
         this.uriBuilder = uriBuilder;
         this.invoiceNotifier = invoiceNotifier;
+        this.clock = clock;
     }
 
     @GET
     @Produces(APPLICATION_JSON)
-    public Response getInvoices(@QueryParam(QUERY_ACCOUNT_ID) final String accountId) throws AccountApiException {
+    public Response getInvoices(@QueryParam(QUERY_ACCOUNT_ID) final String accountId,
+                                @QueryParam(QUERY_INVOICE_WITH_ITEMS) @DefaultValue("false") final boolean withItems) throws AccountApiException {
         // Verify the account exists
         accountApi.getAccountById(UUID.fromString(accountId));
 
         final List<Invoice> invoices = invoiceApi.getInvoicesByAccount(UUID.fromString(accountId));
-        final List<InvoiceJsonSimple> result = new LinkedList<InvoiceJsonSimple>();
-        for (final Invoice invoice : invoices) {
-            result.add(new InvoiceJsonSimple(invoice));
-        }
+        if (withItems) {
+            final List<InvoiceJsonWithItems> result = new LinkedList<InvoiceJsonWithItems>();
+            for (final Invoice invoice : invoices) {
+                result.add(new InvoiceJsonWithItems(invoice));
+            }
 
-        return Response.status(Status.OK).entity(result).build();
+            return Response.status(Status.OK).entity(result).build();
+        } else {
+            final List<InvoiceJsonSimple> result = new LinkedList<InvoiceJsonSimple>();
+            for (final Invoice invoice : invoices) {
+                result.add(new InvoiceJsonSimple(invoice));
+            }
+
+            return Response.status(Status.OK).entity(result).build();
+        }
     }
 
     @GET
     @Path("/{invoiceId:" + UUID_PATTERN + "}/")
     @Produces(APPLICATION_JSON)
     public Response getInvoice(@PathParam("invoiceId") final String invoiceId,
-                               @QueryParam("withItems") @DefaultValue("false") final boolean withItems) throws InvoiceApiException {
+                               @QueryParam(QUERY_INVOICE_WITH_ITEMS) @DefaultValue("false") final boolean withItems) throws InvoiceApiException {
         final Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId));
         if (invoice == null) {
             throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND);
@@ -166,6 +182,47 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
     }
 
+    @POST
+    @Path("/{invoiceId:" + UUID_PATTERN + "}")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response adjustInvoiceItem(final InvoiceItemJsonSimple json,
+                                      @PathParam("invoiceId") final String invoiceId,
+                                      @QueryParam(QUERY_REQUESTED_DT) final String requestedDateTimeString,
+                                      @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                      @HeaderParam(HDR_REASON) final String reason,
+                                      @HeaderParam(HDR_COMMENT) final String comment) throws AccountApiException, InvoiceApiException {
+        final Account account = accountApi.getAccountById(UUID.fromString(json.getAccountId()));
+
+        // Get the effective date of the adjustment, in the account timezone
+        final LocalDate requestedDate;
+        if (requestedDateTimeString == null) {
+            requestedDate = clock.getUTCToday();
+        } else {
+            final DateTime requestedDateTime = DATE_TIME_FORMATTER.parseDateTime(requestedDateTimeString);
+            requestedDate = requestedDateTime.toDateTime(account.getTimeZone()).toLocalDate();
+        }
+
+        final InvoiceItem adjustmentItem;
+        if (json.getAmount() == null) {
+            adjustmentItem = invoiceApi.insertInvoiceItemAdjustment(account.getId(),
+                                                                    UUID.fromString(invoiceId),
+                                                                    UUID.fromString(json.getInvoiceItemId()),
+                                                                    requestedDate,
+                                                                    context.createContext(createdBy, reason, comment));
+        } else {
+            adjustmentItem = invoiceApi.insertInvoiceItemAdjustment(account.getId(),
+                                                                    UUID.fromString(invoiceId),
+                                                                    UUID.fromString(json.getInvoiceItemId()),
+                                                                    requestedDate,
+                                                                    json.getAmount(),
+                                                                    json.getCurrency(),
+                                                                    context.createContext(createdBy, reason, comment));
+        }
+
+        return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId());
+    }
+
     @GET
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
     @Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index 037d036..567a72b 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -51,6 +51,8 @@ public interface JaxrsResource {
 
     public static final String QUERY_ACCOUNT_ID = "accountId";
 
+    public static final String QUERY_INVOICE_WITH_ITEMS = "withItems";
+
     public static final String QUERY_PAYMENT_EXTERNAL = "externalPayment";
     public static final String QUERY_PAYMENT_LAST4_CC = "last4CC";
     public static final String QUERY_PAYMENT_NAME_ON_CC = "nameOnCC";
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
index 8ee10fa..8e61d24 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
@@ -88,7 +88,8 @@ public class PaymentResource extends JaxRsResourceBase {
         final List<RefundJson> result = new ArrayList<RefundJson>(Collections2.transform(refunds, new Function<Refund, RefundJson>() {
             @Override
             public RefundJson apply(final Refund input) {
-                return new RefundJson(input);
+                // TODO Return adjusted items and audits
+                return new RefundJson(input, null, null);
             }
         }));
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java
index fe1e3c7..c50c17a 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/RefundResource.java
@@ -57,7 +57,8 @@ public class RefundResource extends JaxRsResourceBase {
     @Produces(APPLICATION_JSON)
     public Response getRefund(@PathParam("refundId") final String refundId) throws PaymentApiException {
         final Refund refund = paymentApi.getRefund(UUID.fromString(refundId));
-        return Response.status(Status.OK).entity(new RefundJson(refund)).build();
+        // TODO Return adjusted items and audits
+        return Response.status(Status.OK).entity(new RefundJson(refund, null, null)).build();
     }
 
     @Override
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestCreditCollectionJson.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestCreditCollectionJson.java
index 04e8177..6e58ae5 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestCreditCollectionJson.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestCreditCollectionJson.java
@@ -36,10 +36,10 @@ public class TestCreditCollectionJson extends JaxrsTestSuite {
 
     @Test(groups = "fast")
     public void testJson() throws Exception {
-        final UUID accountId = UUID.randomUUID();
+        final String accountId = UUID.randomUUID().toString();
 
         final BigDecimal creditAmount = BigDecimal.TEN;
-        final UUID invoiceId = UUID.randomUUID();
+        final String invoiceId = UUID.randomUUID().toString();
         final String invoiceNumber = UUID.randomUUID().toString();
         final DateTime requestedDate = clock.getUTCNow();
         final DateTime effectiveDate = clock.getUTCNow();
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestCreditJson.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestCreditJson.java
index 913c5ec..e63ba91 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestCreditJson.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestCreditJson.java
@@ -35,12 +35,12 @@ public class TestCreditJson extends JaxrsTestSuite {
     @Test(groups = "fast")
     public void testJson() throws Exception {
         final BigDecimal creditAmount = BigDecimal.TEN;
-        final UUID invoiceId = UUID.randomUUID();
+        final String invoiceId = UUID.randomUUID().toString();
         final String invoiceNumber = UUID.randomUUID().toString();
         final DateTime requestedDate = clock.getUTCNow();
         final DateTime effectiveDate = clock.getUTCNow();
         final String reason = UUID.randomUUID().toString();
-        final UUID accountId = UUID.randomUUID();
+        final String accountId = UUID.randomUUID().toString();
         final List<AuditLogJson> auditLogs = createAuditLogsJson();
         final CreditJson creditJson = new CreditJson(creditAmount, invoiceId, invoiceNumber, requestedDate, effectiveDate,
                                                      reason, accountId, auditLogs);
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceItemJsonSimple.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
index 8168844..a182119 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceItemJsonSimple.java
@@ -17,6 +17,7 @@
 package com.ning.billing.jaxrs.json;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
@@ -30,27 +31,17 @@ import com.ning.billing.jaxrs.JaxrsTestSuite;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.DefaultClock;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.datatype.joda.JodaModule;
-
 public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
 
-    private static final ObjectMapper mapper = new ObjectMapper();
-
     private final Clock clock = new DefaultClock();
 
-    static {
-        mapper.registerModule(new JodaModule());
-        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
-    }
-
     @Test(groups = "fast")
     public void testJson() throws Exception {
-        final UUID invoiceId = UUID.randomUUID();
-        final UUID accountId = UUID.randomUUID();
-        final UUID bundleId = UUID.randomUUID();
-        final UUID subscriptionId = UUID.randomUUID();
+        final String invoiceItemId = UUID.randomUUID().toString();
+        final String invoiceId = UUID.randomUUID().toString();
+        final String accountId = UUID.randomUUID().toString();
+        final String bundleId = UUID.randomUUID().toString();
+        final String subscriptionId = UUID.randomUUID().toString();
         final String planName = UUID.randomUUID().toString();
         final String phaseName = UUID.randomUUID().toString();
         final String description = UUID.randomUUID().toString();
@@ -58,9 +49,11 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
         final LocalDate endDate = clock.getUTCToday();
         final BigDecimal amount = BigDecimal.TEN;
         final Currency currency = Currency.MXN;
-        final InvoiceItemJsonSimple invoiceItemJsonSimple = new InvoiceItemJsonSimple(invoiceId, accountId, bundleId, subscriptionId,
+        final List<AuditLogJson> auditLogs = createAuditLogsJson();
+        final InvoiceItemJsonSimple invoiceItemJsonSimple = new InvoiceItemJsonSimple(invoiceItemId, invoiceId, accountId, bundleId, subscriptionId,
                                                                                       planName, phaseName, description, startDate, endDate,
-                                                                                      amount, currency);
+                                                                                      amount, currency, auditLogs);
+        Assert.assertEquals(invoiceItemJsonSimple.getInvoiceItemId(), invoiceItemId);
         Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceId);
         Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), accountId);
         Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), bundleId);
@@ -72,20 +65,9 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
         Assert.assertEquals(invoiceItemJsonSimple.getEndDate(), endDate);
         Assert.assertEquals(invoiceItemJsonSimple.getAmount(), amount);
         Assert.assertEquals(invoiceItemJsonSimple.getCurrency(), currency);
+        Assert.assertEquals(invoiceItemJsonSimple.getAuditLogs(), auditLogs);
 
         final String asJson = mapper.writeValueAsString(invoiceItemJsonSimple);
-        Assert.assertEquals(asJson, "{\"invoiceId\":\"" + invoiceItemJsonSimple.getInvoiceId().toString() + "\"," +
-                                    "\"accountId\":\"" + invoiceItemJsonSimple.getAccountId().toString() + "\"," +
-                                    "\"bundleId\":\"" + invoiceItemJsonSimple.getBundleId().toString() + "\"," +
-                                    "\"subscriptionId\":\"" + invoiceItemJsonSimple.getSubscriptionId().toString() + "\"," +
-                                    "\"planName\":\"" + invoiceItemJsonSimple.getPlanName() + "\"," +
-                                    "\"phaseName\":\"" + invoiceItemJsonSimple.getPhaseName() + "\"," +
-                                    "\"description\":\"" + invoiceItemJsonSimple.getDescription() + "\"," +
-                                    "\"startDate\":\"" + invoiceItemJsonSimple.getStartDate().toString() + "\"," +
-                                    "\"endDate\":\"" + invoiceItemJsonSimple.getEndDate().toString() + "\"," +
-                                    "\"amount\":" + invoiceItemJsonSimple.getAmount().toString() + "," +
-                                    "\"currency\":\"" + invoiceItemJsonSimple.getCurrency().toString() + "\"}");
-
         final InvoiceItemJsonSimple fromJson = mapper.readValue(asJson, InvoiceItemJsonSimple.class);
         Assert.assertEquals(fromJson, invoiceItemJsonSimple);
     }
@@ -93,6 +75,7 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
     @Test(groups = "fast")
     public void testFromInvoiceItem() throws Exception {
         final InvoiceItem invoiceItem = Mockito.mock(InvoiceItem.class);
+        Mockito.when(invoiceItem.getId()).thenReturn(UUID.randomUUID());
         Mockito.when(invoiceItem.getInvoiceId()).thenReturn(UUID.randomUUID());
         Mockito.when(invoiceItem.getAccountId()).thenReturn(UUID.randomUUID());
         Mockito.when(invoiceItem.getBundleId()).thenReturn(UUID.randomUUID());
@@ -106,10 +89,11 @@ public class TestInvoiceItemJsonSimple extends JaxrsTestSuite {
         Mockito.when(invoiceItem.getCurrency()).thenReturn(Currency.EUR);
 
         final InvoiceItemJsonSimple invoiceItemJsonSimple = new InvoiceItemJsonSimple(invoiceItem);
-        Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceItem.getInvoiceId());
-        Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), invoiceItem.getAccountId());
-        Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), invoiceItem.getBundleId());
-        Assert.assertEquals(invoiceItemJsonSimple.getSubscriptionId(), invoiceItem.getSubscriptionId());
+        Assert.assertEquals(invoiceItemJsonSimple.getInvoiceItemId(), invoiceItem.getId().toString());
+        Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceItem.getInvoiceId().toString());
+        Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), invoiceItem.getAccountId().toString());
+        Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), invoiceItem.getBundleId().toString());
+        Assert.assertEquals(invoiceItemJsonSimple.getSubscriptionId(), invoiceItem.getSubscriptionId().toString());
         Assert.assertEquals(invoiceItemJsonSimple.getPlanName(), invoiceItem.getPlanName());
         Assert.assertEquals(invoiceItemJsonSimple.getPhaseName(), invoiceItem.getPhaseName());
         Assert.assertEquals(invoiceItemJsonSimple.getDescription(), invoiceItem.getDescription());
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
index d312294..4b786ac 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithBundleKeys.java
@@ -110,12 +110,12 @@ public class TestInvoiceJsonWithBundleKeys extends JaxrsTestSuite {
 
     private CreditJson createCreditJson() {
         final BigDecimal creditAmount = BigDecimal.TEN;
-        final UUID invoiceId = UUID.randomUUID();
+        final String invoiceId = UUID.randomUUID().toString();
         final String invoiceNumber = UUID.randomUUID().toString();
         final DateTime requestedDate = clock.getUTCNow();
         final DateTime effectiveDate = clock.getUTCNow();
         final String reason = UUID.randomUUID().toString();
-        final UUID accountId = UUID.randomUUID();
+        final String accountId = UUID.randomUUID().toString();
         return new CreditJson(creditAmount, invoiceId, invoiceNumber, requestedDate, effectiveDate, reason, accountId, null);
     }
 }
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithItems.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithItems.java
index c66bc4f..1b310f6 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithItems.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestInvoiceJsonWithItems.java
@@ -99,10 +99,10 @@ public class TestInvoiceJsonWithItems extends JaxrsTestSuite {
         Assert.assertNull(invoiceJsonWithItems.getAuditLogs());
 
         final InvoiceItemJsonSimple invoiceItemJsonSimple = invoiceJsonWithItems.getItems().get(0);
-        Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceItem.getInvoiceId());
-        Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), invoiceItem.getAccountId());
-        Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), invoiceItem.getBundleId());
-        Assert.assertEquals(invoiceItemJsonSimple.getSubscriptionId(), invoiceItem.getSubscriptionId());
+        Assert.assertEquals(invoiceItemJsonSimple.getInvoiceId(), invoiceItem.getInvoiceId().toString());
+        Assert.assertEquals(invoiceItemJsonSimple.getAccountId(), invoiceItem.getAccountId().toString());
+        Assert.assertEquals(invoiceItemJsonSimple.getBundleId(), invoiceItem.getBundleId().toString());
+        Assert.assertEquals(invoiceItemJsonSimple.getSubscriptionId(), invoiceItem.getSubscriptionId().toString());
         Assert.assertEquals(invoiceItemJsonSimple.getPlanName(), invoiceItem.getPlanName());
         Assert.assertEquals(invoiceItemJsonSimple.getPhaseName(), invoiceItem.getPhaseName());
         Assert.assertEquals(invoiceItemJsonSimple.getDescription(), invoiceItem.getDescription());
@@ -113,10 +113,11 @@ public class TestInvoiceJsonWithItems extends JaxrsTestSuite {
     }
 
     private InvoiceItemJsonSimple createInvoiceItemJson() {
-        final UUID invoiceId = UUID.randomUUID();
-        final UUID accountId = UUID.randomUUID();
-        final UUID bundleId = UUID.randomUUID();
-        final UUID subscriptionId = UUID.randomUUID();
+        final String invoiceItemId = UUID.randomUUID().toString();
+        final String invoiceId = UUID.randomUUID().toString();
+        final String accountId = UUID.randomUUID().toString();
+        final String bundleId = UUID.randomUUID().toString();
+        final String subscriptionId = UUID.randomUUID().toString();
         final String planName = UUID.randomUUID().toString();
         final String phaseName = UUID.randomUUID().toString();
         final String description = UUID.randomUUID().toString();
@@ -124,9 +125,9 @@ public class TestInvoiceJsonWithItems extends JaxrsTestSuite {
         final LocalDate endDate = clock.getUTCToday();
         final BigDecimal amount = BigDecimal.TEN;
         final Currency currency = Currency.MXN;
-        return new InvoiceItemJsonSimple(invoiceId, accountId, bundleId, subscriptionId,
+        return new InvoiceItemJsonSimple(invoiceItemId, invoiceId, accountId, bundleId, subscriptionId,
                                          planName, phaseName, description, startDate, endDate,
-                                         amount, currency);
+                                         amount, currency, null);
     }
 
     private InvoiceItem createInvoiceItem() {
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestRefundJson.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestRefundJson.java
new file mode 100644
index 0000000..d4dde15
--- /dev/null
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestRefundJson.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.jaxrs.JaxrsTestSuite;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestRefundJson extends JaxrsTestSuite {
+
+    private final Clock clock = new ClockMock();
+
+    @Test(groups = "fast")
+    public void testJson() throws Exception {
+        final String refundId = UUID.randomUUID().toString();
+        final String paymentId = UUID.randomUUID().toString();
+        final BigDecimal refundAmount = BigDecimal.TEN;
+        final boolean isAdjusted = true;
+        final DateTime requestedDate = clock.getUTCNow();
+        final DateTime effectiveDate = clock.getUTCNow();
+        final List<InvoiceItemJsonSimple> adjustments = ImmutableList.<InvoiceItemJsonSimple>of(createInvoiceItemJson());
+        final List<AuditLogJson> auditLogs = createAuditLogsJson();
+        final RefundJson refundJson = new RefundJson(refundId, paymentId, refundAmount, isAdjusted, requestedDate,
+                                                     effectiveDate, adjustments, auditLogs);
+        Assert.assertEquals(refundJson.getRefundId(), refundId);
+        Assert.assertEquals(refundJson.getPaymentId(), paymentId);
+        Assert.assertEquals(refundJson.getRefundAmount(), refundAmount);
+        Assert.assertEquals(refundJson.isAdjusted(), isAdjusted);
+        Assert.assertEquals(refundJson.getRequestedDate(), requestedDate);
+        Assert.assertEquals(refundJson.getEffectiveDate(), effectiveDate);
+        Assert.assertEquals(refundJson.getAdjustments(), adjustments);
+        Assert.assertEquals(refundJson.getAuditLogs(), auditLogs);
+
+        final String asJson = mapper.writeValueAsString(refundJson);
+        final RefundJson fromJson = mapper.readValue(asJson, RefundJson.class);
+        Assert.assertEquals(fromJson, refundJson);
+    }
+
+    private InvoiceItemJsonSimple createInvoiceItemJson() {
+        final String invoiceItemId = UUID.randomUUID().toString();
+        final String invoiceId = UUID.randomUUID().toString();
+        final String accountId = UUID.randomUUID().toString();
+        final String bundleId = UUID.randomUUID().toString();
+        final String subscriptionId = UUID.randomUUID().toString();
+        final String planName = UUID.randomUUID().toString();
+        final String phaseName = UUID.randomUUID().toString();
+        final String description = UUID.randomUUID().toString();
+        final LocalDate startDate = clock.getUTCToday();
+        final LocalDate endDate = clock.getUTCToday();
+        final BigDecimal amount = BigDecimal.TEN;
+        final Currency currency = Currency.MXN;
+        final List<AuditLogJson> auditLogs = createAuditLogsJson();
+        return new InvoiceItemJsonSimple(invoiceItemId, invoiceId, accountId, bundleId, subscriptionId,
+                                         planName, phaseName, description, startDate, endDate,
+                                         amount, currency, auditLogs);
+    }
+}
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 69fbb4b..b354c70 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
@@ -13,12 +13,8 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
-package com.ning.billing.payment.core;
-
-import javax.inject.Inject;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+package com.ning.billing.payment.core;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
@@ -28,10 +24,11 @@ import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
-import com.google.inject.name.Named;
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
@@ -40,9 +37,7 @@ import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.payment.api.DefaultRefund;
 import com.ning.billing.payment.api.PaymentApiException;
-import com.ning.billing.payment.api.PaymentStatus;
 import com.ning.billing.payment.api.Refund;
-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.RefundModelDao;
@@ -57,32 +52,36 @@ 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.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.inject.name.Named;
+
 import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 
 public class RefundProcessor extends ProcessorBase {
 
-    private final static Logger log = LoggerFactory.getLogger(RefundProcessor.class);
+    private static final Logger log = LoggerFactory.getLogger(RefundProcessor.class);
 
     private final InvoicePaymentApi invoicePaymentApi;
     private final CallContextFactory factory;
 
     @Inject
     public RefundProcessor(final PaymentProviderPluginRegistry pluginRegistry,
-            final AccountUserApi accountUserApi,
-            final InvoicePaymentApi invoicePaymentApi,
-            final Bus eventBus,
-            final CallContextFactory factory,
-            final PaymentDao paymentDao,
-            final GlobalLocker locker,
-            @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor) {
+                           final AccountUserApi accountUserApi,
+                           final InvoicePaymentApi invoicePaymentApi,
+                           final Bus eventBus,
+                           final CallContextFactory factory,
+                           final PaymentDao paymentDao,
+                           final GlobalLocker locker,
+                           @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor) {
         super(pluginRegistry, accountUserApi, eventBus, paymentDao, locker, executor);
         this.invoicePaymentApi = invoicePaymentApi;
         this.factory = factory;
     }
 
-
     public Refund createRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final boolean isAdjusted, final CallContext context)
-    throws PaymentApiException {
+            throws PaymentApiException {
 
         return new WithAccountLock<Refund>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<Refund>() {
 
@@ -104,8 +103,8 @@ public class RefundProcessor extends ProcessorBase {
                     int foundPluginCompletedRefunds = 0;
                     RefundModelDao refundInfo = null;
                     BigDecimal totalAmountRefunded = BigDecimal.ZERO;
-                    List<RefundModelDao> existingRefunds = paymentDao.getRefundsForPayment(paymentId);
-                    for (RefundModelDao cur : existingRefunds) {
+                    final List<RefundModelDao> existingRefunds = paymentDao.getRefundsForPayment(paymentId);
+                    for (final RefundModelDao cur : existingRefunds) {
 
                         final BigDecimal existingPositiveAmount = cur.getAmount();
                         if (existingPositiveAmount.compareTo(refundAmount) == 0) {
@@ -132,7 +131,7 @@ public class RefundProcessor extends ProcessorBase {
                     }
 
                     final PaymentPluginApi plugin = getPaymentProviderPlugin(payment.getPaymentMethodId());
-                    int nbExistingRefunds = plugin.getNbRefundForPaymentAmount(account, paymentId, refundAmount);
+                    final int nbExistingRefunds = plugin.getNbRefundForPaymentAmount(account, paymentId, refundAmount);
                     log.debug(String.format("found %d pluginRefunds for paymentId %s and amount %s", nbExistingRefunds, paymentId, refundAmount));
 
                     if (nbExistingRefunds > foundPluginCompletedRefunds) {
@@ -158,14 +157,13 @@ public class RefundProcessor extends ProcessorBase {
         });
     }
 
-
     public Refund getRefund(final UUID refundId)
-    throws PaymentApiException {
+            throws PaymentApiException {
         RefundModelDao result = paymentDao.getRefund(refundId);
         if (result == null) {
             throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_REFUND, refundId);
         }
-        List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(Collections.singletonList(result));
+        final List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(Collections.singletonList(result));
         if (filteredInput.size() == 0) {
             throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_REFUND, refundId);
         }
@@ -177,31 +175,30 @@ public class RefundProcessor extends ProcessorBase {
                                  result.isAdjsuted(), result.getCreatedDate());
     }
 
-
     public List<Refund> getAccountRefunds(final Account account)
-    throws PaymentApiException {
+            throws PaymentApiException {
         List<RefundModelDao> result = paymentDao.getRefundsForAccount(account.getId());
         if (completePluginCompletedRefund(result)) {
             result = paymentDao.getRefundsForAccount(account.getId());
         }
-        List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(result);
+        final List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(result);
         return toRefunds(filteredInput);
     }
 
     public List<Refund> getPaymentRefunds(final UUID paymentId)
-    throws PaymentApiException {
+            throws PaymentApiException {
         List<RefundModelDao> result = paymentDao.getRefundsForPayment(paymentId);
         if (completePluginCompletedRefund(result)) {
             result = paymentDao.getRefundsForPayment(paymentId);
         }
-        List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(result);
+        final List<RefundModelDao> filteredInput = filterUncompletedPluginRefund(result);
         return toRefunds(filteredInput);
     }
 
     public List<Refund> toRefunds(final List<RefundModelDao> in) {
         return new ArrayList<Refund>(Collections2.transform(in, new Function<RefundModelDao, Refund>() {
             @Override
-            public Refund apply(RefundModelDao cur) {
+            public Refund apply(final RefundModelDao cur) {
                 return new DefaultRefund(cur.getId(), cur.getPaymentId(), cur.getAmount(), cur.getCurrency(),
                                          cur.isAdjsuted(), cur.getCreatedDate());
             }
@@ -211,7 +208,7 @@ public class RefundProcessor extends ProcessorBase {
     private List<RefundModelDao> filterUncompletedPluginRefund(final List<RefundModelDao> input) {
         return new ArrayList<RefundModelDao>(Collections2.filter(input, new Predicate<RefundModelDao>() {
             @Override
-            public boolean apply(RefundModelDao in) {
+            public boolean apply(final RefundModelDao in) {
                 return in.getRefundStatus() != RefundStatus.CREATED;
             }
         }));
@@ -219,10 +216,9 @@ public class RefundProcessor extends ProcessorBase {
 
     private boolean completePluginCompletedRefund(final List<RefundModelDao> refunds) throws PaymentApiException {
 
-
         final Collection<RefundModelDao> refundsToBeFixed = Collections2.filter(refunds, new Predicate<RefundModelDao>() {
             @Override
-            public boolean apply(RefundModelDao in) {
+            public boolean apply(final RefundModelDao in) {
                 return in.getRefundStatus() == RefundStatus.PLUGIN_COMPLETED;
             }
         });
@@ -231,14 +227,14 @@ public class RefundProcessor extends ProcessorBase {
         }
 
         try {
-            Account account = accountUserApi.getAccountById(refundsToBeFixed.iterator().next().getAccountId());
+            final Account account = accountUserApi.getAccountById(refundsToBeFixed.iterator().next().getAccountId());
             new WithAccountLock<Void>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<Void>() {
 
                 @Override
                 public Void doOperation() throws PaymentApiException {
                     try {
                         final CallContext context = factory.createCallContext("RefundProcessor", CallOrigin.INTERNAL, UserType.SYSTEM);
-                        for (RefundModelDao cur : refundsToBeFixed) {
+                        for (final RefundModelDao cur : refundsToBeFixed) {
                             invoicePaymentApi.createRefund(cur.getPaymentId(), cur.getAmount(), cur.isAdjsuted(), cur.getId(), context);
                             paymentDao.updateRefundStatus(cur.getId(), RefundStatus.COMPLETED, context);
                         }
@@ -253,15 +249,4 @@ public class RefundProcessor extends ProcessorBase {
             throw new PaymentApiException(e);
         }
     }
-
-    private PaymentAttemptModelDao getPaymentAttempt(final UUID paymentId) {
-        List<PaymentAttemptModelDao> attempts = paymentDao.getAttemptsForPayment(paymentId);
-        for (PaymentAttemptModelDao cur : attempts) {
-            if (cur.getPaymentStatus() == PaymentStatus.SUCCESS) {
-                return cur;
-            }
-        }
-        return null;
-    }
-
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestCredit.java b/server/src/test/java/com/ning/billing/jaxrs/TestCredit.java
index d7b5dca..b459ce3 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestCredit.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestCredit.java
@@ -60,9 +60,9 @@ public class TestCredit extends TestJaxrsBase {
         final DateTime requestedDate = clock.getUTCNow();
         final DateTime effectiveDate = clock.getUTCNow();
         final InvoiceJsonSimple invoice = createInvoice();
-        final CreditJson input = new CreditJson(BigDecimal.TEN, UUID.fromString(invoice.getInvoiceId()), UUID.randomUUID().toString(),
+        final CreditJson input = new CreditJson(BigDecimal.TEN, invoice.getInvoiceId(), UUID.randomUUID().toString(),
                                                 requestedDate, effectiveDate,
-                                                UUID.randomUUID().toString(), UUID.fromString(accountJson.getAccountId()),
+                                                UUID.randomUUID().toString(), accountJson.getAccountId(),
                                                 null);
         final String jsonInput = mapper.writeValueAsString(input);
 
@@ -120,9 +120,9 @@ public class TestCredit extends TestJaxrsBase {
     public void testAccountDoesNotExist() throws Exception {
         final DateTime requestedDate = clock.getUTCNow();
         final DateTime effectiveDate = clock.getUTCNow();
-        final CreditJson input = new CreditJson(BigDecimal.TEN, UUID.randomUUID(), UUID.randomUUID().toString(),
+        final CreditJson input = new CreditJson(BigDecimal.TEN, UUID.randomUUID().toString(), UUID.randomUUID().toString(),
                                                 requestedDate, effectiveDate,
-                                                UUID.randomUUID().toString(), UUID.randomUUID(), null);
+                                                UUID.randomUUID().toString(), UUID.randomUUID().toString(), null);
         final String jsonInput = mapper.writeValueAsString(input);
 
         // Try to create the credit
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
index 4dfc5ab..675159a 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -17,157 +17,69 @@
 package com.ning.billing.jaxrs;
 
 import java.math.BigDecimal;
-import java.util.HashMap;
+import java.math.RoundingMode;
 import java.util.List;
-import java.util.Map;
-
-import javax.ws.rs.core.Response.Status;
 
 import org.joda.time.DateTime;
-import org.joda.time.Interval;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
 import org.testng.annotations.Test;
 
-import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.jaxrs.json.AccountJson;
-import com.ning.billing.jaxrs.json.BundleJsonNoSubscriptions;
+import com.ning.billing.jaxrs.json.InvoiceItemJsonSimple;
 import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
+import com.ning.billing.jaxrs.json.InvoiceJsonWithItems;
 import com.ning.billing.jaxrs.json.PaymentJsonSimple;
 import com.ning.billing.jaxrs.json.PaymentMethodJson;
-import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
-import com.ning.billing.jaxrs.resources.JaxrsResource;
 import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
-import com.ning.http.client.Response;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.google.common.collect.ImmutableMap;
 
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
 
 public class TestInvoice extends TestJaxrsBase {
 
-    private static final Logger log = LoggerFactory.getLogger(TestInvoice.class);
-
     @Test(groups = "slow")
     public void testInvoiceOk() throws Exception {
         final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
         clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
 
-        final AccountJson accountJson = createAccountWithDefaultPaymentMethod("poupou", "qhddffrwe", "poupou@yahoo.com");
-        assertNotNull(accountJson);
-
-        final BundleJsonNoSubscriptions bundleJson = createBundle(accountJson.getAccountId(), "9967599");
-        assertNotNull(bundleJson);
-
-        final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
-        assertNotNull(subscriptionJson);
+        final AccountJson accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
 
-        // MOVE AFTER TRIAL
-        final Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
-        clock.addDeltaFromReality(it.toDurationMillis());
-        crappyWaitForLackOfProperSynchonization();
-
-        String uri = JaxrsResource.INVOICES_PATH;
-        final Map<String, String> queryParams = new HashMap<String, String>();
-        queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAccountId());
-
-        Response response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        String baseJson = response.getResponseBody();
-        List<InvoiceJsonSimple> objFromJson = mapper.readValue(baseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
-        assertNotNull(objFromJson);
-        log.info(baseJson);
-        assertEquals(objFromJson.size(), 2);
+        final List<InvoiceJsonSimple> invoices = getInvoicesForAccount(accountJson.getAccountId());
+        assertEquals(invoices.size(), 2);
 
         // Check we can retrieve an individual invoice
-        uri = JaxrsResource.INVOICES_PATH + "/" + objFromJson.get(0).getInvoiceId();
-        response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        baseJson = response.getResponseBody();
-        final InvoiceJsonSimple firstInvoiceJson = mapper.readValue(baseJson, InvoiceJsonSimple.class);
-        assertNotNull(objFromJson);
-        assertEquals(firstInvoiceJson, objFromJson.get(0));
+        final InvoiceJsonSimple invoiceJsonSimple = invoices.get(0);
+        final InvoiceJsonSimple firstInvoiceJson = getInvoice(invoiceJsonSimple.getInvoiceId());
+        assertEquals(firstInvoiceJson, invoiceJsonSimple);
 
         // Then create a dryRun Invoice
         final DateTime futureDate = clock.getUTCNow().plusMonths(1).plusDays(3);
-        uri = JaxrsResource.INVOICES_PATH;
-        queryParams.put(JaxrsResource.QUERY_TARGET_DATE, futureDate.toString());
-        queryParams.put(JaxrsResource.QUERY_DRY_RUN, "true");
-        response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        baseJson = response.getResponseBody();
-        final InvoiceJsonSimple futureInvoice = mapper.readValue(baseJson, InvoiceJsonSimple.class);
-        assertNotNull(futureInvoice);
-        log.info(baseJson);
+        createDryRunInvoice(accountJson.getAccountId(), futureDate);
 
         // The one more time with no DryRun
-        queryParams.remove(JaxrsResource.QUERY_DRY_RUN);
-        response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
-
-        final String location = response.getHeader("Location");
-        Assert.assertNotNull(location);
-
-        // Check again # invoices, should be 5 this time
-        uri = JaxrsResource.INVOICES_PATH;
-        response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
-        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        baseJson = response.getResponseBody();
-        objFromJson = mapper.readValue(baseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
-        assertNotNull(objFromJson);
-        log.info(baseJson);
-        assertEquals(objFromJson.size(), 3);
+        createInvoice(accountJson.getAccountId(), futureDate);
+
+        // Check again # invoices, should be 3 this time
+        final List<InvoiceJsonSimple> newInvoiceList = getInvoicesForAccount(accountJson.getAccountId());
+        assertEquals(newInvoiceList.size(), 3);
     }
 
     @Test(groups = "slow")
     public void testInvoicePayments() throws Exception {
         clock.setTime(new DateTime(2012, 4, 25, 0, 3, 42, 0));
 
-        final AccountJson accountJson = createAccountWithDefaultPaymentMethod("nohup", "shtergyhwF", "nohup@yahoo.com");
-        assertNotNull(accountJson);
-
-        final BundleJsonNoSubscriptions bundleJson = createBundle(accountJson.getAccountId(), "391193");
-        assertNotNull(bundleJson);
+        final AccountJson accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
 
-        final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
-        assertNotNull(subscriptionJson);
-
-        clock.addDays(31);
-
-        crappyWaitForLackOfProperSynchonization();
-
-        final Map<String, String> queryParams = new HashMap<String, String>();
-        queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAccountId());
-        String uri = JaxrsResource.INVOICES_PATH;
-        Response response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
-        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        String baseJson = response.getResponseBody();
-        final List<InvoiceJsonSimple> invoices = mapper.readValue(baseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
-        assertNotNull(invoices);
-        log.info(baseJson);
+        final List<InvoiceJsonSimple> invoices = getInvoicesForAccount(accountJson.getAccountId());
         assertEquals(invoices.size(), 2);
 
         for (final InvoiceJsonSimple cur : invoices) {
+            final List<PaymentJsonSimple> objFromJson = getPaymentsForInvoice(cur.getInvoiceId());
 
-            uri = JaxrsResource.INVOICES_PATH + "/" + cur.getInvoiceId() + "/" + JaxrsResource.PAYMENTS;
-            response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-
-            Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-            baseJson = response.getResponseBody();
-            final List<PaymentJsonSimple> objFromJson = mapper.readValue(baseJson, new TypeReference<List<PaymentJsonSimple>>() {});
-            assertNotNull(objFromJson);
-            log.info(cur.getAmount().toString());
             if (cur.getAmount().compareTo(BigDecimal.ZERO) == 0) {
                 assertEquals(objFromJson.size(), 0);
             } else {
                 assertEquals(objFromJson.size(), 1);
-                assertTrue(cur.getAmount().compareTo(objFromJson.get(0).getAmount()) == 0);
+                assertEquals(cur.getAmount().compareTo(objFromJson.get(0).getAmount()), 0);
             }
         }
     }
@@ -176,31 +88,11 @@ public class TestInvoice extends TestJaxrsBase {
     public void testInvoiceCreatePayment() throws Exception {
         clock.setTime(new DateTime(2012, 4, 25, 0, 3, 42, 0));
 
-        final AccountJson accountJson = createAccountWithDefaultPaymentMethod("nohup", "shtergyhwF", "nohup@yahoo.com");
-        assertNotNull(accountJson);
-
         // STEPH MISSING SET ACCOUNT AUTO_PAY_OFF
+        final AccountJson accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
 
-        final BundleJsonNoSubscriptions bundleJson = createBundle(accountJson.getAccountId(), "391193");
-        assertNotNull(bundleJson);
-
-        final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
-        assertNotNull(subscriptionJson);
-
-        // MOVE AFTER TRIAL
-        clock.addDays(31);
-
-        crappyWaitForLackOfProperSynchonization();
-
-        final Map<String, String> queryParams = new HashMap<String, String>();
-        queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAccountId());
-        String uri = JaxrsResource.INVOICES_PATH;
-        Response response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
-        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        String baseJson = response.getResponseBody();
-        final List<InvoiceJsonSimple> invoices = mapper.readValue(baseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
-        assertNotNull(invoices);
-        log.info(baseJson);
+        // Get the invoices
+        final List<InvoiceJsonSimple> invoices = getInvoicesForAccount(accountJson.getAccountId());
         assertEquals(invoices.size(), 2);
 
         for (final InvoiceJsonSimple cur : invoices) {
@@ -209,89 +101,89 @@ public class TestInvoice extends TestJaxrsBase {
             }
 
             // CREATE INSTA PAYMENT
-            final PaymentJsonSimple payment = new PaymentJsonSimple(cur.getAmount(), BigDecimal.ZERO, accountJson.getAccountId(),
-                                                                    cur.getInvoiceId(), null, null, null, null, 0, null, null, null, null, null, null, null);
-            final String postJson = mapper.writeValueAsString(payment);
-
-            uri = JaxrsResource.INVOICES_PATH + "/" + cur.getInvoiceId() + "/" + JaxrsResource.PAYMENTS;
-            doPost(uri, postJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-
-            response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-
-            Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-            baseJson = response.getResponseBody();
-            final List<PaymentJsonSimple> objFromJson = mapper.readValue(baseJson, new TypeReference<List<PaymentJsonSimple>>() {});
-            assertNotNull(objFromJson);
-            log.info(cur.getAmount().toString());
+            final List<PaymentJsonSimple> objFromJson = createInstaPayment(accountJson, cur);
             assertEquals(objFromJson.size(), 1);
-            assertTrue(cur.getAmount().compareTo(objFromJson.get(0).getAmount()) == 0);
+            assertEquals(cur.getAmount().compareTo(objFromJson.get(0).getAmount()), 0);
         }
     }
 
     @Test(groups = "slow")
     public void testExternalPayment() throws Exception {
-        // Create an account with no payment method
-        final AccountJson accountJson = createAccount("eraahahildo", "sheqrgfhwe", "eraahahildo@yahoo.com");
-        assertNotNull(accountJson);
-
-        // Add a bundle, subscription and move the clock to get the first invoice
-        final BundleJsonNoSubscriptions bundleJson = createBundle(accountJson.getAccountId(), "317199");
-        assertNotNull(bundleJson);
-        final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
-        assertNotNull(subscriptionJson);
-        clock.addMonths(1);
-        crappyWaitForLackOfProperSynchonization();
-
-        final String paymentsURI = JaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId() + "/" + JaxrsResource.PAYMENTS;
+        final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
 
         // Verify we didn't get any payment
-        final Response noPaymentsResponse = doGet(paymentsURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-        assertEquals(noPaymentsResponse.getStatusCode(), Status.OK.getStatusCode());
-        final String noPaymentsBaseJson = noPaymentsResponse.getResponseBody();
-        final List<PaymentJsonSimple> noPaymentsFromJson = mapper.readValue(noPaymentsBaseJson, new TypeReference<List<PaymentJsonSimple>>() {});
+        final List<PaymentJsonSimple> noPaymentsFromJson = getPaymentsForAccount(accountJson.getAccountId());
         assertEquals(noPaymentsFromJson.size(), 0);
 
         // Get the invoices
-        final Map<String, String> queryParams = new HashMap<String, String>();
-        queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAccountId());
-        final String invoicesURI = JaxrsResource.INVOICES_PATH;
-        final Response invoicesResponse = doGet(invoicesURI, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
-        assertEquals(invoicesResponse.getStatusCode(), Status.OK.getStatusCode());
-        final String invoicesBaseJson = invoicesResponse.getResponseBody();
-        final List<InvoiceJsonSimple> invoices = mapper.readValue(invoicesBaseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
-        assertNotNull(invoices);
+        final List<InvoiceJsonSimple> invoices = getInvoicesForAccount(accountJson.getAccountId());
         // 2 invoices but look for the non zero dollar one
         assertEquals(invoices.size(), 2);
         final String invoiceId = invoices.get(1).getInvoiceId();
 
         // Post an external payment
         final BigDecimal paidAmount = BigDecimal.TEN;
-        final PaymentJsonSimple payment = new PaymentJsonSimple(paidAmount, BigDecimal.ZERO, accountJson.getAccountId(),
-                                                                invoiceId, null, null, null, null, 0,
-                                                                null, null, null, null, null, null, null);
-        final String postJson = mapper.writeValueAsString(payment);
-        final String paymentURI = JaxrsResource.INVOICES_PATH + "/" + invoiceId + "/" + JaxrsResource.PAYMENTS;
-        final Response paymentResponse = doPost(paymentURI, postJson, ImmutableMap.<String, String>of("externalPayment", "true"), DEFAULT_HTTP_TIMEOUT_SEC);
-        assertEquals(paymentResponse.getStatusCode(), Status.CREATED.getStatusCode());
+        createExternalPayment(accountJson, invoiceId, paidAmount);
 
         // Verify we indeed got the payment
-        final Response paymentsResponse = doGet(paymentsURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-        assertEquals(paymentsResponse.getStatusCode(), Status.OK.getStatusCode());
-        final String paymentsBaseJson = paymentsResponse.getResponseBody();
-        final List<PaymentJsonSimple> paymentsFromJson = mapper.readValue(paymentsBaseJson, new TypeReference<List<PaymentJsonSimple>>() {});
+        final List<PaymentJsonSimple> paymentsFromJson = getPaymentsForAccount(accountJson.getAccountId());
         assertEquals(paymentsFromJson.size(), 1);
         assertEquals(paymentsFromJson.get(0).getPaidAmount().compareTo(paidAmount), 0);
 
         // Check the PaymentMethod from paymentMethodId returned in the Payment object
         final String paymentMethodId = paymentsFromJson.get(0).getPaymentMethodId();
-        final String paymentMethodURI = JaxrsResource.PAYMENT_METHODS_PATH + "/" + paymentMethodId;
-
-        final Response paymentMethodResponse = doGet(paymentMethodURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-        assertEquals(paymentMethodResponse.getStatusCode(), Status.OK.getStatusCode());
-        final PaymentMethodJson paymentMethodJson = mapper.readValue(paymentMethodResponse.getResponseBody(), PaymentMethodJson.class);
+        final PaymentMethodJson paymentMethodJson = getPaymentMethod(paymentMethodId);
         assertEquals(paymentMethodJson.getPaymentMethodId(), paymentMethodId);
         assertEquals(paymentMethodJson.getAccountId(), accountJson.getAccountId());
         assertEquals(paymentMethodJson.getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
         assertNull(paymentMethodJson.getPluginInfo());
     }
+
+    @Test(groups = "slow")
+    public void testFullInvoiceItemAdjustment() throws Exception {
+        final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        // Get the invoices
+        final List<InvoiceJsonWithItems> invoices = getInvoicesWithItemsForAccount(accountJson.getAccountId());
+        // 2 invoices but look for the non zero dollar one
+        assertEquals(invoices.size(), 2);
+        final InvoiceJsonWithItems invoice = invoices.get(1);
+        // Verify the invoice we picked is non zero
+        assertEquals(invoice.getAmount().compareTo(BigDecimal.ZERO), 1);
+        final InvoiceItemJsonSimple invoiceItem = invoice.getItems().get(0);
+        // Verify the item we picked is non zero
+        assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
+
+        // Adjust the full amount
+        adjustInvoiceItem(accountJson.getAccountId(), invoice.getInvoiceId(), invoiceItem.getInvoiceItemId(), null, null, null);
+
+        // Verify the new invoice balance is zero
+        final InvoiceJsonSimple adjustedInvoice = getInvoice(invoice.getInvoiceId());
+        assertEquals(adjustedInvoice.getAmount().compareTo(BigDecimal.ZERO), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testPartialInvoiceItemAdjustment() throws Exception {
+        final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        // Get the invoices
+        final List<InvoiceJsonWithItems> invoices = getInvoicesWithItemsForAccount(accountJson.getAccountId());
+        // 2 invoices but look for the non zero dollar one
+        assertEquals(invoices.size(), 2);
+        final InvoiceJsonWithItems invoice = invoices.get(1);
+        // Verify the invoice we picked is non zero
+        assertEquals(invoice.getAmount().compareTo(BigDecimal.ZERO), 1);
+        final InvoiceItemJsonSimple invoiceItem = invoice.getItems().get(0);
+        // Verify the item we picked is non zero
+        assertEquals(invoiceItem.getAmount().compareTo(BigDecimal.ZERO), 1);
+
+        // Adjust partially the item
+        final BigDecimal adjustedAmount = invoiceItem.getAmount().divide(BigDecimal.TEN);
+        adjustInvoiceItem(accountJson.getAccountId(), invoice.getInvoiceId(), invoiceItem.getInvoiceItemId(), null, adjustedAmount, null);
+
+        // Verify the new invoice balance
+        final InvoiceJsonSimple adjustedInvoice = getInvoice(invoice.getInvoiceId());
+        final BigDecimal adjustedInvoiceBalance = invoice.getBalance().add(adjustedAmount.negate().setScale(2, RoundingMode.HALF_UP));
+        assertEquals(adjustedInvoice.getBalance().compareTo(adjustedInvoiceBalance), 0);
+    }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index aba8426..34326cf 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -19,6 +19,7 @@ import javax.annotation.Nullable;
 import javax.ws.rs.core.Response.Status;
 import java.io.IOException;
 import java.lang.reflect.Method;
+import java.math.BigDecimal;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.EventListener;
@@ -31,6 +32,7 @@ import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.jetty.servlet.FilterHolder;
+import org.joda.time.DateTime;
 import org.skife.config.ConfigurationObjectFactory;
 import org.skife.jdbi.v2.IDBI;
 import org.slf4j.Logger;
@@ -42,9 +44,12 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.BeforeSuite;
 
+import com.fasterxml.jackson.core.JsonGenerationException;
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.datatype.joda.JodaModule;
+import com.google.common.collect.ImmutableMap;
 import com.google.inject.Module;
 import com.ning.billing.KillbillTestSuiteWithEmbeddedDB;
 import com.ning.billing.account.api.BillCycleDay;
@@ -53,7 +58,10 @@ import com.ning.billing.analytics.setup.AnalyticsModule;
 import com.ning.billing.api.TestApiListener;
 import com.ning.billing.beatrix.glue.BeatrixModule;
 import com.ning.billing.beatrix.integration.TestIntegration;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.config.PaymentConfig;
 import com.ning.billing.dbi.DBIProvider;
@@ -66,6 +74,10 @@ import com.ning.billing.invoice.notification.NullInvoiceNotifier;
 import com.ning.billing.jaxrs.json.AccountJson;
 import com.ning.billing.jaxrs.json.BillCycleDayJson;
 import com.ning.billing.jaxrs.json.BundleJsonNoSubscriptions;
+import com.ning.billing.jaxrs.json.InvoiceItemJsonSimple;
+import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
+import com.ning.billing.jaxrs.json.InvoiceJsonWithItems;
+import com.ning.billing.jaxrs.json.PaymentJsonSimple;
 import com.ning.billing.jaxrs.json.PaymentMethodJson;
 import com.ning.billing.jaxrs.json.PaymentMethodJson.PaymentMethodPluginDetailJson;
 import com.ning.billing.jaxrs.json.PaymentMethodJson.PaymentMethodProperties;
@@ -98,6 +110,7 @@ import com.ning.http.client.Response;
 import com.ning.jetty.core.CoreConfig;
 import com.ning.jetty.core.server.HttpServer;
 
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 
 public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
@@ -326,6 +339,10 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         return new PaymentMethodJson(null, accountId, true, PLUGIN_NAME, info);
     }
 
+    //
+    // ACCOUNT UTILITIES
+    //
+
     protected AccountJson createAccountWithDefaultPaymentMethod(final String name, final String key, final String email) throws Exception {
 
         final AccountJson input = createAccount(name, key, email);
@@ -414,6 +431,199 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         return objFromJson;
     }
 
+    //
+    // INVOICE UTILITIES
+    //
+
+    protected AccountJson createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice() throws Exception {
+        final AccountJson accountJson = createAccountWithDefaultPaymentMethod("nohup", "shtergyhwF", "nohup@yahoo.com");
+        assertNotNull(accountJson);
+
+        // Add a bundle, subscription and move the clock to get the first invoice
+        final BundleJsonNoSubscriptions bundleJson = createBundle(accountJson.getAccountId(), "391193");
+        assertNotNull(bundleJson);
+        final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
+        assertNotNull(subscriptionJson);
+        clock.addMonths(1);
+        crappyWaitForLackOfProperSynchonization();
+
+        return accountJson;
+    }
+
+    protected AccountJson createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice() throws Exception {
+        // Create an account with no payment method
+        final AccountJson accountJson = createAccount("eraahahildo", "sheqrgfhwe", "eraahahildo@yahoo.com");
+        assertNotNull(accountJson);
+
+        // Add a bundle, subscription and move the clock to get the first invoice
+        final BundleJsonNoSubscriptions bundleJson = createBundle(accountJson.getAccountId(), "317199");
+        assertNotNull(bundleJson);
+        final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
+        assertNotNull(subscriptionJson);
+        clock.addMonths(1);
+        crappyWaitForLackOfProperSynchonization();
+
+        // No payment will be triggered as the account doesn't have a payment method
+
+        return accountJson;
+    }
+
+    protected InvoiceJsonSimple getInvoice(final String invoiceId) throws IOException {
+        final String uri = JaxrsResource.INVOICES_PATH + "/" + invoiceId;
+        final Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        final String baseJson = response.getResponseBody();
+
+        final InvoiceJsonSimple firstInvoiceJson = mapper.readValue(baseJson, InvoiceJsonSimple.class);
+        assertNotNull(firstInvoiceJson);
+
+        return firstInvoiceJson;
+    }
+
+    protected List<InvoiceJsonSimple> getInvoicesForAccount(final String accountId) throws IOException {
+        final String invoicesURI = JaxrsResource.INVOICES_PATH;
+
+        final Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountId);
+
+        final Response invoicesResponse = doGet(invoicesURI, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(invoicesResponse.getStatusCode(), Status.OK.getStatusCode());
+
+        final String invoicesBaseJson = invoicesResponse.getResponseBody();
+        final List<InvoiceJsonSimple> invoices = mapper.readValue(invoicesBaseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
+        assertNotNull(invoices);
+
+        return invoices;
+    }
+
+    protected List<InvoiceJsonWithItems> getInvoicesWithItemsForAccount(final String accountId) throws IOException {
+        final String invoicesURI = JaxrsResource.INVOICES_PATH;
+
+        final Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountId);
+        queryParams.put(JaxrsResource.QUERY_INVOICE_WITH_ITEMS, "true");
+
+        final Response invoicesResponse = doGet(invoicesURI, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(invoicesResponse.getStatusCode(), Status.OK.getStatusCode());
+
+        final String invoicesBaseJson = invoicesResponse.getResponseBody();
+        final List<InvoiceJsonWithItems> invoices = mapper.readValue(invoicesBaseJson, new TypeReference<List<InvoiceJsonWithItems>>() {});
+        assertNotNull(invoices);
+
+        return invoices;
+    }
+
+    protected InvoiceJsonSimple createDryRunInvoice(final String accountId, final DateTime futureDate) throws IOException {
+        final String uri = JaxrsResource.INVOICES_PATH;
+
+        final Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountId);
+        queryParams.put(JaxrsResource.QUERY_TARGET_DATE, futureDate.toString());
+        queryParams.put(JaxrsResource.QUERY_DRY_RUN, "true");
+
+        final Response response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+        final String baseJson = response.getResponseBody();
+        final InvoiceJsonSimple futureInvoice = mapper.readValue(baseJson, InvoiceJsonSimple.class);
+        assertNotNull(futureInvoice);
+
+        return futureInvoice;
+    }
+
+    protected void createInvoice(final String accountId, final DateTime futureDate) throws IOException {
+        final String uri = JaxrsResource.INVOICES_PATH;
+
+        final Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountId);
+        queryParams.put(JaxrsResource.QUERY_TARGET_DATE, futureDate.toString());
+
+        final Response response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        final String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+    }
+
+    protected void adjustInvoiceItem(final String accountId, final String invoiceId, final String invoiceItemId,
+                                     @Nullable final DateTime requestedDate, @Nullable final BigDecimal amount, @Nullable final Currency currency) throws IOException {
+        final String uri = JaxrsResource.INVOICES_PATH + "/" + invoiceId;
+
+        final Map<String, String> queryParams = new HashMap<String, String>();
+        if (requestedDate != null) {
+            queryParams.put(JaxrsResource.QUERY_REQUESTED_DT, requestedDate.toDateTimeISO().toString());
+        }
+
+        final InvoiceItemJsonSimple adjustment = new InvoiceItemJsonSimple(invoiceItemId, null, accountId, null, null, null, null,
+                                                                           null, null, null, amount, currency, null);
+        final String adjustmentJson = mapper.writeValueAsString(adjustment);
+        final Response response = doPost(uri, adjustmentJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+    }
+
+    //
+    // PAYMENT UTILITIES
+    //
+
+    protected PaymentMethodJson getPaymentMethod(final String paymentMethodId) throws IOException {
+        final String paymentMethodURI = JaxrsResource.PAYMENT_METHODS_PATH + "/" + paymentMethodId;
+        final Response paymentMethodResponse = doGet(paymentMethodURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(paymentMethodResponse.getStatusCode(), Status.OK.getStatusCode());
+
+        final PaymentMethodJson paymentMethodJson = mapper.readValue(paymentMethodResponse.getResponseBody(), PaymentMethodJson.class);
+        assertNotNull(paymentMethodJson);
+
+        return paymentMethodJson;
+    }
+
+    protected List<PaymentJsonSimple> getPaymentsForAccount(final String accountId) throws IOException {
+        final String paymentsURI = JaxrsResource.ACCOUNTS_PATH + "/" + accountId + "/" + JaxrsResource.PAYMENTS;
+        final Response paymentsResponse = doGet(paymentsURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(paymentsResponse.getStatusCode(), Status.OK.getStatusCode());
+        final String paymentsBaseJson = paymentsResponse.getResponseBody();
+
+        final List<PaymentJsonSimple> paymentJsonSimples = mapper.readValue(paymentsBaseJson, new TypeReference<List<PaymentJsonSimple>>() {});
+        assertNotNull(paymentJsonSimples);
+
+        return paymentJsonSimples;
+    }
+
+    protected List<PaymentJsonSimple> getPaymentsForInvoice(final String invoiceId) throws IOException {
+        final String uri = JaxrsResource.INVOICES_PATH + "/" + invoiceId + "/" + JaxrsResource.PAYMENTS;
+        final Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        final String baseJson = response.getResponseBody();
+        final List<PaymentJsonSimple> objFromJson = mapper.readValue(baseJson, new TypeReference<List<PaymentJsonSimple>>() {});
+        assertNotNull(objFromJson);
+
+        return objFromJson;
+    }
+
+    protected List<PaymentJsonSimple> createInstaPayment(final AccountJson accountJson, final InvoiceJsonSimple invoice) throws IOException {
+        final PaymentJsonSimple payment = new PaymentJsonSimple(invoice.getAmount(), BigDecimal.ZERO, accountJson.getAccountId(),
+                                                                invoice.getInvoiceId(), null, null, null, null, 0, null, null, null, null, null, null, null);
+        final String postJson = mapper.writeValueAsString(payment);
+
+        final String uri = JaxrsResource.INVOICES_PATH + "/" + invoice.getInvoiceId() + "/" + JaxrsResource.PAYMENTS;
+        doPost(uri, postJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+
+        return getPaymentsForInvoice(invoice.getInvoiceId());
+    }
+
+    protected List<PaymentJsonSimple> createExternalPayment(final AccountJson accountJson, final String invoiceId, final BigDecimal paidAmount) throws IOException {
+        final PaymentJsonSimple payment = new PaymentJsonSimple(paidAmount, BigDecimal.ZERO, accountJson.getAccountId(),
+                                                                invoiceId, null, null, null, null, 0,
+                                                                null, null, null, null, null, null, null);
+        final String postJson = mapper.writeValueAsString(payment);
+
+        final String paymentURI = JaxrsResource.INVOICES_PATH + "/" + invoiceId + "/" + JaxrsResource.PAYMENTS;
+        final Response paymentResponse = doPost(paymentURI, postJson, ImmutableMap.<String, String>of("externalPayment", "true"), DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(paymentResponse.getStatusCode(), Status.CREATED.getStatusCode());
+
+        return getPaymentsForInvoice(invoiceId);
+    }
+
     protected Map<String, String> getQueryParamsForCallCompletion(final String timeoutSec) {
         final Map<String, String> queryParams = new HashMap<String, String>();
         queryParams.put(JaxrsResource.QUERY_CALL_COMPLETION, "true");
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java b/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
index 2b16d3f..a10062e 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
@@ -92,7 +92,7 @@ public class TestPayment extends TestJaxrsBase {
 
         // Issue the refund
 
-        final RefundJson refundJson = new RefundJson(null, paymentId, paymentAmount, false, null, null, null);
+        final RefundJson refundJson = new RefundJson(null, paymentId, paymentAmount, false, null, null, null, null);
         baseJson = mapper.writeValueAsString(refundJson);
         response = doPost(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
         assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());