killbill-aplcache

Changes

Details

diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index 3740a57..8c3041a 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -227,7 +227,7 @@ public class TestAnalyticsService extends AnalyticsTestSuiteWithEmbeddedDB {
                 INVOICE_AMOUNT, ACCOUNT_CURRENCY);
         invoice.addInvoiceItem(invoiceItem);
 
-        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), true, context);
         final List<Invoice> invoices = invoiceDao.getInvoicesByAccount(account.getId());
         Assert.assertEquals(invoices.size(), 1);
         Assert.assertEquals(invoices.get(0).getInvoiceItems().size(), 1);
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index 8e000da..189d61e 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -203,6 +203,7 @@ 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."),
+    REFUND_AMOUNT_DONT_MATCH_ITEMS_TO_ADJUST(4907, "You can't specify a refund amount of %s that doesn't match the invoice items amount of %s."),
 
     /*
      *
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 548bfd5..63cd82a 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
@@ -28,8 +28,8 @@ public enum InvoiceItemType {
     // Credit adjustment, either at the account level (on its own invoice) or against an existing invoice
     // (invoice level adjustment)
     CREDIT_ADJ,
-    // Invoice item adjustment
+    // Invoice item adjustment (by itself or triggered by a refund)
     ITEM_ADJ,
-    // Refund adjustment (against a posted payment)
+    // Refund adjustment (against a posted payment), used when adjusting invoices
     REFUND_ADJ
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
index a95c98e..fa0e738 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
@@ -18,6 +18,7 @@ package com.ning.billing.invoice.api;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -26,6 +27,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.util.callcontext.CallContext;
 
 public interface InvoicePaymentApi {
+
     /**
      * @param accountId id of the account
      * @return All invoices, including migrated invoices
@@ -42,7 +44,21 @@ public interface InvoicePaymentApi {
 
     public void notifyOfPayment(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentId, DateTime paymentDate, CallContext context);
 
-    public InvoicePayment createRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, UUID paymentCookieId, CallContext context) throws InvoiceApiException;
+    /**
+     * Create a refund.
+     *
+     *
+     * @param paymentId                 payment associated with that refund
+     * @param amount                    amount to refund
+     * @param isInvoiceAdjusted         whether the refund should trigger an invoice or invoice item adjustment
+     * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
+     * @param paymentCookieId           payment cookie id
+     * @param context                   the call context
+     * @return the created invoice payment object associated with this refund
+     * @throws InvoiceApiException
+     */
+    public InvoicePayment createRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts,
+                                       UUID paymentCookieId, CallContext context) throws InvoiceApiException;
 
     public InvoicePayment createChargeback(UUID invoicePaymentId, BigDecimal amount, CallContext context) throws InvoiceApiException;
 
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 38543f2..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
@@ -22,8 +22,6 @@ import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
 
-import javax.annotation.Nullable;
-
 import org.joda.time.LocalDate;
 
 import com.ning.billing.account.api.AccountApiException;
@@ -160,20 +158,33 @@ public interface InvoiceUserApi {
                                               Currency currency, CallContext context) throws InvoiceApiException;
 
     /**
-     * Adjust a given invoice item.
+     * 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
-     * @param amount        the adjustment amount. Pass null to adjust for the full amount of the original item
-     * @param currency      adjustment currency. Pass null to use the original currency
+     * @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,
-                                                   @Nullable BigDecimal amount, @Nullable Currency currency, CallContext context) throws InvoiceApiException;
+                                                   BigDecimal amount, Currency currency, CallContext context) throws InvoiceApiException;
 
     /**
      * Retrieve the invoice formatted in HTML.
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index dc7488c..bf38550 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -18,6 +18,7 @@ package com.ning.billing.payment.api;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
@@ -35,7 +36,58 @@ public interface PaymentApi {
     public Refund getRefund(final UUID refundId)
             throws PaymentApiException;
 
-    public Refund createRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final boolean isAdjusted, final CallContext context)
+    /**
+     * Create a refund for a given payment. The associated invoice is not adjusted.
+     *
+     * @param account      account to refund
+     * @param paymentId    payment associated with that refund
+     * @param refundAmount amount to refund
+     * @param context      the call context
+     * @return the created Refund
+     * @throws PaymentApiException
+     */
+    public Refund createRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final CallContext context)
+            throws PaymentApiException;
+
+    /**
+     * Create a refund for a given payment. The associated invoice is adjusted.
+     *
+     * @param account      account to refund
+     * @param paymentId    payment associated with that refund
+     * @param refundAmount amount to refund
+     * @param context      the call context
+     * @return the created Refund
+     * @throws PaymentApiException
+     */
+    public Refund createRefundWithAdjustment(final Account account, final UUID paymentId, final BigDecimal refundAmount, final CallContext context)
+            throws PaymentApiException;
+
+    /**
+     * Create a refund for a given payment. The specified invoice items are fully adjusted.
+     * The refund amount will be the sum of all invoice items amounts.
+     *
+     * @param account        account to refund
+     * @param paymentId      payment associated with that refund
+     * @param invoiceItemIds invoice item ids to adjust
+     * @param context        the call context
+     * @return the created Refund
+     * @throws PaymentApiException
+     */
+    public Refund createRefundWithItemsAdjustments(final Account account, final UUID paymentId, final Set<UUID> invoiceItemIds, final CallContext context)
+            throws PaymentApiException;
+
+    /**
+     * Create a refund for a given payment. The specified invoice items are partially adjusted.
+     * The refund amount will be the sum of all adjustments.
+     *
+     * @param account                   account to refund
+     * @param paymentId                 payment associated with that refund
+     * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
+     * @param context                   the call context
+     * @return the created Refund
+     * @throws PaymentApiException
+     */
+    public Refund createRefundWithItemsAdjustments(final Account account, final UUID paymentId, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final CallContext context)
             throws PaymentApiException;
 
     public List<Refund> getAccountRefunds(final Account account)
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixModule.java
index 96fcca7..ed59b21 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixModule.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/BeatrixModule.java
@@ -34,6 +34,7 @@ import com.ning.billing.analytics.setup.AnalyticsModule;
 import com.ning.billing.beatrix.integration.overdue.IntegrationTestOverdueModule;
 import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
 import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.beatrix.util.InvoiceChecker;
 import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.config.PaymentConfig;
@@ -105,6 +106,8 @@ public class BeatrixModule extends AbstractModule {
         install(new PaymentPluginMockModule());
         install(new DefaultJunctionModule());
         install(new IntegrationTestOverdueModule());
+
+        bind(InvoiceChecker.class).asEagerSingleton();
     }
 
     private static final class PaymentPluginMockModule extends PaymentModule {
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java
index cad4fd1..8728ac9 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestAnalytics.java
@@ -67,6 +67,9 @@ import static org.testng.Assert.assertTrue;
 
 @Guice(modules = BeatrixModule.class)
 public class TestAnalytics extends TestIntegrationBase {
+
+    private Account account;
+
     private Plan subscriptionPlan;
 
     @BeforeMethod(groups = "slow")
@@ -119,6 +122,11 @@ public class TestAnalytics extends TestIntegrationBase {
         overdueWrapperFactory.setOverdueConfig(config);
 
         busService.getBus().register(analyticsListener);
+
+        final DateTime initialDate = new DateTime(2012, 8, 1, 0, 15, 42, 0, testTimeZone);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        account = verifyAccountCreation(initialDate.plusDays(30).getDayOfMonth());
     }
 
     @AfterMethod(groups = "slow")
@@ -130,8 +138,6 @@ public class TestAnalytics extends TestIntegrationBase {
 
     @Test(groups = "slow")
     public void testCreateAndCancelSubscription() throws Exception {
-        // Create an account
-        final Account account = verifyAccountCreation();
 
         // Create a bundle
         final SubscriptionBundle bundle = verifyFirstBundle(account);
@@ -140,10 +146,10 @@ public class TestAnalytics extends TestIntegrationBase {
         Subscription subscription = verifyFirstSubscription(account, bundle);
 
         // Move after trial
-        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
         busHandler.pushExpectedEvent(TestApiListener.NextEvent.PHASE);
         busHandler.pushExpectedEvent(TestApiListener.NextEvent.INVOICE);
         busHandler.pushExpectedEvent(TestApiListener.NextEvent.PAYMENT);
+        clock.addDeltaFromReality(AT_LEAST_ONE_MONTH_MS);
         Assert.assertTrue(busHandler.isCompleted(DELAY));
 
         // Check BST - nothing should have changed
@@ -169,8 +175,6 @@ public class TestAnalytics extends TestIntegrationBase {
 
     @Test(groups = "slow")
     public void testCreateAndUpdateSubscription() throws Exception {
-        // Create an account
-        final Account account = verifyAccountCreation();
 
         // Update some fields
         verifyAccountUpdate(account);
@@ -190,10 +194,8 @@ public class TestAnalytics extends TestIntegrationBase {
 
     @Test(groups = "slow")
     public void testOverdue() throws Exception {
-        paymentPlugin.makeAllInvoicesFailWithError(true);
 
-        // Create an account
-        final Account account = verifyAccountCreation();
+        paymentPlugin.makeAllInvoicesFailWithError(true);
 
         // Create a bundle
         final SubscriptionBundle bundle = verifyFirstBundle(account);
@@ -314,8 +316,9 @@ public class TestAnalytics extends TestIntegrationBase {
         Assert.assertEquals(analyticsUserApi.getOverdueStatusesForBundle(bundle.getKey()).get(2).getAccountKey(), account.getExternalKey());
     }
 
-    private Account verifyAccountCreation() throws Exception {
-        final AccountData accountData = getAccountData(1);
+    private Account verifyAccountCreation(int billCycleDay) throws Exception {
+
+        final AccountData accountData = getAccountData(billCycleDay);
 
         // Verify BAC is empty
         Assert.assertNull(analyticsUserApi.getAccountByKey(accountData.getExternalKey()));
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
index cdd35dd..de00151 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
@@ -21,6 +21,7 @@ import static org.testng.Assert.assertTrue;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.UUID;
 
 import junit.framework.Assert;
 
@@ -29,8 +30,10 @@ import org.joda.time.LocalDate;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
+import com.google.common.collect.ImmutableList;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedItemCheck;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
@@ -39,6 +42,7 @@ import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
 
 @Guice(modules = {BeatrixModule.class})
 public class TestBundleTransfer extends TestIntegrationBase {
@@ -99,8 +103,8 @@ public class TestBundleTransfer extends TestIntegrationBase {
         final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
         assertEquals(invoiceItems.size(), 1);
         InvoiceItem theItem = invoiceItems.get(0);
-        Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,05,11)) == 0);
-        Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2013,05,11)) == 0);
+        Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,5,11)) == 0);
+        Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2013,5,11)) == 0);
         Assert.assertTrue(theItem.getAmount().compareTo(new BigDecimal("2399.9500")) == 0);
     }
 
@@ -159,8 +163,84 @@ public class TestBundleTransfer extends TestIntegrationBase {
         final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
         assertEquals(invoiceItems.size(), 1);
         InvoiceItem theItem = invoiceItems.get(0);
-        Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,05,03)) == 0);
-        Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2012,05,15)) == 0);
+        Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,5,3)) == 0);
+        Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2012,5,15)) == 0);
         Assert.assertTrue(theItem.getAmount().compareTo(new BigDecimal("99.98")) == 0);
     }
+
+    @Test(groups = "slow")
+    public void testBundleTransferWithBPMonthlyOnlyWIthCancellationImm() throws Exception {
+
+        final Account account = createAccountWithPaymentMethod(getAccountData(9));
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+
+        final DateTime initialDate = new DateTime(2012, 4, 1, 0, 15, 42, 0, testTimeZone);
+
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        final SubscriptionBundle bundle = entitlementUserApi.createBundleForAccount(account.getId(), "mycutebundle", context);
+
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.MONTHLY;
+        final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE);
+        final PlanPhaseSpecifier bpPlanPhaseSpecifier = new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null);
+        final SubscriptionData bpSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+                                                                                                                       bpPlanPhaseSpecifier,
+                                                                                                                       null,
+                                                                                                                       context));
+        assertNotNull(bpSubscription);
+        assertTrue(busHandler.isCompleted(DELAY));
+        assertListenerStatus();
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId()).size(), 1);
+
+        assertEquals(entitlementUserApi.getSubscriptionFromId(bpSubscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.MONTHLY);
+
+        // Move out of trials for interesting invoices adjustments
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        clock.addDays(32);
+        assertTrue(busHandler.isCompleted(DELAY));
+        assertListenerStatus();
+
+        // BUNDLE TRANSFER
+        final Account newAccount = createAccountWithPaymentMethod(getAccountData(15));
+
+        busHandler.pushExpectedEvent(NextEvent.CANCEL);
+        busHandler.pushExpectedEvent(NextEvent.TRANSFER);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.INVOICE);
+        busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+        transferApi.transferBundle(account.getId(), newAccount.getId(), "mycutebundle", clock.getUTCNow(), false, true, context);
+        assertTrue(busHandler.isCompleted(DELAY));
+        assertListenerStatus();
+
+        List<Invoice> invoices =invoiceUserApi.getInvoicesByAccount(account.getId());
+        assertEquals(invoices.size(), 3);
+
+
+        // CHECK OLD ACCOUNTS ITEMS
+        ImmutableList<ExpectedItemCheck> toBeChecked = ImmutableList.<ExpectedItemCheck>of(
+                new ExpectedItemCheck(new LocalDate(2012,5,1), new LocalDate(2012,5,9), InvoiceItemType.RECURRING, new BigDecimal("66.66")),
+                new ExpectedItemCheck(new LocalDate(2012,5,1), new LocalDate(2012,5,9), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-66.66")),
+                new ExpectedItemCheck(new LocalDate(2012,5,3), new LocalDate(2012,5,3), InvoiceItemType.CBA_ADJ, new BigDecimal("66.66")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), toBeChecked);
+
+        toBeChecked = ImmutableList.<ExpectedItemCheck>of(
+                new ExpectedItemCheck(new LocalDate(2012,5,1), new LocalDate(2012,5,3), InvoiceItemType.RECURRING, new BigDecimal("16.67")),
+                new ExpectedItemCheck(new LocalDate(2012,5,3), new LocalDate(2012,5,3), InvoiceItemType.CBA_ADJ, new BigDecimal("-16.67")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), toBeChecked);
+
+        // CHECK NEW ACCOUNT ITEMS
+        invoices =invoiceUserApi.getInvoicesByAccount(newAccount.getId());
+        assertEquals(invoices.size(), 1);
+
+        toBeChecked = ImmutableList.<ExpectedItemCheck>of(
+                new ExpectedItemCheck(new LocalDate(2012,5,3), new LocalDate(2012,5,15), InvoiceItemType.RECURRING, new BigDecimal("99.98")));
+        invoiceChecker.checkInvoice(invoices.get(0).getId(), toBeChecked);
+    }
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestEntitlement.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestEntitlement.java
index 57220ce..7d322e0 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestEntitlement.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestEntitlement.java
@@ -16,12 +16,17 @@
 
 package com.ning.billing.beatrix.integration;
 
+import java.math.BigDecimal;
+import java.util.List;
+
 import org.joda.time.LocalDate;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
+import com.google.common.collect.ImmutableList;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedItemCheck;
 import com.ning.billing.catalog.api.ActionPolicy;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
@@ -29,6 +34,8 @@ import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItemType;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
@@ -68,21 +75,69 @@ public class TestEntitlement extends TestIntegrationBase {
         assertEquals(entitlementUserApi.getSubscriptionFromId(bpSubscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
 
         // Move out of trials for interesting invoices adjustments
-        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
         clock.addDays(40);
         assertTrue(busHandler.isCompleted(DELAY));
         assertListenerStatus();
 
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId());
+        assertEquals(invoices.size(), 2);
+        ImmutableList<ExpectedItemCheck> toBeChecked = ImmutableList.<ExpectedItemCheck>of(
+                new ExpectedItemCheck(new LocalDate(2012,5,1), new LocalDate(2013,5,1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), toBeChecked);
+
         //
         // FORCE AN IMMEDIATE CHANGE OF THE BILLING PERIOD
         //
+        busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
         assertTrue(bpSubscription.changePlanWithPolicy(productName, BillingPeriod.MONTHLY, planSetName, clock.getUTCNow(), ActionPolicy.IMMEDIATE, context));
         assertEquals(entitlementUserApi.getSubscriptionFromId(bpSubscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.MONTHLY);
+        assertTrue(busHandler.isCompleted(DELAY));
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId());
+        assertEquals(invoices.size(), 3);
+        toBeChecked = ImmutableList.<ExpectedItemCheck>of(
+                new ExpectedItemCheck(new LocalDate(2012,5,1), new LocalDate(2013,5,1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                new ExpectedItemCheck(new LocalDate(2012,5,1), new LocalDate(2013,5,1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2399.95")),
+                new ExpectedItemCheck(new LocalDate(2012,5,11), new LocalDate(2012,5,11), InvoiceItemType.CBA_ADJ, new BigDecimal("2399.95")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), toBeChecked);
+
+        toBeChecked = ImmutableList.<ExpectedItemCheck>of(
+                new ExpectedItemCheck(new LocalDate(2012,5,1), new LocalDate(2012,5,11), InvoiceItemType.RECURRING, new BigDecimal("65.76")),
+                new ExpectedItemCheck(new LocalDate(2012,5,11), new LocalDate(2012,6,1), InvoiceItemType.RECURRING, new BigDecimal("169.32")),
+                new ExpectedItemCheck(new LocalDate(2012,5,11), new LocalDate(2012,5,11), InvoiceItemType.CBA_ADJ, new BigDecimal("-235.08")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), toBeChecked);
+
+
 
         //
         // FORCE ANOTHER CHANGE
         //
+        busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.INVOICE);
         assertTrue(bpSubscription.changePlanWithPolicy(productName, BillingPeriod.ANNUAL, planSetName, clock.getUTCNow(), ActionPolicy.IMMEDIATE, context));
         assertEquals(entitlementUserApi.getSubscriptionFromId(bpSubscription.getId()).getCurrentPlan().getBillingPeriod(), BillingPeriod.ANNUAL);
+        assertTrue(busHandler.isCompleted(DELAY));
+        assertListenerStatus();
+
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId());
+        assertEquals(invoices.size(), 4);
+
+
+        toBeChecked = ImmutableList.<ExpectedItemCheck>of(
+                new ExpectedItemCheck(new LocalDate(2012,5,1), new LocalDate(2012,5,11), InvoiceItemType.RECURRING, new BigDecimal("65.76")),
+                new ExpectedItemCheck(new LocalDate(2012,5,11), new LocalDate(2012,6,1), InvoiceItemType.RECURRING, new BigDecimal("169.32")),
+                new ExpectedItemCheck(new LocalDate(2012,5,11), new LocalDate(2012,5,11), InvoiceItemType.CBA_ADJ, new BigDecimal("-235.08")),
+                new ExpectedItemCheck(new LocalDate(2012,5,11), new LocalDate(2012,6,1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")),
+                new ExpectedItemCheck(new LocalDate(2012,5,11), new LocalDate(2012,5,11), InvoiceItemType.CBA_ADJ, new BigDecimal("169.32")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), toBeChecked);
+
+
+        toBeChecked = ImmutableList.<ExpectedItemCheck>of(
+                new ExpectedItemCheck(new LocalDate(2012,5,11), new LocalDate(2012,6,1), InvoiceItemType.RECURRING, new BigDecimal("137.76")),
+                new ExpectedItemCheck(new LocalDate(2012,5,11), new LocalDate(2012,5,11), InvoiceItemType.CBA_ADJ, new BigDecimal("-137.76")));
+        invoiceChecker.checkInvoice(invoices.get(3).getId(), toBeChecked);
+
+
     }
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index 36854d4..9bb0ed4 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -92,11 +92,14 @@ public class TestIntegration extends TestIntegrationBase {
         //
         busHandler.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.INVOICE);
         bpSubscription.cancel(clock.getUTCNow(), false, context);
+
+        //Thread.sleep(10000000);
+
         assertTrue(busHandler.isCompleted(DELAY));
         assertListenerStatus();
 
         final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId());
-        assertEquals(invoices.size(), 3);
+        assertEquals(invoices.size(), 2);
         // The first invoice is for the trial BP
         assertEquals(invoices.get(0).getNumberOfItems(), 1);
         assertEquals(invoices.get(0).getInvoiceItems().get(0).getStartDate().compareTo(today), 0);
@@ -120,8 +123,6 @@ public class TestIntegration extends TestIntegrationBase {
                 assertEquals(item.getAmount().compareTo(new BigDecimal("399.9500")), 0);
             }
         }
-        // Null invoice
-        assertEquals(invoices.get(2).getNumberOfItems(), 0);
     }
 
     @Test(groups = "slow")
@@ -520,9 +521,11 @@ public class TestIntegration extends TestIntegrationBase {
         //waitForDebug();
 
         assertTrue(busHandler.isCompleted(DELAY));
-
         startDate = chargeThroughDate;
-        DateTime endDate = chargeThroughDate.plusMonths(1);
+
+        // Resync latest with real CTD so test does not drift
+        DateTime realChargeThroughDate = entitlementUserApi.getSubscriptionFromId(subscription.getId()).getChargedThroughDate();
+        DateTime endDate = realChargeThroughDate;
         price = subscription.getCurrentPhase().getRecurringPrice().getPrice(Currency.USD);
         invoiceItemCount += 1;
         verifyTestResult(accountId, subscription.getId(), startDate, endDate, price, endDate, invoiceItemCount);
@@ -558,7 +561,8 @@ public class TestIntegration extends TestIntegrationBase {
 
         // MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
         busHandler.pushExpectedEvent(NextEvent.CANCEL);
-        final Interval it = new Interval(clock.getUTCNow(), endDate);
+        realChargeThroughDate = entitlementUserApi.getSubscriptionFromId(subscription.getId()).getChargedThroughDate();
+        final Interval it = new Interval(clock.getUTCNow(), realChargeThroughDate.plusSeconds(5));
         clock.addDeltaFromReality(it.toDurationMillis());
         assertTrue(busHandler.isCompleted(DELAY));
 
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index 4aa10d8..2339a8f 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -49,6 +49,7 @@ import com.ning.billing.api.TestApiListener;
 import com.ning.billing.api.TestListenerStatus;
 import com.ning.billing.beatrix.BeatrixTestSuiteWithEmbeddedDB;
 import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.beatrix.util.InvoiceChecker;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.EntitlementService;
@@ -97,7 +98,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
     protected static final BigDecimal THIRTY_ONE = new BigDecimal("31.0000").setScale(NUMBER_OF_DECIMALS);
 
     protected static final Logger log = LoggerFactory.getLogger(TestIntegration.class);
-    protected static long AT_LEAST_ONE_MONTH_MS = 31L * 24L * 3600L * 1000L + 2000L;
+    protected static long AT_LEAST_ONE_MONTH_MS = 32L * 24L * 3600L * 1000L;
 
     protected static final long DELAY = 5000;
 
@@ -161,6 +162,9 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
     @Inject
     protected AnalyticsListener analyticsListener;
 
+    @Inject
+    protected InvoiceChecker invoiceChecker;
+
     protected TestApiListener busHandler;
 
     private boolean isListenerFailed;
@@ -251,8 +255,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
         log.info("Checking CTD: " + ctd.toString() + "; clock is " + clock.getUTCNow().toString());
         // Either the ctd is today (start of the trial) or the clock is strictly before the CTD
         assertTrue(clock.getUTCToday().compareTo(new LocalDate(ctd)) == 0 || clock.getUTCNow().isBefore(ctd));
-        // The CTD is rounded too
-        assertTrue(ctd.compareTo(new DateTime(chargeThroughDate.getYear(), chargeThroughDate.getMonthOfYear(), chargeThroughDate.getDayOfMonth(), 0, 0, testTimeZone)) == 0);
+        assertTrue(ctd.toDateTime(testTimeZone).toLocalDate().compareTo(new LocalDate(chargeThroughDate.getYear(), chargeThroughDate.getMonthOfYear(), chargeThroughDate.getDayOfMonth())) == 0);
     }
 
     protected SubscriptionData subscriptionDataFromSubscription(final Subscription sub) {
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
new file mode 100644
index 0000000..dca65a5
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2011 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.beatrix.util;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+
+import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+
+public class InvoiceChecker {
+
+    private final static Logger log = LoggerFactory.getLogger(InvoiceChecker.class);
+
+    private final InvoiceUserApi invoiceUserApi;
+
+    @Inject
+    public InvoiceChecker(final InvoiceUserApi invoiceUserApi) {
+       this.invoiceUserApi = invoiceUserApi;
+    }
+
+    public void checkInvoice(final UUID invoiceId, final List<ExpectedItemCheck> expected) {
+        final Invoice invoice =invoiceUserApi.getInvoice(invoiceId);
+        Assert.assertNotNull(invoice);
+
+        final List<InvoiceItem> actual = invoice.getInvoiceItems();
+        Assert.assertEquals(expected.size(), actual.size());
+        for (ExpectedItemCheck cur : expected) {
+            boolean found = false;
+            for (InvoiceItem in : actual) {
+                // Match first on type and start date
+                if (in.getInvoiceItemType() != cur.getType() || (in.getStartDate().compareTo(cur.getStartDate()) != 0)) {
+                    continue;
+                }
+                if (in.getAmount().compareTo(cur.getAmount()) != 0) {
+                    log.info(String.format("Found item type = %s and startDate = %s but amount differ (actual = %s, expected = %s) ",
+                            cur.getType(), cur.getStartDate(), in.getAmount(), cur.getAmount()));
+                    continue;
+                }
+
+                if ((cur.getEndDate() == null && in.getEndDate() == null) ||
+                        (cur.getEndDate() != null && in.getEndDate() != null && cur.getEndDate().compareTo(in.getEndDate()) == 0)) {
+                    found = true;
+                    break;
+                }
+                log.info(String.format("Found item type = %s and startDate = %s, amount = %s but endDate differ (actual = %s, expected = %s) ",
+                        cur.getType(), cur.getStartDate(), in.getAmount(), in.getEndDate(), cur.getEndDate()));
+            }
+            if (!found) {
+                Assert.fail(String.format("Failed to find invoice item type = %s and startDate = %s, amount = %s, endDate = %s",
+                        cur.getType(), cur.getStartDate(), cur.getAmount(), cur.getEndDate()));
+            }
+        }
+    }
+
+    public static class ExpectedItemCheck {
+
+        private final LocalDate startDate;
+        private final LocalDate endDate;
+        private final InvoiceItemType type;
+        private final BigDecimal Amount;
+
+        public ExpectedItemCheck(final LocalDate startDate, final LocalDate endDate,
+                final InvoiceItemType type, final BigDecimal amount) {
+            this.startDate = startDate;
+            this.endDate = endDate;
+            this.type = type;
+            Amount = amount;
+        }
+
+        public LocalDate getStartDate() {
+            return startDate;
+        }
+        public LocalDate getEndDate() {
+            return endDate;
+        }
+        public InvoiceItemType getType() {
+            return type;
+        }
+        public BigDecimal getAmount() {
+            return Amount;
+        }
+    }
+
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java
index 455fe64..d2cd173 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java
@@ -705,6 +705,13 @@ public class AuditedEntitlementDao implements EntitlementDao {
         final List<EntityAudit> audits = new ArrayList<EntityAudit>();
         Long recordId;
         final SubscriptionBundleData bundleData = bundleTransferData.getData();
+
+        final SubscriptionBundle existingBundle = transBundleDao.getBundleFromAccountAndKey(bundleData.getAccountId().toString(), bundleData.getKey());
+        if (existingBundle != null) {
+            log.error(String.format("Attempted to create a bundle for account %s and key %s that already existed, skip...", bundleData.getAccountId().toString(), bundleData.getKey()));
+            return;
+        }
+
         for (final SubscriptionMigrationData curSubscription : bundleTransferData.getSubscriptions()) {
             final SubscriptionData subData = curSubscription.getData();
             for (final EntitlementEvent curEvent : curSubscription.getInitialEvents()) {
diff --git a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.sql.stg b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.sql.stg
index 09bf42d..f10428d 100644
--- a/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.sql.stg
+++ b/entitlement/src/main/resources/com/ning/billing/entitlement/engine/dao/EntitlementEventSqlDao.sql.stg
@@ -90,8 +90,6 @@ getFutureActiveEventForSubscription() ::= <<
       and effective_date > :now
     order by
       effective_date asc
-      , created_date asc
-      , requested_date asc
       , record_id asc
     ;
 >> 
@@ -103,8 +101,6 @@ getEventsForSubscription() ::= <<
       subscription_id = :subscriptionId
     order by
       effective_date asc
-      , created_date asc
-      , requested_date asc
       , record_id asc
     ;      
 >>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index effca13..0bd3a5c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -19,6 +19,7 @@ package com.ning.billing.invoice.api.invoice;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
@@ -108,7 +109,8 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
 
     @Override
     public InvoicePayment createRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted,
-                                       final UUID paymentCookieId, final CallContext context) throws InvoiceApiException {
+                                       final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final UUID paymentCookieId,
+                                       final CallContext context) throws InvoiceApiException {
 
         return invoicePaymentWithException.executeAndThrow(new WithInvoiceApiExceptionCallback<InvoicePayment>() {
 
@@ -117,7 +119,7 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
                 if (amount.compareTo(BigDecimal.ZERO) <= 0) {
                     throw new InvoiceApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL);
                 }
-                return dao.createRefund(paymentId, amount, isInvoiceAdjusted, paymentCookieId, context);
+                return dao.createRefund(paymentId, amount, isInvoiceAdjusted, invoiceItemIdsWithAmounts, paymentCookieId, context);
             }
         });
     }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java
index f007eac..f45509c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java
@@ -70,7 +70,7 @@ public class DefaultInvoiceMigrationApi implements InvoiceMigrationApi {
         final InvoiceItem migrationInvoiceItem = new MigrationInvoiceItem(migrationInvoice.getId(), accountId, targetDate, balance, currency);
         migrationInvoice.addInvoiceItem(migrationInvoiceItem);
 
-        dao.create(migrationInvoice, account.getBillCycleDay().getDayOfMonthUTC(), context);
+        dao.create(migrationInvoice, account.getBillCycleDay().getDayOfMonthUTC(), true, context);
         return migrationInvoice.getId();
     }
 }
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 2fc3623..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
@@ -158,6 +158,11 @@ 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 {
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 0de61e7..4fe738e 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
@@ -20,6 +20,7 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -57,9 +58,11 @@ import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.dao.TableName;
 import com.ning.billing.util.tag.ControlTagType;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap.Builder;
 import com.google.inject.Inject;
 
 public class DefaultInvoiceDao implements InvoiceDao {
@@ -157,7 +160,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public void create(final Invoice invoice, final int billCycleDayUTC, final CallContext context) {
+    public void create(final Invoice invoice, final int billCycleDayUTC, final boolean isRealInvoice, final CallContext context) {
         invoiceSqlDao.inTransaction(new Transaction<Void, InvoiceSqlDao>() {
             @Override
             public Void inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
@@ -165,9 +168,14 @@ public class DefaultInvoiceDao implements InvoiceDao {
                 if (currentInvoice == null) {
                     final List<EntityAudit> audits = new ArrayList<EntityAudit>();
 
-                    transactional.create(invoice, context);
-                    final Long recordId = transactional.getRecordId(invoice.getId().toString());
-                    audits.add(new EntityAudit(TableName.INVOICES, recordId, ChangeType.INSERT));
+                    // We only want to insert that invoice if there are real invoiceItems associated to it-- if not, this is just
+                    // a shell invoice and we only need to insert the invoiceItems-- for the already existing invoices
+
+                    if (isRealInvoice) {
+                        transactional.create(invoice, context);
+                        final Long recordId = transactional.getRecordId(invoice.getId().toString());
+                        audits.add(new EntityAudit(TableName.INVOICES, recordId, ChangeType.INSERT));
+                    }
 
                     List<Long> recordIdList;
 
@@ -301,27 +309,31 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public InvoicePayment createRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted, final UUID paymentCookieId, final CallContext context)
+    public InvoicePayment createRefund(final UUID paymentId, final BigDecimal requestedRefundAmount, final boolean isInvoiceAdjusted,
+                                       final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts, final UUID paymentCookieId,
+                                       final CallContext context)
             throws InvoiceApiException {
         return invoicePaymentSqlDao.inTransaction(new Transaction<InvoicePayment, InvoicePaymentSqlDao>() {
             @Override
             public InvoicePayment inTransaction(final InvoicePaymentSqlDao transactional, final TransactionStatus status) throws Exception {
 
+                final InvoiceSqlDao transInvoiceDao = transactional.become(InvoiceSqlDao.class);
+
                 final InvoicePayment payment = transactional.getByPaymentId(paymentId.toString());
                 if (payment == null) {
                     throw new InvoiceApiException(ErrorCode.INVOICE_PAYMENT_BY_ATTEMPT_NOT_FOUND, paymentId);
                 }
-                final BigDecimal maxRefundAmount = payment.getAmount() == null ? BigDecimal.ZERO : payment.getAmount();
-                final BigDecimal requestedPositiveAmount = amount == null ? maxRefundAmount : amount;
-                // This check is good but not enough, we need to also take into account previous refunds
-                // (But that should have been checked in the payment call already)
-                if (requestedPositiveAmount.compareTo(maxRefundAmount) > 0) {
-                    throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_TOO_HIGH, requestedPositiveAmount, maxRefundAmount);
-                }
 
-                // Before we go further, check if that refund already got inserted -- the payment system keps a state machine
+                // Retrieve the amounts to adjust, if needed
+                final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts = computeItemAdjustments(payment.getInvoiceId().toString(),
+                                                                                               transInvoiceDao,
+                                                                                               invoiceItemIdsWithNullAmounts);
+
+                // Compute the actual amount to refund
+                final BigDecimal requestedPositiveAmount = computePositiveRefundAmount(payment, requestedRefundAmount, invoiceItemIdsWithAmounts);
+
+                // Before we go further, check if that refund already got inserted -- the payment system keeps a state machine
                 // and so this call may be called several time for the same  paymentCookieId (which is really the refundId)
-                //
                 final InvoicePayment existingRefund = transactional.getPaymentsForCookieId(paymentCookieId.toString());
                 if (existingRefund != null) {
                     return existingRefund;
@@ -333,7 +345,6 @@ public class DefaultInvoiceDao implements InvoiceDao {
                 transactional.create(refund, context);
 
                 // Retrieve invoice after the Refund
-                final InvoiceSqlDao transInvoiceDao = transactional.become(InvoiceSqlDao.class);
                 final Invoice invoice = transInvoiceDao.getById(payment.getInvoiceId().toString());
                 if (invoice != null) {
                     populateChildren(invoice, transInvoiceDao);
@@ -344,8 +355,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
                 final BigDecimal invoiceBalanceAfterRefund = invoice.getBalance();
                 final InvoiceItemSqlDao transInvoiceItemDao = transInvoiceDao.become(InvoiceItemSqlDao.class);
 
-                // If we have an existing CBA > 0, we need to adjust it
-                //final BigDecimal cbaAmountAfterRefund = invoice.getCBAAmount();
+                // If we have an existing CBA > 0 at the account level, we need to use it
                 final BigDecimal accountCbaAvailable = getAccountCBAFromTransaction(invoice.getAccountId(), transInvoiceDao);
                 BigDecimal cbaAdjAmount = BigDecimal.ZERO;
                 if (accountCbaAvailable.compareTo(BigDecimal.ZERO) > 0) {
@@ -355,19 +365,102 @@ public class DefaultInvoiceDao implements InvoiceDao {
                 }
                 final BigDecimal requestedPositiveAmountAfterCbaAdj = requestedPositiveAmount.add(cbaAdjAmount);
 
-                if (isInvoiceAdjusted) {
+                // At this point, we created the refund which made the invoice balance positive and applied any existing
+                // available CBA to that invoice.
+                // We now need to adjust the invoice and/or invoice items if needed and specified.
+                if (isInvoiceAdjusted && invoiceItemIdsWithAmounts.size() == 0) {
+                    // Invoice adjustment
                     final BigDecimal maxBalanceToAdjust = (invoiceBalanceAfterRefund.compareTo(BigDecimal.ZERO) <= 0) ? BigDecimal.ZERO : invoiceBalanceAfterRefund;
                     final BigDecimal requestedPositiveAmountToAdjust = requestedPositiveAmountAfterCbaAdj.compareTo(maxBalanceToAdjust) > 0 ? maxBalanceToAdjust : requestedPositiveAmountAfterCbaAdj;
                     if (requestedPositiveAmountToAdjust.compareTo(BigDecimal.ZERO) > 0) {
                         final InvoiceItem adjItem = new RefundAdjInvoiceItem(invoice.getId(), invoice.getAccountId(), context.getCreatedDate().toLocalDate(), requestedPositiveAmountToAdjust.negate(), invoice.getCurrency());
                         transInvoiceItemDao.create(adjItem, context);
                     }
+                } else if (isInvoiceAdjusted) {
+                    // Invoice item adjustment
+                    for (final UUID invoiceItemId : invoiceItemIdsWithAmounts.keySet()) {
+                        final BigDecimal adjAmount = invoiceItemIdsWithAmounts.get(invoiceItemId);
+                        final InvoiceItem item = createAdjustmentItem(transInvoiceDao, invoice.getId(), invoiceItemId, adjAmount,
+                                                                      invoice.getCurrency(), context.getCreatedDate().toLocalDate());
+                        transInvoiceItemDao.create(item, context);
+                    }
                 }
+
                 return refund;
             }
         });
     }
 
+    /**
+     * Find amounts to adjust for individual items, if not specified.
+     * The user gives us a list of items to adjust associated with a given amount (how much to refund per invoice item).
+     * In case of full adjustments, the amount can be null: in this case, we retrieve the original amount for the invoice
+     * item.
+     *
+     * @param invoiceId                     original invoice id
+     * @param transInvoiceDao               the transactional InvoiceSqlDao
+     * @param invoiceItemIdsWithNullAmounts the original mapping between invoice item ids and amount to refund (contains null)
+     * @return the final mapping between invoice item ids and amount to refund
+     * @throws InvoiceApiException
+     */
+    private Map<UUID, BigDecimal> computeItemAdjustments(final String invoiceId, final InvoiceSqlDao transInvoiceDao,
+                                                         final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts) throws InvoiceApiException {
+        // Populate the missing amounts for individual items, if needed
+        final Builder<UUID, BigDecimal> invoiceItemIdsWithAmountsBuilder = new Builder<UUID, BigDecimal>();
+        if (invoiceItemIdsWithNullAmounts.size() == 0) {
+            return invoiceItemIdsWithAmountsBuilder.build();
+        }
+
+        // Retrieve invoice before the Refund
+        final Invoice invoice = transInvoiceDao.getById(invoiceId);
+        if (invoice != null) {
+            populateChildren(invoice, transInvoiceDao);
+        } else {
+            throw new IllegalStateException("Invoice shouldn't be null for id " + invoiceId);
+        }
+
+        for (final UUID invoiceItemId : invoiceItemIdsWithNullAmounts.keySet()) {
+            final BigDecimal adjAmount = Objects.firstNonNull(invoiceItemIdsWithNullAmounts.get(invoiceItemId),
+                                                              getInvoiceItemAmountForId(invoice, invoiceItemId));
+            invoiceItemIdsWithAmountsBuilder.put(invoiceItemId, adjAmount);
+        }
+
+        return invoiceItemIdsWithAmountsBuilder.build();
+    }
+
+    private BigDecimal getInvoiceItemAmountForId(final Invoice invoice, final UUID invoiceItemId) throws InvoiceApiException {
+        for (final InvoiceItem invoiceItem : invoice.getInvoiceItems()) {
+            if (invoiceItem.getId().equals(invoiceItemId)) {
+                return invoiceItem.getAmount();
+            }
+        }
+
+        throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
+    }
+
+    @VisibleForTesting
+    BigDecimal computePositiveRefundAmount(final InvoicePayment payment, final BigDecimal requestedAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) throws InvoiceApiException {
+        final BigDecimal maxRefundAmount = payment.getAmount() == null ? BigDecimal.ZERO : payment.getAmount();
+        final BigDecimal requestedPositiveAmount = requestedAmount == null ? maxRefundAmount : requestedAmount;
+        // This check is good but not enough, we need to also take into account previous refunds
+        // (But that should have been checked in the payment call already)
+        if (requestedPositiveAmount.compareTo(maxRefundAmount) > 0) {
+            throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_TOO_HIGH, requestedPositiveAmount, maxRefundAmount);
+        }
+
+        // Verify if the requested amount matches the invoice items to adjust, if specified
+        BigDecimal amountFromItems = BigDecimal.ZERO;
+        for (final BigDecimal itemAmount : invoiceItemIdsWithAmounts.values()) {
+            amountFromItems = amountFromItems.add(itemAmount);
+        }
+
+        // Sanity check: if some items were specified, then the sum should be equal to specified refund amount, if specified
+        if (amountFromItems.compareTo(BigDecimal.ZERO) != 0 && requestedPositiveAmount.compareTo(amountFromItems) != 0) {
+            throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_DONT_MATCH_ITEMS_TO_ADJUST, requestedPositiveAmount, amountFromItems);
+        }
+        return requestedPositiveAmount;
+    }
+
     @Override
     public InvoicePayment postChargeback(final UUID invoicePaymentId, final BigDecimal amount, final CallContext context) throws InvoiceApiException {
 
@@ -438,8 +531,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
     @Override
     public InvoiceItem insertCredit(final UUID accountId, final UUID invoiceId, final BigDecimal positiveCreditAmount,
-                                    final LocalDate effectiveDate, final Currency currency,
-                                    final CallContext context) {
+                                    final LocalDate effectiveDate, 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 {
@@ -453,7 +545,7 @@ public class DefaultInvoiceDao implements InvoiceDao {
 
                 // Note! The amount is negated here!
                 final InvoiceItem credit = new CreditAdjInvoiceItem(invoiceIdForCredit, accountId, effectiveDate, positiveCreditAmount.negate(), currency);
-                createItemAndAddCBAIfNeeded(transactional, credit, context);
+                insertItemAndAddCBAIfNeeded(transactional, credit, context);
                 return credit;
             }
         });
@@ -466,28 +558,9 @@ public class DefaultInvoiceDao implements InvoiceDao {
         return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
             @Override
             public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
-                // 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!
-                final InvoiceItem invoiceItemAdjustment = new ItemAdjInvoiceItem(invoiceItemToBeAdjusted, effectiveDate,
-                                                                                 amountToRefund.negate(), currencyForAdjustment);
-                createItemAndAddCBAIfNeeded(transactional, invoiceItemAdjustment, context);
+                final InvoiceItem invoiceItemAdjustment = createAdjustmentItem(transactional, invoiceId, invoiceItemId, positiveAdjAmount,
+                                                                               currency, effectiveDate);
+                insertItemAndAddCBAIfNeeded(transactional, invoiceItemAdjustment, context);
                 return invoiceItemAdjustment;
             }
         });
@@ -499,25 +572,71 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     /**
+     * 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 amountToAdjust = 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, amountToAdjust.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 createItemAndAddCBAIfNeeded(final InvoiceSqlDao transactional, final InvoiceItem item, final CallContext context) {
+    private void insertItemAndAddCBAIfNeeded(final InvoiceSqlDao transactional, final InvoiceItem item, final CallContext context) {
         final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
         transInvoiceItemDao.create(item, context);
 
-        final Invoice invoice = transactional.getById(item.getInvoiceId().toString());
+        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 " + item.getInvoiceId());
+            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);
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 8a352a3..a9104f9 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
@@ -18,6 +18,7 @@ package com.ning.billing.invoice.dao;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -34,7 +35,7 @@ import com.ning.billing.util.callcontext.CallContext;
 
 public interface InvoiceDao {
 
-    void create(final Invoice invoice, final int billCycleDayUTC, final CallContext context);
+    void create(final Invoice invoice, final int billCycleDayUTC, final boolean isRealInvoice, final CallContext context);
 
     Invoice getById(final UUID id);
 
@@ -70,7 +71,21 @@ public interface InvoiceDao {
 
     InvoicePayment postChargeback(final UUID invoicePaymentId, final BigDecimal amount, final CallContext context) throws InvoiceApiException;
 
-    InvoicePayment createRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, UUID paymentCookieId, CallContext context) throws InvoiceApiException;
+    /**
+     * Create a refund.
+     *
+     *
+     * @param paymentId                 payment associated with that refund
+     * @param amount                    amount to refund
+     * @param isInvoiceAdjusted         whether the refund should trigger an invoice or invoice item adjustment
+     * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
+     * @param paymentCookieId           payment cookie id
+     * @param context                   the call context
+     * @return the created invoice payment object associated with this refund
+     * @throws InvoiceApiException
+     */
+    InvoicePayment createRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts,
+                                final UUID paymentCookieId, final CallContext context) throws InvoiceApiException;
 
     BigDecimal getRemainingAmountPaid(final UUID invoicePaymentId);
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
index 694330d..087cf4a 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/InvoiceDispatcher.java
@@ -62,6 +62,8 @@ import com.ning.billing.util.globallocker.GlobalLocker;
 import com.ning.billing.util.globallocker.GlobalLocker.LockerType;
 import com.ning.billing.util.globallocker.LockFailedException;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
 
 public class InvoiceDispatcher {
@@ -178,7 +180,16 @@ public class InvoiceDispatcher {
                 }
                 outputDebugData(billingEvents, invoices);
                 if (!dryRun) {
-                    invoiceDao.create(invoice, account.getBillCycleDay().getDayOfMonthUTC(), context);
+
+                    // We need to check whether this is just a 'shell' invoice or a real invoice with items on it
+                    final boolean isRealInvoiceWithItems = Collections2.filter(invoice.getInvoiceItems(), new Predicate<InvoiceItem>() {
+                        @Override
+                        public boolean apply(InvoiceItem input) {
+                            return input.getInvoiceId().equals(invoice.getId());
+                        }
+                    }).size() > 0;
+
+                    invoiceDao.create(invoice, account.getBillCycleDay().getDayOfMonthUTC(), isRealInvoiceWithItems, context);
 
                     final List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
                     final List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
@@ -236,16 +247,17 @@ public class InvoiceDispatcher {
             final UUID subscriptionId = item.getSubscriptionId();
             final DateTime endDate;
             if (item.getEndDate() != null) {
-                endDate = new DateTime(item.getEndDate().toDateTimeAtStartOfDay(), DateTimeZone.UTC);
+                endDate = new DateTime(item.getEndDate().toDateTime(clock.getUTCNow()) , DateTimeZone.UTC);
             } else {
                 // item end date is null for fixed price items for instance
-                endDate = new DateTime(item.getStartDate().toDateTimeAtStartOfDay(), DateTimeZone.UTC);
+                endDate = new DateTime(item.getStartDate().toDateTime(clock.getUTCNow()), DateTimeZone.UTC);
             }
 
             if (chargeThroughDates.containsKey(subscriptionId)) {
                 if (chargeThroughDates.get(subscriptionId).isBefore(endDate)) {
                     // The CTD should always align with the BCD
-                    chargeThroughDates.put(subscriptionId, InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(endDate, billCycleDay.getDayOfMonthLocal()));
+                    final DateTime ctd = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(endDate, billCycleDay.getDayOfMonthLocal());
+                    chargeThroughDates.put(subscriptionId, ctd);
                 }
             } else {
                 chargeThroughDates.put(subscriptionId, endDate);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
index 07e385b..69193f6 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InAdvanceBillingMode.java
@@ -53,10 +53,28 @@ public class InAdvanceBillingMode implements BillingMode {
 
         final List<RecurringInvoiceItemData> results = new ArrayList<RecurringInvoiceItemData>();
 
-        // beginning from the start date, find the first billing date
         final LocalDate firstBillingCycleDate = calculateBillingCycleDateOnOrAfter(startDate, accountTimeZone, billingCycleDayLocal);
 
-        // add pro-ration item if needed
+        // We are not billing for less than a day (we could...)
+        if (endDate != null && endDate.equals(startDate)) {
+            return results;
+        }
+        //
+        // If there is an endDate and that endDate is before our first coming firstBillingCycleDate, all we have to do
+        // is to charge for that period
+        //
+        if (endDate != null && !endDate.isAfter(firstBillingCycleDate)) {
+            final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, endDate, billingPeriod);
+            final RecurringInvoiceItemData itemData = new RecurringInvoiceItemData(startDate, endDate, leadingProRationPeriods);
+            results.add(itemData);
+            return results;
+        }
+
+        //
+        // Leading proration if
+        // i) The first firstBillingCycleDate is strictly after our start date AND
+        // ii) The endDate is is not null and is strictly after our firstBillingCycleDate (previous check)
+        //
         if (firstBillingCycleDate.isAfter(startDate)) {
             final BigDecimal leadingProRationPeriods = calculateProRationBeforeFirstBillingPeriod(startDate, firstBillingCycleDate, billingPeriod);
             if (leadingProRationPeriods != null && leadingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
@@ -67,7 +85,11 @@ public class InAdvanceBillingMode implements BillingMode {
             }
         }
 
-        // add one item per billing period
+        //
+        // Calculate the effectiveEndDate from the firstBillingCycleDate:
+        // - If endDate != null and targetDate is after endDate => this is the endDate and will lead to a trailing pro-ration
+        // - If not, this is the last billingCycleDate calculation right after the targetDate
+        //
         final LocalDate effectiveEndDate;
         if (endDate != null) {
             effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, endDate, billingPeriod);
@@ -75,6 +97,9 @@ public class InAdvanceBillingMode implements BillingMode {
             effectiveEndDate = calculateEffectiveEndDate(firstBillingCycleDate, targetDate, billingPeriod);
         }
 
+        //
+        // Based on what we calculated previously, code recompute one more time the numberOfWholeBillingPeriods
+        //
         final LocalDate lastBillingCycleDate = calculateLastBillingCycleDateBefore(effectiveEndDate, firstBillingCycleDate, billingCycleDayLocal, billingPeriod);
         final int numberOfWholeBillingPeriods = calculateNumberOfWholeBillingPeriods(firstBillingCycleDate, lastBillingCycleDate, billingPeriod);
         final int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
@@ -97,7 +122,9 @@ public class InAdvanceBillingMode implements BillingMode {
             results.add(new RecurringInvoiceItemData(servicePeriodStartDate, servicePeriodEndDate, BigDecimal.ONE));
         }
 
-        // check to see if a trailing pro-ration amount is needed
+        //
+        // Now we check if indeed we need a trailing proration and add that incomplete item
+        //
         if (effectiveEndDate.isAfter(lastBillingCycleDate)) {
             final BigDecimal trailingProRationPeriods = calculateProRationAfterLastBillingCycleDate(effectiveEndDate, lastBillingCycleDate, billingPeriod);
             if (trailingProRationPeriods.compareTo(BigDecimal.ZERO) > 0) {
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index baec2d4..a4ee86c 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -117,14 +117,13 @@ getChargeBacksByAccountId() ::= <<
     SELECT <invoicePaymentFields("ip.")>
     FROM invoice_payments ip
     INNER JOIN invoices i ON i.id = ip.invoice_id
-    WHERE i.account_id = :accountId
-    AND linked_invoice_payment_id IS NOT NULL;
+    WHERE ip.type = 'CHARGED_BACK' AND i.account_id = :accountId;
 >>
 
 getChargebacksByPaymentId() ::= <<
     SELECT <invoicePaymentFields()>
     FROM invoice_payments
-    WHERE linked_invoice_payment_id IN
+    WHERE type = 'CHARGED_BACK' AND linked_invoice_payment_id IN
         (SELECT id FROM invoice_payments WHERE payment_id = :paymentId);
 >>
 ;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
new file mode 100644
index 0000000..9ccbf84
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
@@ -0,0 +1,181 @@
+/*
+ * 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.invoice;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.IDBI;
+import org.testng.Assert;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+import com.ning.billing.KillbillTestSuiteWithEmbeddedDB;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.InvoicePayment.InvoicePaymentType;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.dao.DefaultInvoiceDao;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.InvoiceItemSqlDao;
+import com.ning.billing.invoice.dao.InvoiceSqlDao;
+import com.ning.billing.invoice.notification.MockNextBillingDatePoster;
+import com.ning.billing.invoice.notification.NextBillingDatePoster;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TestCallContext;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.tag.api.DefaultTagUserApi;
+import com.ning.billing.util.tag.dao.MockTagDao;
+import com.ning.billing.util.tag.dao.MockTagDefinitionDao;
+import com.ning.billing.util.tag.dao.TagDao;
+import com.ning.billing.util.tag.dao.TagDefinitionDao;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import static com.ning.billing.invoice.tests.InvoiceTestUtils.createAndPersistInvoice;
+import static com.ning.billing.invoice.tests.InvoiceTestUtils.createAndPersistPayment;
+
+public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB {
+
+    private static final BigDecimal THIRTY = new BigDecimal("30.00");
+    private static final Currency CURRENCY = Currency.EUR;
+
+    private final Clock clock = new ClockMock();
+
+    private InvoiceSqlDao invoiceSqlDao;
+    private InvoiceItemSqlDao invoiceItemSqlDao;
+    private InvoicePaymentApi invoicePaymentApi;
+    private CallContext context;
+
+    @BeforeSuite(groups = "slow")
+    public void setup() throws IOException {
+        final MysqlTestingHelper helper = KillbillTestSuiteWithEmbeddedDB.getMysqlTestingHelper();
+        final IDBI dbi = helper.getDBI();
+
+        invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
+        invoiceSqlDao.test();
+
+        invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
+        invoiceItemSqlDao.test();
+
+        final NextBillingDatePoster nextBillingDatePoster = new MockNextBillingDatePoster();
+        final TagDefinitionDao tagDefinitionDao = new MockTagDefinitionDao();
+        final TagDao tagDao = new MockTagDao();
+        final TagUserApi tagUserApi = new DefaultTagUserApi(tagDefinitionDao, tagDao);
+        final InvoiceDao invoiceDao = new DefaultInvoiceDao(dbi, nextBillingDatePoster, tagUserApi, clock);
+        invoicePaymentApi = new DefaultInvoicePaymentApi(invoiceDao);
+
+        context = new TestCallContext("Invoice payment tests");
+    }
+
+    @Test(groups = "slow")
+    public void testFullRefundWithNoAdjustment() throws Exception {
+        verifyRefund(THIRTY, THIRTY, THIRTY, false, ImmutableMap.<UUID, BigDecimal>of());
+    }
+
+    @Test(groups = "slow")
+    public void testPartialRefundWithNoAdjustment() throws Exception {
+        verifyRefund(THIRTY, BigDecimal.TEN, BigDecimal.TEN, false, ImmutableMap.<UUID, BigDecimal>of());
+    }
+
+    @Test(groups = "slow")
+    public void testFullRefundWithInvoiceAdjustment() throws Exception {
+        verifyRefund(THIRTY, THIRTY, BigDecimal.ZERO, true, ImmutableMap.<UUID, BigDecimal>of());
+    }
+
+    @Test(groups = "slow")
+    public void testPartialRefundWithInvoiceAdjustment() throws Exception {
+        verifyRefund(THIRTY, BigDecimal.TEN, BigDecimal.ZERO, true, ImmutableMap.<UUID, BigDecimal>of());
+    }
+
+    @Test(groups = "slow")
+    public void testFullRefundWithBothInvoiceItemAdjustments() throws Exception {
+        // Create an invoice with two items (30 \u20ac and 10 \u20ac)
+        final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock,
+                                                        ImmutableList.<BigDecimal>of(THIRTY, BigDecimal.TEN), CURRENCY, context);
+
+        // Fully adjust both items
+        final Map<UUID, BigDecimal> adjustments = new HashMap<UUID, BigDecimal>();
+        adjustments.put(invoice.getInvoiceItems().get(0).getId(), null);
+        adjustments.put(invoice.getInvoiceItems().get(1).getId(), null);
+
+        verifyRefund(invoice, new BigDecimal("40"), new BigDecimal("40"), BigDecimal.ZERO, true, adjustments);
+    }
+
+    @Test(groups = "slow")
+    public void testPartialRefundWithSingleInvoiceItemAdjustment() throws Exception {
+        // Create an invoice with two items (30 \u20ac and 10 \u20ac)
+        final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock,
+                                                        ImmutableList.<BigDecimal>of(THIRTY, BigDecimal.TEN), CURRENCY, context);
+
+        // Fully adjust both items
+        final Map<UUID, BigDecimal> adjustments = new HashMap<UUID, BigDecimal>();
+        adjustments.put(invoice.getInvoiceItems().get(0).getId(), null);
+
+        verifyRefund(invoice, new BigDecimal("40"), new BigDecimal("30"), BigDecimal.ZERO, true, adjustments);
+    }
+
+    @Test(groups = "slow")
+    public void testPartialRefundWithTwoInvoiceItemAdjustment() throws Exception {
+        // Create an invoice with two items (30 \u20ac and 10 \u20ac)
+        final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock,
+                                                        ImmutableList.<BigDecimal>of(THIRTY, BigDecimal.TEN), CURRENCY, context);
+        // Adjust partially both items: the invoice posted was 40 \u20ac, but we should really just have charged you 2 \u20ac
+        final ImmutableMap<UUID, BigDecimal> adjustments = ImmutableMap.<UUID, BigDecimal>of(invoice.getInvoiceItems().get(0).getId(), new BigDecimal("29"),
+                                                                                             invoice.getInvoiceItems().get(1).getId(), new BigDecimal("9"));
+        verifyRefund(invoice, new BigDecimal("40"), new BigDecimal("38"), BigDecimal.ZERO, true, adjustments);
+    }
+
+    private void verifyRefund(final BigDecimal invoiceAmount, final BigDecimal refundAmount, final BigDecimal finalInvoiceAmount,
+                              final boolean adjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) throws InvoiceApiException {
+        final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock, invoiceAmount, CURRENCY, context);
+        verifyRefund(invoice, invoiceAmount, refundAmount, finalInvoiceAmount, adjusted, invoiceItemIdsWithAmounts);
+    }
+
+    private void verifyRefund(final Invoice invoice, final BigDecimal invoiceAmount, final BigDecimal refundAmount, final BigDecimal finalInvoiceAmount,
+                              final boolean adjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) throws InvoiceApiException {
+        final InvoicePayment payment = createAndPersistPayment(invoicePaymentApi, clock, invoice.getId(), invoiceAmount, CURRENCY, context);
+
+        // Verify the initial invoice balance
+        final BigDecimal initialInvoiceBalance = invoicePaymentApi.getInvoice(invoice.getId()).getBalance();
+        Assert.assertEquals(initialInvoiceBalance.compareTo(BigDecimal.ZERO), 0);
+
+        // Create a full refund with no adjustment
+        final InvoicePayment refund = invoicePaymentApi.createRefund(payment.getPaymentId(), refundAmount, adjusted, invoiceItemIdsWithAmounts,
+                                                                     UUID.randomUUID(), context);
+        Assert.assertEquals(refund.getAmount().compareTo(refundAmount.negate()), 0);
+        Assert.assertEquals(refund.getCurrency(), CURRENCY);
+        Assert.assertEquals(refund.getInvoiceId(), invoice.getId());
+        Assert.assertEquals(refund.getPaymentId(), payment.getPaymentId());
+        Assert.assertEquals(refund.getType(), InvoicePaymentType.REFUND);
+
+        // Verify the current invoice balance
+        final BigDecimal newInvoiceBalance = invoicePaymentApi.getInvoice(invoice.getId()).getBalance().setScale(2, RoundingMode.HALF_UP);
+        Assert.assertEquals(newInvoiceBalance.compareTo(finalInvoiceAmount.setScale(2, RoundingMode.HALF_UP)), 0);
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
index 87183b4..f229e4a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
@@ -19,6 +19,7 @@ package com.ning.billing.invoice.api;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -167,8 +168,9 @@ public class MockInvoicePaymentApi implements InvoicePaymentApi {
     }
 
     @Override
-    public InvoicePayment createRefund(UUID paymentId,
-            BigDecimal amount, boolean isInvoiceAdjusted, UUID paymentCookieId, CallContext context)
+    public InvoicePayment createRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted,
+                                       final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final UUID paymentCookieId,
+                                       final CallContext context)
             throws InvoiceApiException {
         return null;
     }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index 88ce414..2de15d7 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -136,7 +136,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
 
         // Adjust the invoice for the full amount
         final InvoiceItem adjInvoiceItem = invoiceUserApi.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItem.getId(),
-                                                                                      clock.getUTCToday(), null, null, context);
+                                                                                      clock.getUTCToday(), context);
         Assert.assertEquals(adjInvoiceItem.getInvoiceId(), invoiceId);
         Assert.assertEquals(adjInvoiceItem.getInvoiceItemType(), InvoiceItemType.ITEM_ADJ);
         Assert.assertEquals(adjInvoiceItem.getAccountId(), accountId);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index a483c06..301866c 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
@@ -25,12 +25,10 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
-import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 
 import com.google.inject.Inject;
 
-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;
@@ -51,7 +49,7 @@ public class MockInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public void create(final Invoice invoice, final int billCycleDay, final CallContext context) {
+    public void create(final Invoice invoice, final int billCycleDay, final boolean isRealInvoice, final CallContext context) {
         synchronized (monitor) {
             invoices.put(invoice.getId(), invoice);
         }
@@ -282,10 +280,10 @@ public class MockInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public InvoicePayment createRefund(UUID paymentId,
-            BigDecimal amount, boolean isInvoiceAdjusted, UUID paymentCookieId, CallContext context)
+    public InvoicePayment createRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted,
+                                       final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final UUID paymentCookieId,
+                                       final CallContext context)
             throws InvoiceApiException {
         return null;
     }
-
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java
index 2bccd7b..9c8a92a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestDefaultInvoiceDao.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.invoice.dao;
 
+import java.math.BigDecimal;
 import java.util.Map;
 import java.util.UUID;
 
@@ -25,8 +26,11 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.ning.billing.ErrorCode;
 import com.ning.billing.invoice.InvoiceTestSuite;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
 import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.callcontext.CallContext;
@@ -40,7 +44,10 @@ import com.ning.billing.util.tag.dao.MockTagDefinitionDao;
 import com.ning.billing.util.tag.dao.TagDao;
 import com.ning.billing.util.tag.dao.TagDefinitionDao;
 
+import com.google.common.collect.ImmutableMap;
+
 public class TestDefaultInvoiceDao extends InvoiceTestSuite {
+
     private InvoiceSqlDao invoiceSqlDao;
     private TagUserApi tagUserApi;
     private DefaultInvoiceDao dao;
@@ -59,6 +66,43 @@ public class TestDefaultInvoiceDao extends InvoiceTestSuite {
     }
 
     @Test(groups = "fast")
+    public void testComputePositiveRefundAmount() throws Exception {
+        // Verify the cases with no adjustment first
+        final Map<UUID, BigDecimal> noItemAdjustment = ImmutableMap.<UUID, BigDecimal>of();
+        verifyComputedRefundAmount(null, null, noItemAdjustment, BigDecimal.ZERO);
+        verifyComputedRefundAmount(null, BigDecimal.ZERO, noItemAdjustment, BigDecimal.ZERO);
+        verifyComputedRefundAmount(BigDecimal.TEN, null, noItemAdjustment, BigDecimal.TEN);
+        verifyComputedRefundAmount(BigDecimal.TEN, BigDecimal.ONE, noItemAdjustment, BigDecimal.ONE);
+        try {
+            verifyComputedRefundAmount(BigDecimal.ONE, BigDecimal.TEN, noItemAdjustment, BigDecimal.TEN);
+            Assert.fail("Shouldn't have been able to compute a refund amount");
+        } catch (InvoiceApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.REFUND_AMOUNT_TOO_HIGH.getCode());
+        }
+
+        // Try with adjustments now
+        final Map<UUID, BigDecimal> itemAdjustments = ImmutableMap.<UUID, BigDecimal>of(UUID.randomUUID(), BigDecimal.ONE,
+                                                                                        UUID.randomUUID(), BigDecimal.TEN,
+                                                                                        UUID.randomUUID(), BigDecimal.ZERO);
+        verifyComputedRefundAmount(new BigDecimal("100"), new BigDecimal("11"), itemAdjustments, new BigDecimal("11"));
+        try {
+            verifyComputedRefundAmount(new BigDecimal("100"), BigDecimal.TEN, itemAdjustments, BigDecimal.TEN);
+            Assert.fail("Shouldn't have been able to compute a refund amount");
+        } catch (InvoiceApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.REFUND_AMOUNT_DONT_MATCH_ITEMS_TO_ADJUST.getCode());
+        }
+    }
+
+    private void verifyComputedRefundAmount(final BigDecimal paymentAmount, final BigDecimal requestedAmount,
+                                            final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final BigDecimal expectedRefundAmount) throws InvoiceApiException {
+        final InvoicePayment invoicePayment = Mockito.mock(InvoicePayment.class);
+        Mockito.when(invoicePayment.getAmount()).thenReturn(paymentAmount);
+
+        final BigDecimal actualRefundAmount = dao.computePositiveRefundAmount(invoicePayment, requestedAmount, invoiceItemIdsWithAmounts);
+        Assert.assertEquals(actualRefundAmount, expectedRefundAmount);
+    }
+
+    @Test(groups = "fast")
     public void testFindByNumber() throws Exception {
         final Integer number = Integer.MAX_VALUE;
         final Invoice invoice = Mockito.mock(Invoice.class);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
index 01e628d..55a2e61 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDao.java
@@ -27,7 +27,6 @@ import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
 import org.mockito.Mockito;
-import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.ning.billing.catalog.DefaultPrice;
@@ -66,6 +65,8 @@ import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.dao.AuditedTagDao;
 import com.ning.billing.util.tag.dao.TagDao;
 
+import com.google.common.collect.ImmutableMap;
+
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
@@ -79,7 +80,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final Invoice invoice = new DefaultInvoice(accountId, clock.getUTCToday(), clock.getUTCToday(), Currency.USD);
         final LocalDate invoiceDate = invoice.getInvoiceDate();
 
-        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), true, context);
 
         final List<Invoice> invoices = invoiceDao.getInvoicesByAccount(accountId);
         assertNotNull(invoices);
@@ -105,7 +106,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
                                                                  new BigDecimal("21.00"), new BigDecimal("7.00"), Currency.USD);
 
         invoice.addInvoiceItem(invoiceItem);
-        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), true, context);
 
         final Invoice savedInvoice = invoiceDao.getById(invoiceId);
         assertNotNull(savedInvoice);
@@ -150,7 +151,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
 
         // Create invoice 1 (subscriptions 1-4)
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final UUID invoiceId1 = invoice1.getId();
 
@@ -175,7 +176,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
 
         // Create invoice 2 (subscriptions 1-3)
         final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate, Currency.USD);
-        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
 
         final UUID invoiceId2 = invoice2.getId();
 
@@ -226,7 +227,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
 
         // Create invoice 1 (subscriptions 1-4)
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final UUID invoiceId1 = invoice1.getId();
 
@@ -251,7 +252,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
 
         // create invoice 2 (subscriptions 1-3)
         final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate, Currency.USD);
-        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
 
         final UUID invoiceId2 = invoice2.getId();
 
@@ -302,7 +303,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
 
         // Create invoice 1 (subscriptions 1-4)
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final UUID invoiceId1 = invoice1.getId();
 
@@ -343,7 +344,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
 
         // create invoice 2 (subscriptions 1-3)
         final DefaultInvoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate, Currency.USD);
-        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
 
         final UUID invoiceId2 = invoice2.getId();
 
@@ -392,11 +393,11 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final UUID accountId = UUID.randomUUID();
         final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true,  context);
 
         final LocalDate targetDate2 = new LocalDate(2011, 12, 6);
         final Invoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate2, Currency.USD);
-        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
 
         List<Invoice> invoices;
         invoices = invoiceDao.getInvoicesByAccount(accountId, new LocalDate(2011, 1, 1));
@@ -421,7 +422,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final UUID bundleId = UUID.randomUUID();
         final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final LocalDate startDate = new LocalDate(2011, 3, 1);
         final LocalDate endDate = startDate.plusMonths(1);
@@ -451,7 +452,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final UUID bundleId = UUID.randomUUID();
         final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final LocalDate startDate = new LocalDate(2011, 3, 1);
         final LocalDate endDate = startDate.plusMonths(1);
@@ -475,7 +476,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final UUID bundleId = UUID.randomUUID();
         final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final LocalDate startDate = new LocalDate(2011, 3, 1);
         final LocalDate endDate = startDate.plusMonths(1);
@@ -500,7 +501,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final UUID accountId = UUID.randomUUID();
         final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final BigDecimal payment1 = new BigDecimal("48.0");
         final InvoicePayment payment = new DefaultInvoicePayment(InvoicePaymentType.ATTEMPT, UUID.randomUUID(), invoice1.getId(), new DateTime(), payment1, Currency.USD);
@@ -526,7 +527,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final UUID bundleId = UUID.randomUUID();
         final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final LocalDate startDate = new LocalDate(2011, 3, 1);
         final LocalDate endDate = startDate.plusMonths(1);
@@ -550,7 +551,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         balance = invoiceDao.getAccountBalance(accountId);
         assertEquals(balance.compareTo(new BigDecimal("0.00")), 0);
 
-        invoiceDao.createRefund(paymentId, refund1, withAdjustment, UUID.randomUUID(), context);
+        invoiceDao.createRefund(paymentId, refund1, withAdjustment, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID(), context);
         balance = invoiceDao.getAccountBalance(accountId);
         if (withAdjustment) {
             assertEquals(balance.compareTo(BigDecimal.ZERO), 0);
@@ -592,7 +593,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final UUID bundleId = UUID.randomUUID();
         final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final LocalDate startDate = new LocalDate(2011, 3, 1);
         final LocalDate endDate = startDate.plusMonths(1);
@@ -642,7 +643,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         assertEquals(cba.compareTo(new BigDecimal("10.00")), 0);
 
         // PARTIAL REFUND on the payment
-        invoiceDao.createRefund(paymentId, refundAmount, withAdjustment, UUID.randomUUID(), context);
+        invoiceDao.createRefund(paymentId, refundAmount, withAdjustment, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID(), context);
 
         balance = invoiceDao.getAccountBalance(accountId);
         assertEquals(balance.compareTo(expectedFinalBalance), 0);
@@ -659,7 +660,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final UUID bundleId = UUID.randomUUID();
         final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final LocalDate startDate = new LocalDate(2011, 3, 1);
         final LocalDate endDate = startDate.plusMonths(1);
@@ -721,7 +722,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         // NEXT RECURRING on invoice 2
 
         final Invoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1.plusMonths(1), Currency.USD);
-        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
 
         final RecurringInvoiceItem nextItem = new RecurringInvoiceItem(invoice2.getId(), accountId, bundleId, UUID.randomUUID(), "test plan", "test bla", startDate.plusMonths(1),
                                                                        endDate.plusMonths(1), rate2, rate2, Currency.USD);
@@ -809,7 +810,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         // Crete one invoice with a fixed invoice item
         final LocalDate targetDate = new LocalDate(2011, 2, 15);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final LocalDate startDate = new LocalDate(2011, 3, 1);
 
@@ -852,7 +853,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         final UUID bundleId = UUID.randomUUID();
         final LocalDate targetDate1 = new LocalDate(2011, 10, 6);
         final Invoice invoice1 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate1, Currency.USD);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
 
         final LocalDate startDate = new LocalDate(2011, 3, 1);
         final LocalDate endDate = startDate.plusMonths(1);
@@ -881,7 +882,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
 
         final LocalDate targetDate2 = new LocalDate(2011, 7, 1);
         final Invoice invoice2 = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate2, Currency.USD);
-        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
 
         final LocalDate startDate2 = new LocalDate(2011, 6, 1);
         final LocalDate endDate2 = startDate2.plusMonths(3);
@@ -951,8 +952,8 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         assertEquals(invoice2.getBalance(), FIVE);
         invoiceList.add(invoice2);
 
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
-        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
 
         final Invoice savedInvoice1 = invoiceDao.getById(invoice1.getId());
         assertEquals(savedInvoice1.getBalance(), ZERO);
@@ -1094,7 +1095,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         assertEquals(invoice.getNumberOfItems(), 2);
         assertEquals(invoice.getBalance().compareTo(cheapAmount), 0);
 
-        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), true, context);
         final Invoice savedInvoice = invoiceDao.getById(invoice.getId());
 
         assertNotNull(savedInvoice);
@@ -1130,7 +1131,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
 
         Invoice invoice1 = generator.generateInvoice(UUID.randomUUID(), events, invoices, new LocalDate(targetDate1), DateTimeZone.UTC, Currency.USD);
         invoices.add(invoice1);
-        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice1, invoice1.getTargetDate().getDayOfMonth(), true, context);
         invoice1 = invoiceDao.getById(invoice1.getId());
         assertNotNull(invoice1.getInvoiceNumber());
 
@@ -1140,7 +1141,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
                                                            "testEvent2", 2L, SubscriptionTransitionType.CHANGE);
         events.add(event2);
         Invoice invoice2 = generator.generateInvoice(UUID.randomUUID(), events, invoices, new LocalDate(targetDate2), DateTimeZone.UTC, Currency.USD);
-        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice2, invoice2.getTargetDate().getDayOfMonth(), true, context);
         invoice2 = invoiceDao.getById(invoice2.getId());
         assertNotNull(invoice2.getInvoiceNumber());
     }
@@ -1167,7 +1168,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         events.add(event1);
 
         final Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new LocalDate(targetDate1), DateTimeZone.UTC, Currency.USD);
-        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), true, context);
         invoiceDao.setWrittenOff(invoice.getId(), context);
 
         final TagDao tagDao = new AuditedTagDao(dbi, tagEventBuilder, bus);
@@ -1198,7 +1199,7 @@ public class TestInvoiceDao extends InvoiceDaoTestBase {
         events.add(event1);
 
         final Invoice invoice = generator.generateInvoice(UUID.randomUUID(), events, null, new LocalDate(targetDate1), DateTimeZone.UTC, Currency.USD);
-        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), context);
+        invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), true, context);
         invoiceDao.setWrittenOff(invoice.getId(), context);
 
         final TagDao tagDao = new AuditedTagDao(dbi, tagEventBuilder, bus);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
index 5e82f30..e76e890 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceDaoForItemAdjustment.java
@@ -63,7 +63,7 @@ public class TestInvoiceDaoForItemAdjustment extends InvoiceDaoTestBase {
                                                                  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);
+        invoiceDao.create(invoice, 1, true, context);
 
         try {
             invoiceDao.insertInvoiceItemAdjustment(invoice.getAccountId(), UUID.randomUUID(), invoiceItem.getId(), new LocalDate(2010, 1, 1), null, null, context);
@@ -81,7 +81,7 @@ public class TestInvoiceDaoForItemAdjustment extends InvoiceDaoTestBase {
                                                                  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);
+        invoiceDao.create(invoice, 1, true, context);
 
         final InvoiceItem adjustedInvoiceItem = createAndCheckAdjustment(invoice, invoiceItem, null);
         Assert.assertEquals(adjustedInvoiceItem.getAmount().compareTo(invoiceItem.getAmount().negate()), 0);
@@ -95,7 +95,7 @@ public class TestInvoiceDaoForItemAdjustment extends InvoiceDaoTestBase {
                                                                  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);
+        invoiceDao.create(invoice, 1, true, context);
 
         final InvoiceItem adjustedInvoiceItem = createAndCheckAdjustment(invoice, invoiceItem, BigDecimal.TEN);
         Assert.assertEquals(adjustedInvoiceItem.getAmount().compareTo(BigDecimal.TEN.negate()), 0);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java
index f8766f9..4c98b52 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java
@@ -115,7 +115,7 @@ public class TestInvoiceItemDao extends InvoiceDaoTestBase {
         final LocalDate targetDate = new LocalDate(2011, 5, 23);
         final DefaultInvoice invoice = new DefaultInvoice(accountId, clock.getUTCToday(), targetDate, Currency.USD);
 
-        invoiceDao.create(invoice, targetDate.getDayOfMonth(), context);
+        invoiceDao.create(invoice, targetDate.getDayOfMonth(), true, context);
 
         final UUID invoiceId = invoice.getId();
         final LocalDate startDate = new LocalDate(2011, 3, 1);
diff --git a/invoice/src/test/java/com/ning/billing/invoice/model/TestInAdvanceBillingMode.java b/invoice/src/test/java/com/ning/billing/invoice/model/TestInAdvanceBillingMode.java
index 10ded27..fccd7b1 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/model/TestInAdvanceBillingMode.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/model/TestInAdvanceBillingMode.java
@@ -41,8 +41,6 @@ public class TestInAdvanceBillingMode {
         final int billingCycleDayLocal = 15;
 
         final LinkedHashMap<LocalDate, LocalDate> expectedDates = new LinkedHashMap<LocalDate, LocalDate>();
-        expectedDates.put(new LocalDate(2012, 7, 16), new LocalDate(2012, 8, 15));
-
         verifyInvoiceItems(startDate, endDate, targetDate, TIMEZONE, billingCycleDayLocal, BILLING_PERIOD, expectedDates);
     }
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/InvoiceTestUtils.java b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoiceTestUtils.java
new file mode 100644
index 0000000..8826134
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/InvoiceTestUtils.java
@@ -0,0 +1,109 @@
+/*
+ * 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.tests;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.mockito.Mockito;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.InvoicePayment.InvoicePaymentType;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.dao.InvoiceItemSqlDao;
+import com.ning.billing.invoice.dao.InvoiceSqlDao;
+import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.Clock;
+
+import com.google.common.collect.ImmutableList;
+
+public class InvoiceTestUtils {
+
+    private InvoiceTestUtils() {}
+
+    public static Invoice createAndPersistInvoice(final InvoiceSqlDao invoiceSqlDao,
+                                                  final InvoiceItemSqlDao invoiceItemSqlDao,
+                                                  final Clock clock,
+                                                  final BigDecimal amount,
+                                                  final Currency currency,
+                                                  final CallContext callContext) {
+        return createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock, ImmutableList.<BigDecimal>of(amount),
+                                       currency, callContext);
+    }
+
+    public static Invoice createAndPersistInvoice(final InvoiceSqlDao invoiceSqlDao,
+                                                  final InvoiceItemSqlDao invoiceItemSqlDao,
+                                                  final Clock clock,
+                                                  final List<BigDecimal> amounts,
+                                                  final Currency currency,
+                                                  final CallContext callContext) {
+        final Invoice invoice = Mockito.mock(Invoice.class);
+        final UUID invoiceId = UUID.randomUUID();
+        final UUID accountId = UUID.randomUUID();
+
+        Mockito.when(invoice.getId()).thenReturn(invoiceId);
+        Mockito.when(invoice.getAccountId()).thenReturn(accountId);
+        Mockito.when(invoice.getInvoiceDate()).thenReturn(clock.getUTCToday());
+        Mockito.when(invoice.getTargetDate()).thenReturn(clock.getUTCToday());
+        Mockito.when(invoice.getCurrency()).thenReturn(currency);
+        Mockito.when(invoice.isMigrationInvoice()).thenReturn(false);
+
+        final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+        for (final BigDecimal amount : amounts) {
+            final InvoiceItem invoiceItem = createInvoiceItem(clock, invoiceId, accountId, amount, currency);
+            invoiceItemSqlDao.create(invoiceItem, callContext);
+            invoiceItems.add(invoiceItem);
+        }
+        Mockito.when(invoice.getInvoiceItems()).thenReturn(invoiceItems);
+
+        invoiceSqlDao.create(invoice, callContext);
+
+        return invoice;
+    }
+
+    public static InvoiceItem createInvoiceItem(final Clock clock, final UUID invoiceId, final UUID accountId, final BigDecimal amount, final Currency currency) {
+        return new FixedPriceInvoiceItem(invoiceId, accountId, UUID.randomUUID(), UUID.randomUUID(),
+                                         "charge back test", "charge back phase", clock.getUTCToday(), amount, currency);
+    }
+
+    public static InvoicePayment createAndPersistPayment(final InvoicePaymentApi invoicePaymentApi,
+                                                         final Clock clock,
+                                                         final UUID invoiceId,
+                                                         final BigDecimal amount,
+                                                         final Currency currency,
+                                                         final CallContext callContext) {
+        final InvoicePayment payment = Mockito.mock(InvoicePayment.class);
+        Mockito.when(payment.getId()).thenReturn(UUID.randomUUID());
+        Mockito.when(payment.getType()).thenReturn(InvoicePaymentType.ATTEMPT);
+        Mockito.when(payment.getInvoiceId()).thenReturn(invoiceId);
+        Mockito.when(payment.getPaymentId()).thenReturn(UUID.randomUUID());
+        Mockito.when(payment.getPaymentCookieId()).thenReturn(UUID.randomUUID());
+        Mockito.when(payment.getPaymentDate()).thenReturn(clock.getUTCNow());
+        Mockito.when(payment.getAmount()).thenReturn(amount);
+        Mockito.when(payment.getCurrency()).thenReturn(currency);
+
+        invoicePaymentApi.notifyOfPayment(payment, callContext);
+
+        return payment;
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java b/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java
index ef43230..689b11a 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tests/TestChargeBacks.java
@@ -19,11 +19,9 @@ package com.ning.billing.invoice.tests;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
-import org.mockito.Mockito;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.exceptions.TransactionFailedException;
 import org.testng.annotations.BeforeSuite;
@@ -35,16 +33,14 @@ import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
 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.InvoicePayment;
-import com.ning.billing.invoice.api.InvoicePayment.InvoicePaymentType;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
 import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.InvoiceItemSqlDao;
 import com.ning.billing.invoice.dao.InvoiceSqlDao;
 import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
-import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.invoice.notification.MockNextBillingDatePoster;
 import com.ning.billing.invoice.notification.NextBillingDatePoster;
 import com.ning.billing.util.api.TagUserApi;
@@ -58,16 +54,20 @@ import com.ning.billing.util.tag.dao.MockTagDefinitionDao;
 import com.ning.billing.util.tag.dao.TagDao;
 import com.ning.billing.util.tag.dao.TagDefinitionDao;
 
+import static com.ning.billing.invoice.tests.InvoiceTestUtils.createAndPersistInvoice;
+import static com.ning.billing.invoice.tests.InvoiceTestUtils.createAndPersistPayment;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
 public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
+
     private static final BigDecimal FIFTEEN = new BigDecimal("15.00");
     private static final BigDecimal THIRTY = new BigDecimal("30.00");
     private static final BigDecimal ONE_MILLION = new BigDecimal("1000000.00");
     private InvoiceSqlDao invoiceSqlDao;
+    private InvoiceItemSqlDao invoiceItemSqlDao;
     private InvoicePaymentApi invoicePaymentApi;
     private CallContext context;
     private final Clock clock = new ClockMock();
@@ -83,6 +83,8 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
         invoiceSqlDao = dbi.onDemand(InvoiceSqlDao.class);
         invoiceSqlDao.test();
 
+        invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
+        invoiceItemSqlDao.test();
         final NextBillingDatePoster nextBillingDatePoster = new MockNextBillingDatePoster();
         final TagDefinitionDao tagDefinitionDao = new MockTagDefinitionDao();
         final TagDao tagDao = new MockTagDao();
@@ -105,8 +107,8 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testCompleteChargeBack() throws InvoiceApiException {
-        final Invoice invoice = createAndPersistInvoice(THIRTY);
-        final InvoicePayment payment = createAndPersistPayment(invoice.getId(), THIRTY);
+        final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock, THIRTY, CURRENCY, context);
+        final InvoicePayment payment = createAndPersistPayment(invoicePaymentApi, clock, invoice.getId(), THIRTY, CURRENCY, context);
 
         // create a full charge back
         invoicePaymentApi.createChargeback(payment.getId(), THIRTY, context);
@@ -118,8 +120,8 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testPartialChargeBack() throws InvoiceApiException {
-        final Invoice invoice = createAndPersistInvoice(THIRTY);
-        final InvoicePayment payment = createAndPersistPayment(invoice.getId(), THIRTY);
+        final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock, THIRTY, CURRENCY, context);
+        final InvoicePayment payment = createAndPersistPayment(invoicePaymentApi, clock, invoice.getId(), THIRTY, CURRENCY, context);
 
         // create a partial charge back
         invoicePaymentApi.createChargeback(payment.getId(), FIFTEEN, context);
@@ -132,8 +134,8 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
     @Test(groups = "slow", expectedExceptions = InvoiceApiException.class)
     public void testChargeBackLargerThanPaymentAmount() throws InvoiceApiException {
         try {
-            final Invoice invoice = createAndPersistInvoice(THIRTY);
-            final InvoicePayment payment = createAndPersistPayment(invoice.getId(), THIRTY);
+            final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock, THIRTY, CURRENCY, context);
+            final InvoicePayment payment = createAndPersistPayment(invoicePaymentApi, clock, invoice.getId(), THIRTY, CURRENCY, context);
 
             // create a large charge back
             invoicePaymentApi.createChargeback(payment.getId(), ONE_MILLION, context);
@@ -146,8 +148,8 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
     @Test(groups = "slow", expectedExceptions = InvoiceApiException.class)
     public void testNegativeChargeBackAmount() throws InvoiceApiException {
         try {
-            final Invoice invoice = createAndPersistInvoice(THIRTY);
-            final InvoicePayment payment = createAndPersistPayment(invoice.getId(), THIRTY);
+            final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock, THIRTY, CURRENCY, context);
+            final InvoicePayment payment = createAndPersistPayment(invoicePaymentApi, clock, invoice.getId(), THIRTY, CURRENCY, context);
 
             // create a partial charge back
             invoicePaymentApi.createChargeback(payment.getId(), BigDecimal.ONE.negate(), context);
@@ -158,8 +160,8 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testGetAccountIdFromPaymentIdHappyPath() throws InvoiceApiException {
-        final Invoice invoice = createAndPersistInvoice(THIRTY);
-        final InvoicePayment payment = createAndPersistPayment(invoice.getId(), THIRTY);
+        final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock, THIRTY, CURRENCY, context);
+        final InvoicePayment payment = createAndPersistPayment(invoicePaymentApi, clock, invoice.getId(), THIRTY, CURRENCY, context);
         final UUID accountId = invoicePaymentApi.getAccountIdFromInvoicePaymentId(payment.getId());
         assertEquals(accountId, invoice.getAccountId());
     }
@@ -178,8 +180,8 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testGetChargeBacksByAccountIdHappyPath() throws InvoiceApiException {
-        final Invoice invoice = createAndPersistInvoice(THIRTY);
-        final InvoicePayment payment = createAndPersistPayment(invoice.getId(), THIRTY);
+        final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock, THIRTY, CURRENCY, context);
+        final InvoicePayment payment = createAndPersistPayment(invoicePaymentApi, clock, invoice.getId(), THIRTY, CURRENCY, context);
 
         // create a partial charge back
         invoicePaymentApi.createChargeback(payment.getId(), FIFTEEN, context);
@@ -199,8 +201,8 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testGetChargeBacksByInvoicePaymentIdHappyPath() throws InvoiceApiException {
-        final Invoice invoice = createAndPersistInvoice(THIRTY);
-        final InvoicePayment payment = createAndPersistPayment(invoice.getId(), THIRTY);
+        final Invoice invoice = createAndPersistInvoice(invoiceSqlDao, invoiceItemSqlDao, clock, THIRTY, CURRENCY, context);
+        final InvoicePayment payment = createAndPersistPayment(invoicePaymentApi, clock, invoice.getId(), THIRTY, CURRENCY, context);
 
         // create a partial charge back
         invoicePaymentApi.createChargeback(payment.getId(), FIFTEEN, context);
@@ -210,45 +212,4 @@ public class TestChargeBacks extends InvoiceTestSuiteWithEmbeddedDB {
         assertEquals(chargebacks.size(), 1);
         assertEquals(chargebacks.get(0).getLinkedInvoicePaymentId(), payment.getId());
     }
-
-    private Invoice createAndPersistInvoice(final BigDecimal amount) {
-        final Invoice invoice = Mockito.mock(Invoice.class);
-        final UUID invoiceId = UUID.randomUUID();
-        final UUID accountId = UUID.randomUUID();
-
-        Mockito.when(invoice.getId()).thenReturn(invoiceId);
-        Mockito.when(invoice.getAccountId()).thenReturn(accountId);
-        Mockito.when(invoice.getInvoiceDate()).thenReturn(clock.getUTCToday());
-        Mockito.when(invoice.getTargetDate()).thenReturn(clock.getUTCToday());
-        Mockito.when(invoice.getCurrency()).thenReturn(CURRENCY);
-        Mockito.when(invoice.isMigrationInvoice()).thenReturn(false);
-
-        final List<InvoiceItem> items = new ArrayList<InvoiceItem>();
-        items.add(createInvoiceItem(invoiceId, accountId, amount));
-        Mockito.when(invoice.getInvoiceItems()).thenReturn(items);
-
-        invoiceSqlDao.create(invoice, context);
-
-        return invoice;
-    }
-
-    private InvoiceItem createInvoiceItem(final UUID invoiceId, final UUID accountId, final BigDecimal amount) {
-        return new FixedPriceInvoiceItem(invoiceId, accountId, UUID.randomUUID(), UUID.randomUUID(),
-                                         "charge back test", "charge back phase", clock.getUTCToday(), amount, CURRENCY);
-    }
-
-    private InvoicePayment createAndPersistPayment(final UUID invoiceId, final BigDecimal amount) {
-        final InvoicePayment payment = Mockito.mock(InvoicePayment.class);
-        Mockito.when(payment.getId()).thenReturn(UUID.randomUUID());
-        Mockito.when(payment.getType()).thenReturn(InvoicePaymentType.ATTEMPT);
-        Mockito.when(payment.getInvoiceId()).thenReturn(invoiceId);
-        Mockito.when(payment.getPaymentId()).thenReturn(UUID.randomUUID());
-        Mockito.when(payment.getPaymentDate()).thenReturn(clock.getUTCNow());
-        Mockito.when(payment.getAmount()).thenReturn(amount);
-        Mockito.when(payment.getCurrency()).thenReturn(CURRENCY);
-
-        invoicePaymentApi.notifyOfPayment(payment, context);
-
-        return payment;
-    }
 }
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/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 90be987..265a608 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
@@ -53,9 +53,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;
@@ -69,6 +71,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;
 
 @Path(JaxrsResource.INVOICES_PATH)
@@ -87,6 +90,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,
@@ -96,7 +100,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;
@@ -104,28 +109,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);
@@ -165,6 +181,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..3f49504 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
@@ -16,8 +16,11 @@
 
 package com.ning.billing.jaxrs.resources;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.ws.rs.Consumes;
@@ -37,6 +40,7 @@ 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.jaxrs.json.CustomFieldJson;
+import com.ning.billing.jaxrs.json.InvoiceItemJsonSimple;
 import com.ning.billing.jaxrs.json.RefundJson;
 import com.ning.billing.jaxrs.util.Context;
 import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
@@ -88,7 +92,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);
             }
         }));
 
@@ -109,7 +114,22 @@ public class PaymentResource extends JaxRsResourceBase {
         final Payment payment = paymentApi.getPayment(paymentUuid);
         final Account account = accountApi.getAccountById(payment.getAccountId());
 
-        final Refund result = paymentApi.createRefund(account, paymentUuid, json.getRefundAmount(), json.isAdjusted(), context.createContext(createdBy, reason, comment));
+        final Refund result;
+        if (json.isAdjusted()) {
+            if (json.getAdjustments() != null && json.getAdjustments().size() > 0) {
+                final Map<UUID, BigDecimal> adjustments = new HashMap<UUID, BigDecimal>();
+                for (final InvoiceItemJsonSimple item : json.getAdjustments()) {
+                    adjustments.put(UUID.fromString(item.getInvoiceItemId()), item.getAmount());
+                }
+                result = paymentApi.createRefundWithItemsAdjustments(account, paymentUuid, adjustments, context.createContext(createdBy, reason, comment));
+            } else {
+                // Invoice adjustment
+                result = paymentApi.createRefundWithAdjustment(account, paymentUuid, json.getRefundAmount(), context.createContext(createdBy, reason, comment));
+            }
+        } else {
+            // Refund without adjustment
+            result = paymentApi.createRefund(account, paymentUuid, json.getRefundAmount(), context.createContext(createdBy, reason, comment));
+        }
 
         return uriBuilder.buildResponse(RefundResource.class, "getRefund", result.getId(), uriInfo.getBaseUri().toString());
     }
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/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/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/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
index 9777935..19f0f28 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/DefaultBillingApi.java
@@ -113,9 +113,9 @@ public class DefaultBillingApi implements BillingApi {
 
 
     private void debugLog(final SortedSet<BillingEvent> result, final String title) {
-        log.debug(title);
+        log.info(title);
         for (final BillingEvent aResult : result) {
-            log.debug(aResult.toString());
+            log.info(aResult.toString());
         }
     }
 
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index b4631a2..a2c1f11 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -17,7 +17,9 @@
 package com.ning.billing.payment.api;
 
 import java.math.BigDecimal;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
@@ -28,6 +30,7 @@ import com.ning.billing.payment.core.PaymentProcessor;
 import com.ning.billing.payment.core.RefundProcessor;
 import com.ning.billing.util.callcontext.CallContext;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.inject.Inject;
 
 public class DefaultPaymentApi implements PaymentApi {
@@ -82,14 +85,34 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public Refund createRefund(final Account account, final UUID paymentId,
-                               final BigDecimal refundAmount, final boolean isAdjusted, final CallContext context)
-            throws PaymentApiException {
+    public Refund createRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final CallContext context) throws PaymentApiException {
+        if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL);
+        }
+        return refundProcessor.createRefund(account, paymentId, refundAmount, false, ImmutableMap.<UUID, BigDecimal>of(), context);
+    }
+
+    @Override
+    public Refund createRefundWithAdjustment(final Account account, final UUID paymentId, final BigDecimal refundAmount, final CallContext context) throws PaymentApiException {
         if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
             throw new PaymentApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL);
         }
-        return refundProcessor.createRefund(account, paymentId, refundAmount, isAdjusted, context);
+        return refundProcessor.createRefund(account, paymentId, refundAmount, true, ImmutableMap.<UUID, BigDecimal>of(), context);
+    }
+
+    @Override
+    public Refund createRefundWithItemsAdjustments(final Account account, final UUID paymentId, final Set<UUID> invoiceItemIds, final CallContext context) throws PaymentApiException {
+        final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts = new HashMap<UUID, BigDecimal>();
+        for (final UUID invoiceItemId : invoiceItemIds) {
+            invoiceItemIdsWithAmounts.put(invoiceItemId, null);
+        }
 
+        return refundProcessor.createRefund(account, paymentId, null, true, invoiceItemIdsWithAmounts, context);
+    }
+
+    @Override
+    public Refund createRefundWithItemsAdjustments(final Account account, final UUID paymentId, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final CallContext context) throws PaymentApiException {
+        return refundProcessor.createRefund(account, paymentId, null, true, invoiceItemIdsWithAmounts, context);
     }
 
     @Override
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..63706ba 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,36 +13,34 @@
  * 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;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 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.annotation.Nullable;
+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;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceItem;
 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,37 +55,59 @@ import com.ning.billing.util.callcontext.CallOrigin;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.globallocker.GlobalLocker;
 
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.name.Named;
+
 import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 
 public class RefundProcessor extends ProcessorBase {
 
-    private 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 {
+    /**
+     * Create a refund and adjust the invoice or invoice items as necessary.
+     *
+     * @param account                   account to refund
+     * @param paymentId                 payment associated with that refund
+     * @param specifiedRefundAmount     amount to refund. If null, the amount will be the sum of adjusted invoice items
+     * @param isAdjusted                whether the refund should trigger an invoice or invoice item adjustment
+     * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
+     * @param context                   the call context
+     * @return the created context
+     * @throws PaymentApiException
+     */
+    public Refund createRefund(final Account account, final UUID paymentId, @Nullable final BigDecimal specifiedRefundAmount,
+                               final boolean isAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final CallContext context)
+            throws PaymentApiException {
 
         return new WithAccountLock<Refund>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<Refund>() {
 
             @Override
             public Refund doOperation() throws PaymentApiException {
+                // First, compute the refund amount, if necessary
+                final BigDecimal refundAmount = computeRefundAmount(paymentId, specifiedRefundAmount, invoiceItemIdsWithAmounts);
+
                 try {
 
                     final PaymentModelDao payment = paymentDao.getPayment(paymentId);
@@ -104,8 +124,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 +152,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) {
@@ -143,7 +163,7 @@ public class RefundProcessor extends ProcessorBase {
                     }
                     paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.PLUGIN_COMPLETED, context);
 
-                    invoicePaymentApi.createRefund(paymentId, refundAmount, isAdjusted, refundInfo.getId(), context);
+                    invoicePaymentApi.createRefund(paymentId, refundAmount, isAdjusted, invoiceItemIdsWithAmounts, refundInfo.getId(), context);
 
                     paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.COMPLETED, context);
 
@@ -158,14 +178,48 @@ public class RefundProcessor extends ProcessorBase {
         });
     }
 
+    /**
+     * Compute the refund amount (computed from the invoice or invoice items as necessary).
+     *
+     * @param paymentId                 payment id associated with this refund
+     * @param specifiedRefundAmount     amount to refund. If null, the amount will be the sum of adjusted invoice items
+     * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
+     * @return the refund amount
+     */
+    private BigDecimal computeRefundAmount(final UUID paymentId, @Nullable final BigDecimal specifiedRefundAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) {
+        final List<InvoiceItem> items = invoicePaymentApi.getInvoiceForPaymentId(paymentId).getInvoiceItems();
+
+        BigDecimal amountFromItems = BigDecimal.ZERO;
+        for (final UUID itemId : invoiceItemIdsWithAmounts.keySet()) {
+            amountFromItems = amountFromItems.add(Objects.firstNonNull(invoiceItemIdsWithAmounts.get(itemId),
+                                                                       getAmountFromItem(items, itemId)));
+        }
+
+        // Sanity check: if some items were specified, then the sum should be equal to specified refund amount, if specified
+        if (amountFromItems.compareTo(BigDecimal.ZERO) != 0 && specifiedRefundAmount != null && specifiedRefundAmount.compareTo(amountFromItems) != 0) {
+            throw new IllegalArgumentException("You can't specify a refund amount that doesn't match the invoice items amounts");
+        }
+
+        return Objects.firstNonNull(specifiedRefundAmount, amountFromItems);
+    }
+
+    private BigDecimal getAmountFromItem(final List<InvoiceItem> items, final UUID itemId) {
+        for (final InvoiceItem item : items) {
+            if (item.getId().equals(itemId)) {
+                return item.getAmount();
+            }
+        }
+
+        throw new IllegalArgumentException("Unable to find invoice item for id " + itemId);
+    }
 
     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 +231,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 +264,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 +272,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,15 +283,16 @@ 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) {
-                            invoicePaymentApi.createRefund(cur.getPaymentId(), cur.getAmount(), cur.isAdjsuted(), cur.getId(), context);
+                        for (final RefundModelDao cur : refundsToBeFixed) {
+                            // TODO - we currently don't save the items to be adjusted. If we crash, they won't be adjusted...
+                            invoicePaymentApi.createRefund(cur.getPaymentId(), cur.getAmount(), cur.isAdjsuted(), ImmutableMap.<UUID, BigDecimal>of(), cur.getId(), context);
                             paymentDao.updateRefundStatus(cur.getId(), RefundStatus.COMPLETED, context);
                         }
                     } catch (InvoiceApiException e) {
@@ -253,15 +306,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/TestBundle.java b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
index 94ae280..5a0945e 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
@@ -25,6 +25,7 @@ import java.util.Map;
 import javax.ws.rs.core.Response.Status;
 
 import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -125,7 +126,7 @@ public class TestBundle extends TestJaxrsBase {
 
         final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), productName, ProductCategory.BASE.toString(), term.toString(), true);
         Assert.assertNotNull(subscriptionJson.getChargedThroughDate());
-        Assert.assertEquals(subscriptionJson.getChargedThroughDate().toString(), "2012-04-25T00:00:00.000Z");
+        Assert.assertEquals(subscriptionJson.getChargedThroughDate().toLocalDate(), new LocalDate("2012-04-25"));
 
         final AccountJson newAccount = createAccountWithDefaultPaymentMethod("dst", "dst", "dst@yahoo.com");
 
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..c5334d9 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -13,12 +13,12 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.jaxrs;
 
-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;
@@ -30,30 +30,30 @@ import java.util.Map.Entry;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
+import javax.annotation.Nullable;
+import javax.ws.rs.core.Response.Status;
+
 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;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
 import org.testng.annotations.AfterSuite;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.BeforeSuite;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.datatype.joda.JodaModule;
-import com.google.inject.Module;
 import com.ning.billing.KillbillTestSuiteWithEmbeddedDB;
-import com.ning.billing.account.api.BillCycleDay;
 import com.ning.billing.account.glue.AccountModule;
 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,9 +66,14 @@ 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;
+import com.ning.billing.jaxrs.json.RefundJson;
 import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
 import com.ning.billing.jaxrs.resources.JaxrsResource;
 import com.ning.billing.junction.glue.DefaultJunctionModule;
@@ -88,7 +93,6 @@ import com.ning.billing.util.glue.CustomFieldModule;
 import com.ning.billing.util.glue.GlobalLockerModule;
 import com.ning.billing.util.glue.NotificationQueueModule;
 import com.ning.billing.util.glue.TagStoreModule;
-import com.ning.billing.util.io.IOUtils;
 import com.ning.http.client.AsyncCompletionHandler;
 import com.ning.http.client.AsyncHttpClient;
 import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
@@ -98,9 +102,19 @@ import com.ning.http.client.Response;
 import com.ning.jetty.core.CoreConfig;
 import com.ning.jetty.core.server.HttpServer;
 
+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 static com.ning.billing.jaxrs.resources.JaxrsResource.QUERY_PAYMENT_METHOD_PLUGIN_INFO;
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 
 public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
+
     protected static final String PLUGIN_NAME = "noop";
 
     // STEPH
@@ -140,6 +154,7 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
     }
 
     public static class TestKillbillGuiceListener extends KillbillGuiceListener {
+
         private final MysqlTestingHelper helper;
         private final Clock clock;
 
@@ -169,6 +184,7 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
     }
 
     public static class InvoiceModuleWithMockSender extends DefaultInvoiceModule {
+
         @Override
         protected void installInvoiceNotifier() {
             bind(InvoiceNotifier.class).to(NullInvoiceNotifier.class).asEagerSingleton();
@@ -176,6 +192,7 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
     }
 
     public static class TestKillbillServerModule extends KillbillServerModule {
+
         private final MysqlTestingHelper helper;
         private final Clock clock;
 
@@ -191,6 +208,7 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         }
 
         private static final class PaymentMockModule extends PaymentModule {
+
             @Override
             protected void installPaymentProviderPlugins(final PaymentConfig config) {
                 install(new MockPaymentProviderPluginModule(PLUGIN_NAME));
@@ -311,7 +329,6 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         return properties;
     }
 
-
     protected List<PaymentMethodProperties> getPaymentMethodPaypalProperties() {
         final List<PaymentMethodProperties> properties = new ArrayList<PaymentMethodJson.PaymentMethodProperties>();
         properties.add(new PaymentMethodProperties("type", "CreditCard", false));
@@ -320,12 +337,15 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         return properties;
     }
 
-
     protected PaymentMethodJson getPaymentMethodJson(final String accountId, final List<PaymentMethodProperties> properties) {
         final PaymentMethodPluginDetailJson info = new PaymentMethodPluginDetailJson(null, properties);
         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);
@@ -339,7 +359,6 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         Response response = doPost(uri, baseJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
         Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
 
-
         queryParams = new HashMap<String, String>();
         queryParams.put(JaxrsResource.QUERY_EXTERNAL_KEY, input.getExternalKey());
         response = doGet(JaxrsResource.ACCOUNTS_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
@@ -370,7 +389,6 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         return objFromJson;
     }
 
-
     protected BundleJsonNoSubscriptions createBundle(final String accountId, final String key) throws Exception {
         final BundleJsonNoSubscriptions input = new BundleJsonNoSubscriptions(null, accountId, key, null, null);
         String baseJson = mapper.writeValueAsString(input);
@@ -395,7 +413,6 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         final SubscriptionJsonNoEvents input = new SubscriptionJsonNoEvents(null, bundleId, null, productName, productCategory, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null, null);
         String baseJson = mapper.writeValueAsString(input);
 
-
         final Map<String, String> queryParams = waitCompletion ? getQueryParamsForCallCompletion("5") : DEFAULT_EMPTY_QUERY;
         Response response = doPost(JaxrsResource.SUBSCRIPTIONS_PATH, baseJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
         Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
@@ -414,6 +431,273 @@ 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 {
+        return doGetInvoice(invoiceId, Boolean.FALSE, InvoiceJsonSimple.class);
+    }
+
+    protected InvoiceJsonWithItems getInvoiceWithItems(final String invoiceId) throws IOException {
+        return doGetInvoice(invoiceId, Boolean.TRUE, InvoiceJsonWithItems.class);
+    }
+
+    private <T> T doGetInvoice(final String invoiceId, final Boolean withItems, final Class<T> clazz) throws IOException {
+        final String uri = JaxrsResource.INVOICES_PATH + "/" + invoiceId;
+
+        final Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(JaxrsResource.QUERY_INVOICE_WITH_ITEMS, withItems.toString());
+
+        final Response response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        final String baseJson = response.getResponseBody();
+
+        final T firstInvoiceJson = mapper.readValue(baseJson, clazz);
+        assertNotNull(firstInvoiceJson);
+
+        return firstInvoiceJson;
+    }
+
+    protected List<InvoiceJsonSimple> getInvoicesForAccount(final String accountId) throws IOException {
+        return doGetInvoicesForAccount(accountId, Boolean.FALSE, InvoiceJsonSimple.class);
+    }
+
+    protected List<InvoiceJsonWithItems> getInvoicesWithItemsForAccount(final String accountId) throws IOException {
+        return doGetInvoicesForAccount(accountId, Boolean.TRUE, InvoiceJsonWithItems.class);
+    }
+
+    private <T> List<T> doGetInvoicesForAccount(final String accountId, final Boolean withItems, final Class<T> clazz) 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, withItems.toString());
+
+        final Response invoicesResponse = doGet(invoicesURI, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(invoicesResponse.getStatusCode(), Status.OK.getStatusCode());
+
+        final String invoicesBaseJson = invoicesResponse.getResponseBody();
+        final List<T> invoices = mapper.readValue(invoicesBaseJson, new TypeReference<List<T>>() {});
+        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 PaymentMethodJson getPaymentMethodWithPluginInfo(final String paymentMethodId) throws IOException {
+        final String paymentMethodURI = JaxrsResource.PAYMENT_METHODS_PATH + "/" + paymentMethodId;
+
+        final Map<String, String> queryPaymentMethods = new HashMap<String, String>();
+        queryPaymentMethods.put(QUERY_PAYMENT_METHOD_PLUGIN_INFO, "true");
+        final Response paymentMethodResponse = doGet(paymentMethodURI, queryPaymentMethods, 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);
+    }
+
+    //
+    // REFUNDS
+    //
+
+    protected List<RefundJson> getRefundsForPayment(final String paymentId) throws IOException {
+        final String uri = JaxrsResource.PAYMENTS_PATH + "/" + paymentId + "/" + JaxrsResource.REFUNDS;
+        final Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        final String baseJson = response.getResponseBody();
+        final List<RefundJson> refunds = mapper.readValue(baseJson, new TypeReference<List<RefundJson>>() {});
+        assertNotNull(refunds);
+
+        return refunds;
+    }
+
+    protected RefundJson createRefund(final String paymentId, final BigDecimal amount) throws IOException {
+        return doCreateRefund(paymentId, amount, false, ImmutableMap.<String, BigDecimal>of());
+    }
+
+    protected RefundJson createRefundWithInvoiceAdjustment(final String paymentId, final BigDecimal amount) throws IOException {
+        return doCreateRefund(paymentId, amount, true, ImmutableMap.<String, BigDecimal>of());
+    }
+
+    protected RefundJson createRefundWithInvoiceItemAdjustment(final String paymentId, final String invoiceItemId, final BigDecimal amount) throws IOException {
+        final Map<String, BigDecimal> adjustments = new HashMap<String, BigDecimal>();
+        adjustments.put(invoiceItemId, amount);
+        return doCreateRefund(paymentId, amount, true, adjustments);
+    }
+
+    private RefundJson doCreateRefund(final String paymentId, final BigDecimal amount, final boolean adjusted, final Map<String, BigDecimal> itemAdjustments) throws IOException {
+        final String uri = JaxrsResource.PAYMENTS_PATH + "/" + paymentId + "/" + JaxrsResource.REFUNDS;
+
+        final List<InvoiceItemJsonSimple> adjustments = new ArrayList<InvoiceItemJsonSimple>();
+        for (final String itemId : itemAdjustments.keySet()) {
+            adjustments.add(new InvoiceItemJsonSimple(itemId, null, null, null, null, null, null, null, null, null,
+                                                      itemAdjustments.get(itemId), null, null));
+        }
+        final RefundJson refundJson = new RefundJson(null, paymentId, amount, adjusted, null, null, adjustments, null);
+        final String baseJson = mapper.writeValueAsString(refundJson);
+        final Response response = doPost(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        final String locationCC = response.getHeader("Location");
+        Assert.assertNotNull(locationCC);
+
+        // Retrieves by Id based on Location returned
+        final Response retrievedResponse = doGetWithUrl(locationCC, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(retrievedResponse.getStatusCode(), Status.OK.getStatusCode());
+        final String retrievedBaseJson = retrievedResponse.getResponseBody();
+        final RefundJson retrievedRefundJson = mapper.readValue(retrievedBaseJson, RefundJson.class);
+        assertNotNull(retrievedRefundJson);
+
+        return retrievedRefundJson;
+    }
+
     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..14c4ae9 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestPayment.java
@@ -16,96 +16,160 @@
 
 package com.ning.billing.jaxrs;
 
+import java.io.IOException;
 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.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.RefundJson;
-import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
-import com.ning.billing.jaxrs.resources.JaxrsResource;
-import com.ning.http.client.Response;
 
-import com.fasterxml.jackson.core.type.TypeReference;
+public class TestPayment extends TestJaxrsBase {
+
+    @Test(groups = "slow")
+    public void testFullRefundWithNoAdjustment() throws Exception {
+        final PaymentJsonSimple paymentJsonSimple = setupScenarioWithPayment();
 
-import static com.ning.billing.jaxrs.resources.JaxrsResource.QUERY_PAYMENT_METHOD_PLUGIN_INFO;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
+        // Issue a refund for the full amount
+        final BigDecimal refundAmount = paymentJsonSimple.getAmount();
+        final BigDecimal expectedInvoiceBalance = refundAmount;
 
-public class TestPayment extends TestJaxrsBase {
+        // Post and verify the refund
+        final RefundJson refundJsonCheck = createRefund(paymentJsonSimple.getPaymentId(), refundAmount);
+        verifyRefund(paymentJsonSimple, refundJsonCheck, refundAmount);
+
+        // Verify the invoice balance
+        verifyInvoice(paymentJsonSimple, expectedInvoiceBalance);
+    }
+
+    @Test(groups = "slow")
+    public void testPartialRefundWithNoAdjustment() throws Exception {
+        final PaymentJsonSimple paymentJsonSimple = setupScenarioWithPayment();
+
+        // Issue a refund for a fraction of the amount
+        final BigDecimal refundAmount = getFractionOfAmount(paymentJsonSimple.getAmount());
+        final BigDecimal expectedInvoiceBalance = refundAmount;
+
+        // Post and verify the refund
+        final RefundJson refundJsonCheck = createRefund(paymentJsonSimple.getPaymentId(), refundAmount);
+        verifyRefund(paymentJsonSimple, refundJsonCheck, refundAmount);
+
+        // Verify the invoice balance
+        verifyInvoice(paymentJsonSimple, expectedInvoiceBalance);
+    }
+
+    @Test(groups = "slow")
+    public void testFullRefundWithInvoiceAdjustment() throws Exception {
+        final PaymentJsonSimple paymentJsonSimple = setupScenarioWithPayment();
+
+        // Issue a refund for the full amount
+        final BigDecimal refundAmount = paymentJsonSimple.getAmount();
+        final BigDecimal expectedInvoiceBalance = BigDecimal.ZERO;
+
+        // Post and verify the refund
+        final RefundJson refundJsonCheck = createRefundWithInvoiceAdjustment(paymentJsonSimple.getPaymentId(), refundAmount);
+        verifyRefund(paymentJsonSimple, refundJsonCheck, refundAmount);
+
+        // Verify the invoice balance
+        verifyInvoice(paymentJsonSimple, expectedInvoiceBalance);
+    }
+
+    @Test(groups = "slow")
+    public void testPartialRefundWithInvoiceAdjustment() throws Exception {
+        final PaymentJsonSimple paymentJsonSimple = setupScenarioWithPayment();
+
+        // Issue a refund for a fraction of the amount
+        final BigDecimal refundAmount = getFractionOfAmount(paymentJsonSimple.getAmount());
+        final BigDecimal expectedInvoiceBalance = BigDecimal.ZERO;
+
+        // Post and verify the refund
+        final RefundJson refundJsonCheck = createRefundWithInvoiceAdjustment(paymentJsonSimple.getPaymentId(), refundAmount);
+        verifyRefund(paymentJsonSimple, refundJsonCheck, refundAmount);
+
+        // Verify the invoice balance
+        verifyInvoice(paymentJsonSimple, expectedInvoiceBalance);
+    }
 
     @Test(groups = "slow")
-    public void testPaymentWithRefund() throws Exception {
-        final AccountJson accountJson = createAccountWithDefaultPaymentMethod("eraahahildo", "sheqrgfhwe", "eraahahildo@yahoo.com");
-        assertNotNull(accountJson);
+    public void testRefundWithFullInvoiceItemAdjustment() throws Exception {
+        final PaymentJsonSimple paymentJsonSimple = setupScenarioWithPayment();
 
-        final BundleJsonNoSubscriptions bundleJson = createBundle(accountJson.getAccountId(), "317199");
-        assertNotNull(bundleJson);
+        // Get the individual items for the invoice
+        final InvoiceJsonWithItems invoice = getInvoiceWithItems(paymentJsonSimple.getInvoiceId());
+        final InvoiceItemJsonSimple itemToAdjust = invoice.getItems().get(0);
 
-        final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
-        assertNotNull(subscriptionJson);
+        // Issue a refund for the full amount
+        final BigDecimal refundAmount = itemToAdjust.getAmount();
+        final BigDecimal expectedInvoiceBalance = BigDecimal.ZERO;
 
-        clock.addMonths(1);
-        crappyWaitForLackOfProperSynchonization();
+        // Post and verify the refund
+        final RefundJson refundJsonCheck = createRefundWithInvoiceItemAdjustment(paymentJsonSimple.getPaymentId(),
+                                                                                 itemToAdjust.getInvoiceItemId(),
+                                                                                 null /* null means full adjustment for that item */);
+        verifyRefund(paymentJsonSimple, refundJsonCheck, refundAmount);
 
-        String uri = JaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId() + "/" + JaxrsResource.PAYMENTS;
+        // Verify the invoice balance
+        verifyInvoice(paymentJsonSimple, expectedInvoiceBalance);
+    }
 
-        Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        String baseJson = response.getResponseBody();
-        final List<PaymentJsonSimple> objFromJson = mapper.readValue(baseJson, new TypeReference<List<PaymentJsonSimple>>() {});
-        Assert.assertEquals(objFromJson.size(), 1);
+    @Test(groups = "slow")
+    public void testPartialRefundWithInvoiceItemAdjustment() throws Exception {
+        final PaymentJsonSimple paymentJsonSimple = setupScenarioWithPayment();
 
-        final String paymentId = objFromJson.get(0).getPaymentId();
-        final BigDecimal paymentAmount = objFromJson.get(0).getAmount();
+        // Get the individual items for the invoice
+        final InvoiceJsonWithItems invoice = getInvoiceWithItems(paymentJsonSimple.getInvoiceId());
+        final InvoiceItemJsonSimple itemToAdjust = invoice.getItems().get(0);
+
+        // Issue a refund for a fraction of the amount
+        final BigDecimal refundAmount = getFractionOfAmount(itemToAdjust.getAmount());
+        final BigDecimal expectedInvoiceBalance = BigDecimal.ZERO;
+
+        // Post and verify the refund
+        final RefundJson refundJsonCheck = createRefundWithInvoiceItemAdjustment(paymentJsonSimple.getPaymentId(),
+                                                                                 itemToAdjust.getInvoiceItemId(),
+                                                                                 refundAmount);
+        verifyRefund(paymentJsonSimple, refundJsonCheck, refundAmount);
+
+        // Verify the invoice balance
+        verifyInvoice(paymentJsonSimple, expectedInvoiceBalance);
+    }
+
+    private BigDecimal getFractionOfAmount(final BigDecimal amount) {
+        return amount.divide(BigDecimal.TEN).setScale(2, BigDecimal.ROUND_HALF_UP);
+    }
+
+    private PaymentJsonSimple setupScenarioWithPayment() throws Exception {
+        final AccountJson accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        final List<PaymentJsonSimple> firstPaymentForAccount = getPaymentsForAccount(accountJson.getAccountId());
+        Assert.assertEquals(firstPaymentForAccount.size(), 1);
+
+        final PaymentJsonSimple paymentJsonSimple = firstPaymentForAccount.get(0);
 
         // Check the PaymentMethod from paymentMethodId returned in the Payment object
-        final String paymentMethodId = objFromJson.get(0).getPaymentMethodId();
-        uri = JaxrsResource.PAYMENT_METHODS_PATH + "/" + paymentMethodId;
-
-        final Map<String, String> queryPaymentMethods = new HashMap<String, String>();
-        queryPaymentMethods.put(QUERY_PAYMENT_METHOD_PLUGIN_INFO, "true");
-        response = doGet(uri, queryPaymentMethods, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        final PaymentMethodJson paymentMethodJson = mapper.readValue(response.getResponseBody(), PaymentMethodJson.class);
+        final String paymentMethodId = paymentJsonSimple.getPaymentMethodId();
+        final PaymentMethodJson paymentMethodJson = getPaymentMethodWithPluginInfo(paymentMethodId);
         Assert.assertEquals(paymentMethodJson.getPaymentMethodId(), paymentMethodId);
         Assert.assertEquals(paymentMethodJson.getAccountId(), accountJson.getAccountId());
         Assert.assertNotNull(paymentMethodJson.getPluginInfo().getExternalPaymentId());
 
-        uri = JaxrsResource.PAYMENTS_PATH + "/" + paymentId + "/" + JaxrsResource.REFUNDS;
-        response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        baseJson = response.getResponseBody();
-        List<RefundJson> objRefundFromJson = mapper.readValue(baseJson, new TypeReference<List<RefundJson>>() {});
+        // Verify the refunds
+        final List<RefundJson> objRefundFromJson = getRefundsForPayment(paymentJsonSimple.getPaymentId());
         Assert.assertEquals(objRefundFromJson.size(), 0);
+        return paymentJsonSimple;
+    }
 
-        // Issue the refund
-
-        final RefundJson refundJson = new RefundJson(null, paymentId, paymentAmount, false, null, null, null);
-        baseJson = mapper.writeValueAsString(refundJson);
-        response = doPost(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
-
-        final String locationCC = response.getHeader("Location");
-        Assert.assertNotNull(locationCC);
-
-        // Retrieves by Id based on Location returned
-        response = doGetWithUrl(locationCC, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        baseJson = response.getResponseBody();
-        final RefundJson refundJsonCheck = mapper.readValue(baseJson, RefundJson.class);
-        Assert.assertTrue(refundJsonCheck.equalsNoIdNoDates(refundJson));
+    private void verifyRefund(final PaymentJsonSimple paymentJsonSimple, final RefundJson refundJsonCheck, final BigDecimal refundAmount) throws IOException {
+        Assert.assertEquals(refundJsonCheck.getPaymentId(), paymentJsonSimple.getPaymentId());
+        Assert.assertEquals(refundJsonCheck.getRefundAmount().setScale(2, RoundingMode.HALF_UP), refundAmount.setScale(2, RoundingMode.HALF_UP));
         Assert.assertEquals(refundJsonCheck.getEffectiveDate().getYear(), clock.getUTCNow().getYear());
         Assert.assertEquals(refundJsonCheck.getEffectiveDate().getMonthOfYear(), clock.getUTCNow().getMonthOfYear());
         Assert.assertEquals(refundJsonCheck.getEffectiveDate().getDayOfMonth(), clock.getUTCNow().getDayOfMonth());
@@ -113,11 +177,14 @@ public class TestPayment extends TestJaxrsBase {
         Assert.assertEquals(refundJsonCheck.getRequestedDate().getMonthOfYear(), clock.getUTCNow().getMonthOfYear());
         Assert.assertEquals(refundJsonCheck.getRequestedDate().getDayOfMonth(), clock.getUTCNow().getDayOfMonth());
 
-        uri = JaxrsResource.PAYMENTS_PATH + "/" + paymentId + "/" + JaxrsResource.REFUNDS;
-        response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-        baseJson = response.getResponseBody();
-        objRefundFromJson = mapper.readValue(baseJson, new TypeReference<List<RefundJson>>() {});
-        Assert.assertEquals(objRefundFromJson.size(), 1);
+        // Verify the refunds
+        final List<RefundJson> retrievedRefunds = getRefundsForPayment(paymentJsonSimple.getPaymentId());
+        Assert.assertEquals(retrievedRefunds.size(), 1);
+    }
+
+    private void verifyInvoice(final PaymentJsonSimple paymentJsonSimple, final BigDecimal expectedInvoiceBalance) throws IOException {
+        final InvoiceJsonSimple invoiceJsonSimple = getInvoice(paymentJsonSimple.getInvoiceId());
+        Assert.assertEquals(invoiceJsonSimple.getBalance().setScale(2, BigDecimal.ROUND_HALF_UP),
+                            expectedInvoiceBalance.setScale(2, BigDecimal.ROUND_HALF_UP));
     }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
index da9b666..9465e90 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
@@ -23,6 +23,7 @@ import javax.ws.rs.core.Response.Status;
 
 import org.joda.time.DateTime;
 import org.joda.time.Interval;
+import org.joda.time.LocalDate;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -57,7 +58,7 @@ public class TestSubscription extends TestJaxrsBase {
 
         final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), productName, ProductCategory.BASE.toString(), term.toString(), true);
         Assert.assertNotNull(subscriptionJson.getChargedThroughDate());
-        Assert.assertEquals(subscriptionJson.getChargedThroughDate().toString(), "2012-04-25T00:00:00.000Z");
+        Assert.assertEquals(subscriptionJson.getChargedThroughDate().toLocalDate(), new LocalDate("2012-04-25"));
 
         String uri = JaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId();
 
@@ -144,7 +145,7 @@ public class TestSubscription extends TestJaxrsBase {
 
         final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), productName, ProductCategory.BASE.toString(), term.toString(), true);
         Assert.assertNotNull(subscriptionJson.getChargedThroughDate());
-        Assert.assertEquals(subscriptionJson.getChargedThroughDate().toString(), "2012-04-25T00:00:00.000Z");
+        Assert.assertEquals(subscriptionJson.getChargedThroughDate().toLocalDate(), new LocalDate("2012-04-25"));
 
         final String uri = JaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId();