killbill-uncached

Changes

pom.xml 2(+1 -1)

Details

diff --git a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
index b50879e..6c44797 100644
--- a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
@@ -43,8 +43,6 @@ public interface InvoiceInternalApi {
 
     public InvoicePayment getInvoicePaymentForAttempt(UUID paymentId, InternalTenantContext context) throws InvoiceApiException;
 
-    public InvoicePayment getInvoicePaymentForRefund(UUID paymentId, InternalTenantContext context) throws InvoiceApiException;
-
     public InvoicePayment getInvoicePaymentForChargeback(UUID paymentId, InternalTenantContext context) throws InvoiceApiException;
 
     public Invoice getInvoiceForPaymentId(UUID paymentId, InternalTenantContext context) throws InvoiceApiException;
diff --git a/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java b/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java
index b548665..07537b0 100644
--- a/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java
+++ b/api/src/main/java/org/killbill/billing/junction/DefaultBlockingState.java
@@ -29,9 +29,6 @@ import org.killbill.clock.Clock;
 
 public class DefaultBlockingState extends EntityBase implements BlockingState {
 
-    public static final String CLEAR_STATE_NAME = "__KILLBILL__CLEAR__OVERDUE_STATE__";
-
-    private static BlockingState clearState = null;
 
     private final UUID blockedId;
     private final String stateName;
@@ -43,13 +40,6 @@ public class DefaultBlockingState extends EntityBase implements BlockingState {
     private final BlockingStateType type;
     private final Long totalOrdering;
 
-    public static BlockingState getClearState(final BlockingStateType type, final String serviceName, final Clock clock) {
-        if (clearState == null) {
-            clearState = new DefaultBlockingState(null, type, CLEAR_STATE_NAME, serviceName, false, false, false, clock.getUTCNow());
-        }
-        return clearState;
-    }
-
     // Used by the DAO
     public DefaultBlockingState(final UUID id,
                                 final UUID blockedId,
diff --git a/api/src/main/java/org/killbill/billing/util/UUIDs.java b/api/src/main/java/org/killbill/billing/util/UUIDs.java
index 9a89bdd..c1fc204 100644
--- a/api/src/main/java/org/killbill/billing/util/UUIDs.java
+++ b/api/src/main/java/org/killbill/billing/util/UUIDs.java
@@ -1,5 +1,6 @@
 /*
- * Copyright 2015 The Billing Project, LLC
+ * Copyright 2015-2016 Groupon, Inc
+ * Copyright 2015-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -30,6 +31,14 @@ public abstract class UUIDs {
 
     public static UUID randomUUID() { return rndUUIDv4(); }
 
+    public static void setRandom(final Random random) {
+        threadRandom.set(random);
+    }
+
+    public static Random getRandom() {
+        return threadRandom.get();
+    }
+
     private static UUID rndUUIDv4() {
         // ~ return UUID.randomUUID() :
         final Random random = threadRandom.get();
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestBillingAlignment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestBillingAlignment.java
index 086fab5..9ee5303 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestBillingAlignment.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestBillingAlignment.java
@@ -21,8 +21,6 @@ package org.killbill.billing.beatrix.integration.overdue;
 import java.math.BigDecimal;
 
 import org.joda.time.LocalDate;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.beatrix.integration.TestIntegrationBase;
@@ -31,22 +29,18 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.subscription.api.user.SubscriptionBaseTransition;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertNotNull;
 
 public class TestBillingAlignment extends TestIntegrationBase {
 
-    // TODO test fails as it should not create a proration when the chnage to annual occurs. Instaed we should restart from the data of the chnage
-    // since we have as a catalog rule:
-    // <billingAlignmentCase>
-    // <billingPeriod>ANNUAL</billingPeriod>
-    // <alignment>SUBSCRIPTION</alignment>
-    // </billingAlignmentCase>
-    //
-    @Test(groups = "slow", enabled = false)
-    public void testTransitonAccountBAToSubscriptionBA() throws Exception {
-
-        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+    @Test(groups = "slow")
+    public void testTransitionAccountBAToSubscriptionBA() throws Exception {
+        // Set the BCD to the 25th
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(25));
 
         // We take april as it has 30 days (easier to play with BCD)
         // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
@@ -54,17 +48,44 @@ public class TestBillingAlignment extends TestIntegrationBase {
 
         //
         // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
-        // (Start with monthly that has a 'Account' billing alignment
+        // (Start with monthly that has an 'Account' billing alignment)
         //
         final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
         assertNotNull(bpEntitlement);
+
         invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(bpEntitlement.getId(), new LocalDate(2012, 4, 1), callContext);
 
-        // GET OUT TRIAL
+        // GET OUT TRIAL (moving clock to 2012-05-04)
         addDaysAndCheckForCompletion(33, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
 
-        //
-        // Change plan to annual that has been configured to have a 'SubscriptionBase' billing alignment
-        changeEntitlementAndCheckForCompletion(bpEntitlement, "Shotgun", BillingPeriod.ANNUAL, null, NextEvent.CHANGE, NextEvent.INVOICE);
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 25), InvoiceItemType.RECURRING, new BigDecimal("199.96")));
+        invoiceChecker.checkChargedThroughDate(bpEntitlement.getId(), new LocalDate(2012, 5, 25), callContext);
+
+        // Change plan to annual that has been configured to have a 'Subscription' billing alignment
+        final DefaultEntitlement changedBpEntitlement = changeEntitlementAndCheckForCompletion(bpEntitlement, "Shotgun", BillingPeriod.ANNUAL, null, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        invoiceChecker.checkInvoice(account.getId(),
+                                    3,
+                                    callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 4), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2380.22")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 4), new LocalDate(2012, 5, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-174.97")));
+        invoiceChecker.checkChargedThroughDate(bpEntitlement.getId(), new LocalDate(2013, 5, 1), callContext);
+
+        Assert.assertEquals(changedBpEntitlement.getSubscriptionBase().getAllTransitions().size(), 3);
+
+        final SubscriptionBaseTransition trial = changedBpEntitlement.getSubscriptionBase().getAllTransitions().get(0);
+        Assert.assertEquals(trial.getEffectiveTransitionTime().toLocalDate().compareTo(new LocalDate(2012, 4, 1)), 0);
+        Assert.assertEquals(trial.getNextPhase().getName(), "shotgun-monthly-trial");
+
+        final SubscriptionBaseTransition smEvergreen = changedBpEntitlement.getSubscriptionBase().getAllTransitions().get(1);
+        Assert.assertEquals(smEvergreen.getEffectiveTransitionTime().toLocalDate().compareTo(new LocalDate(2012, 5, 1)), 0);
+        Assert.assertEquals(smEvergreen.getNextPhase().getName(), "shotgun-monthly-evergreen");
+
+        final SubscriptionBaseTransition saEvergreen = changedBpEntitlement.getSubscriptionBase().getAllTransitions().get(2);
+        // Verify the IMMEDIATE policy
+        Assert.assertEquals(saEvergreen.getEffectiveTransitionTime().toLocalDate().compareTo(new LocalDate(2012, 5, 4)), 0);
+        // Verify the START_OF_SUBSCRIPTION alignment (both plans have the same 30 days trial)
+        Assert.assertEquals(saEvergreen.getNextPhase().getName(), "shotgun-annual-evergreen");
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
index ccc25d5..5f5b4e2 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
@@ -27,11 +27,12 @@ import org.killbill.billing.account.api.Account;
 import org.killbill.billing.beatrix.integration.BeatrixIntegrationModule;
 import org.killbill.billing.beatrix.integration.TestIntegrationBase;
 import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.api.SubscriptionBundle;
 import org.killbill.billing.overdue.OverdueService;
-import org.killbill.billing.overdue.api.DefaultOverdueInternalApi;
 import org.killbill.billing.overdue.config.DefaultOverdueConfig;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
 import org.killbill.billing.payment.api.TestPaymentMethodPluginBase;
 import org.killbill.xmlloader.XMLLoader;
@@ -81,11 +82,15 @@ public abstract class TestOverdueBase extends TestIntegrationBase {
             await().atMost(10, SECONDS).until(new Callable<Boolean>() {
                 @Override
                 public Boolean call() throws Exception {
-                    return expected.equals(blockingApi.getBlockingStateForService(account.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext).getStateName());
+                    final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(account.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
+                    final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
+                    return expected.equals(stateName);
                 }
             });
         } catch (final Exception e) {
-            Assert.assertEquals(blockingApi.getBlockingStateForService(account.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext).getStateName(), expected, "Got exception: " + e.toString());
+            final BlockingState blockingStateForService = blockingApi.getBlockingStateForService(account.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext);
+            final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
+            Assert.assertEquals(stateName, expected, "Got exception: " + e.toString());
         }
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index a974451..b97040b 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -45,7 +45,7 @@ import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
-import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.testng.annotations.Test;
@@ -136,15 +136,15 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // 2012-06-08 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-16 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-24 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-30 => P1
         addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
@@ -223,15 +223,15 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // 2012-06-08 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-16 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-24 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-30 => P1
         addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
@@ -317,15 +317,15 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // 2012-06-08 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-16 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-24 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-30
         addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
@@ -417,15 +417,15 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // 2012-06-08 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-16 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-24 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-30
         addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
@@ -506,15 +506,15 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // 2012-06-08 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-16 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-24 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-30 => OD1
         addDaysAndCheckForCompletion(6, NextEvent.BLOCK);
@@ -585,13 +585,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-15 => DAY 45 - 15 days after invoice
         addDaysAndCheckForCompletion(15);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-07-05 => DAY 65 - 35 days after invoice
         // Single PAYMENT_ERROR here here triggered by the invoice
@@ -686,7 +686,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         // Should still be in clear state - the invoice for the bundle has been paid, but not the invoice with the external charge
         // We refresh overdue just to be safe, see below
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-06 => Past 30 days since the external charge
         addDaysAndCheckForCompletion(6, NextEvent.BLOCK);
@@ -697,7 +697,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         final Invoice externalChargeInvoice = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).iterator().next();
         createExternalPaymentAndCheckForCompletion(account, externalChargeInvoice, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
         // We should be clear now
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
     }
 
     @Test(groups = "slow", description = "Test overdue after refund with no adjustment")
@@ -719,13 +719,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-15 => DAY 45 - 15 days after invoice
         addDaysAndCheckForCompletion(15);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-07-05 => DAY 65 - 35 days after invoice
         addDaysAndCheckForCompletion(20, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -734,7 +734,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // Now, refund the second (first non-zero dollar) invoice
         final Payment payment = paymentApi.getPayment(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).get(1).getPayments().get(0).getPaymentId(), false, PLUGIN_PROPERTIES, callContext);
@@ -763,13 +763,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-15 => DAY 45 - 15 days after invoice
         addDaysAndCheckForCompletion(15);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-07-05 => DAY 65 - 35 days after invoice
         addDaysAndCheckForCompletion(20, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
@@ -778,7 +778,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // Now, create a chargeback for the second (first non-zero dollar) invoice
         final InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayments(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).get(1).getPayments().get(0).getPaymentId(), callContext).get(0);
@@ -809,13 +809,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-06-15 => DAY 45 - 15 days after invoice
         addDaysAndCheckForCompletion(15, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // 2012-07-05 => DAY 65 - 35 days after invoice
         addDaysAndCheckForCompletion(20, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
@@ -834,7 +834,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         final Invoice firstNonZeroInvoice = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).iterator().next();
         createExternalPaymentAndCheckForCompletion(account, firstNonZeroInvoice, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.BLOCK);
         // We should be clear now
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
     }
 
     @Test(groups = "slow", description = "Test overdue clear after item adjustment")
@@ -857,13 +857,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // DAY 45 - 15 days after invoice
         addDaysAndCheckForCompletion(15, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // DAY 65 - 35 days after invoice
         addDaysAndCheckForCompletion(20, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
@@ -881,7 +881,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         final Invoice firstNonZeroInvoice = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext).iterator().next();
         fullyAdjustInvoiceItemAndCheckForCompletion(account, firstNonZeroInvoice, 1, NextEvent.BLOCK, NextEvent.INVOICE_ADJUSTMENT);
         // We should be clear now
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         invoiceChecker.checkInvoice(account.getId(), 2,
                                     callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
@@ -892,13 +892,13 @@ public class TestOverdueIntegration extends TestOverdueBase {
         addDaysAndCheckForCompletion(5);
 
         // We should still be clear
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // DAY 80 - 20 days after second invoice
         addDaysAndCheckForCompletion(10, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
 
         // We should still be clear
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // DAY 95 - 35 days after second invoice
         addDaysAndCheckForCompletion(15, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.INVOICE_PAYMENT_ERROR);
@@ -920,12 +920,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         }
 
         // We should be cleared again
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
-    }
-
-    @Test(groups = "slow", enabled = false)
-    public void testOverdueStateAndWRITTEN_OFFTag() throws Exception {
-        // TODO add/remove tag to invoice
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
     }
 
     private void allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(final boolean extraPayment) {
@@ -955,7 +950,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
                 }
             }
         }
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
     }
 
     private List<Invoice> getUnpaidInvoicesOrderFromRecent() {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
index 026d500..9575752 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
@@ -31,6 +31,7 @@ import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.entitlement.api.SubscriptionBundle;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.testng.annotations.Test;
 
@@ -92,7 +93,7 @@ public class TestOverdueWithSubscriptionCancellation extends TestOverdueBase {
 
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
 
         // DAY 36 -- RIGHT AFTER OD1 (two block events, for the cancellation and the OD1 state)
@@ -148,7 +149,7 @@ public class TestOverdueWithSubscriptionCancellation extends TestOverdueBase {
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
 
         // Should still be in clear state
-        checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
+        checkODState(OverdueWrapper.CLEAR_STATE_NAME);
 
         // DAY 36 (2012-06-06)-- RIGHT AFTER OD1 (two block events, for the cancellation and the OD1 state)
         // One BLOCK event is for the overdue state transition
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index a52037e..fef7033 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -20,6 +20,7 @@ package org.killbill.billing.beatrix.integration;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -98,7 +99,8 @@ import org.killbill.billing.util.api.RecordIdApi;
 import org.killbill.billing.util.api.TagApiException;
 import org.killbill.billing.util.api.TagDefinitionApiException;
 import org.killbill.billing.util.api.TagUserApi;
-import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.TestCallContext;
 import org.killbill.billing.util.nodes.KillbillNodesApi;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
@@ -413,12 +415,11 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
             @Override
             public Payment apply(@Nullable final Void input) {
                 try {
-
                     final List<PluginProperty> properties = new ArrayList<PluginProperty>();
                     final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
                     properties.add(prop1);
                     return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, amount, currency, UUID.randomUUID().toString(),
-                                                                       UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
+                                                                       UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, refreshedCallContext());
                 } catch (final PaymentApiException e) {
                     fail(e.toString());
                     return null;
@@ -437,7 +438,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                     properties.add(prop1);
 
                     return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, invoice.getBalance(), invoice.getCurrency(), UUID.randomUUID().toString(),
-                                                                       UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
+                                                                       UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, refreshedCallContext());
                 } catch (final PaymentApiException e) {
                     fail(e.toString());
                     return null;
@@ -457,7 +458,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                     properties.add(prop1);
 
                     return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, invoice.getBalance(), invoice.getCurrency(), UUID.randomUUID().toString(),
-                                                                       UUID.randomUUID().toString(), properties, EXTERNAL_PAYMENT_OPTIONS, callContext);
+                                                                       UUID.randomUUID().toString(), properties, EXTERNAL_PAYMENT_OPTIONS, refreshedCallContext());
                 } catch (final PaymentApiException e) {
                     fail(e.toString());
                     return null;
@@ -467,12 +468,16 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     }
 
     protected Payment refundPaymentAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
+        return refundPaymentAndCheckForCompletion(account, payment, payment.getPurchasedAmount(), payment.getCurrency(), events);
+    }
+
+    protected Payment refundPaymentAndCheckForCompletion(final Account account, final Payment payment, final BigDecimal amount, final Currency currency, final NextEvent... events) {
         return doCallAndCheckForCompletion(new Function<Void, Payment>() {
             @Override
             public Payment apply(@Nullable final Void input) {
                 try {
-                    return paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
-                                                                     PLUGIN_PROPERTIES, PAYMENT_OPTIONS, callContext);
+                    return paymentApi.createRefundWithPaymentControl(account, payment.getId(), amount, currency, UUID.randomUUID().toString(),
+                                                                     PLUGIN_PROPERTIES, PAYMENT_OPTIONS, refreshedCallContext());
                 } catch (final PaymentApiException e) {
                     fail(e.toString());
                     return null;
@@ -491,7 +496,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                 properties.add(prop1);
                 try {
                     return paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
-                                                                     properties, PAYMENT_OPTIONS, callContext);
+                                                                     properties, PAYMENT_OPTIONS, refreshedCallContext());
                 } catch (final PaymentApiException e) {
                     fail(e.toString());
                     return null;
@@ -501,19 +506,22 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     }
 
     protected Payment refundPaymentWithInvoiceItemAdjAndCheckForCompletion(final Account account, final Payment payment, final Map<UUID, BigDecimal> iias, final NextEvent... events) {
+        return refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment, payment.getPurchasedAmount(), payment.getCurrency(), iias, events);
+    }
+
+    protected Payment refundPaymentWithInvoiceItemAdjAndCheckForCompletion(final Account account, final Payment payment, final BigDecimal amount, final Currency currency, final Map<UUID, BigDecimal> iias, final NextEvent... events) {
         return doCallAndCheckForCompletion(new Function<Void, Payment>() {
             @Override
             public Payment apply(@Nullable final Void input) {
-
-                final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+                final Collection<PluginProperty> properties = new ArrayList<PluginProperty>();
                 final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_WITH_ADJUSTMENTS, "true", false);
                 properties.add(prop1);
                 final PluginProperty prop2 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY, iias, false);
                 properties.add(prop2);
 
                 try {
-                    return paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
-                                                                     properties, PAYMENT_OPTIONS, callContext);
+                    return paymentApi.createRefundWithPaymentControl(account, payment.getId(), amount, currency, UUID.randomUUID().toString(),
+                                                                     properties, PAYMENT_OPTIONS, refreshedCallContext());
                 } catch (final PaymentApiException e) {
                     fail(e.toString());
                     return null;
@@ -522,18 +530,21 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         }, events);
     }
 
-    protected void createChargeBackAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
-        doCallAndCheckForCompletion(new Function<Void, Void>() {
+    protected Payment createChargeBackAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
+        return createChargeBackAndCheckForCompletion(account, payment, payment.getPurchasedAmount(), payment.getCurrency(), events);
+    }
+
+    protected Payment createChargeBackAndCheckForCompletion(final Account account, final Payment payment, final BigDecimal amount, final Currency currency, final NextEvent... events) {
+        return doCallAndCheckForCompletion(new Function<Void, Payment>() {
             @Override
-            public Void apply(@Nullable final Void input) {
+            public Payment apply(@Nullable final Void input) {
                 try {
-                    paymentApi.createChargebackWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
-                                                                  PAYMENT_OPTIONS, callContext);
-                } catch (PaymentApiException e) {
+                    return paymentApi.createChargebackWithPaymentControl(account, payment.getId(), amount, currency, UUID.randomUUID().toString(),
+                                                                         PAYMENT_OPTIONS, refreshedCallContext());
+                } catch (final PaymentApiException e) {
                     fail(e.toString());
                     return null;
                 }
-                return null;
             }
         }, events);
     }
@@ -700,6 +711,11 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         assertListenerStatus();
     }
 
+    // Update the context dates (matters for payments ordering for instance)
+    protected CallContext refreshedCallContext() {
+        return new TestCallContext(callContext, clock.getUTCNow());
+    }
+
     private <T> T doCallAndCheckForCompletion(final Function<Void, T> f, final NextEvent... events) {
         final Joiner joiner = Joiner.on(", ");
         log.debug("            ************    STARTING BUS HANDLER CHECK : {} ********************", joiner.join(events));
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
index c18cd69..e79eb43 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
@@ -18,13 +18,19 @@
 package org.killbill.billing.beatrix.integration;
 
 import java.math.BigDecimal;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 
+import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.beatrix.util.PaymentChecker.ExpectedPaymentCheck;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.ProductCategory;
@@ -32,9 +38,12 @@ import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -46,8 +55,281 @@ import static org.testng.Assert.assertTrue;
 public class TestInvoicePayment extends TestIntegrationBase {
 
     @Test(groups = "slow")
-    public void testPartialPayments() throws Exception {
+    public void testPartialPaymentByPaymentPlugin() throws Exception {
+        // 2012-05-01T00:03:42.000Z
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
+        final AccountData accountData = getAccountData(0);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // Trigger a partial payment on the next invoice
+        paymentPlugin.overrideNextProcessedAmount(BigDecimal.TEN);
+
+        // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        Invoice invoice2 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // Invoice is partially paid
+        final Payment payment1 = paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 31), new BigDecimal("249.95"), TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("239.95")), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+        // 2012-06-30
+        addDaysAndCheckForCompletion(30, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        Invoice invoice3 = invoiceChecker.checkInvoice(account.getId(), 3, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
+
+        // Invoice is fully paid
+        final Payment payment2 = paymentChecker.checkPayment(account.getId(), 2, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 6, 30), new BigDecimal("249.95"), TransactionStatus.SUCCESS, invoice3.getId(), Currency.USD));
+        Assert.assertEquals(payment2.getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment2.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("249.95")), 0);
+        invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+        Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("239.95")), 0);
+        Assert.assertEquals(invoice3.getBalance().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+        // Fully pay the second invoice
+        final Payment payment3 = createPaymentAndCheckForCompletion(account, invoice2, invoice2.getBalance(), account.getCurrency(), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        paymentChecker.checkPayment(account.getId(), 3, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 6, 30), new BigDecimal("239.95"), TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
+        Assert.assertEquals(payment3.getPurchasedAmount().compareTo(new BigDecimal("239.95")), 0);
+        Assert.assertEquals(payment3.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("239.95")), 0);
+        invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+        invoice3 = invoiceUserApi.getInvoice(invoice3.getId(), callContext);
+        Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(invoice3.getBalance().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testPartialRefunds() throws Exception {
+        // 2012-05-01T00:03:42.000Z
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        final AccountData accountData = getAccountData(0);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        Invoice invoice2 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // Invoice is fully paid
+        Payment payment1 = paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 31), new BigDecimal("249.95"), TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+        // Trigger first partial refund ($1), no adjustment
+        payment1 = refundPaymentAndCheckForCompletion(account, payment1, BigDecimal.TEN, Currency.USD, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getRefundedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().size(), 2);
+        Assert.assertEquals(payment1.getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+        Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+        // Trigger second partial refund ($1), with item adjustment
+        final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
+        iias.put(invoice2.getInvoiceItems().get(0).getId(), BigDecimal.ONE);
+        payment1 = refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, BigDecimal.ONE, Currency.USD, iias, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getRefundedAmount().compareTo(new BigDecimal("11")), 0);
+        Assert.assertEquals(payment1.getTransactions().size(), 3);
+        Assert.assertEquals(payment1.getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(2).getAmount().compareTo(BigDecimal.ONE), 0);
+        Assert.assertEquals(payment1.getTransactions().get(2).getProcessedAmount().compareTo(BigDecimal.ONE), 0);
+        invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+        Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+        // Trigger third partial refund ($10), with item adjustment
+        iias.put(invoice2.getInvoiceItems().get(0).getId(), BigDecimal.TEN);
+        payment1 = refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, BigDecimal.TEN, Currency.USD, iias, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getRefundedAmount().compareTo(new BigDecimal("21")), 0);
+        Assert.assertEquals(payment1.getTransactions().size(), 4);
+        Assert.assertEquals(payment1.getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(2).getAmount().compareTo(BigDecimal.ONE), 0);
+        Assert.assertEquals(payment1.getTransactions().get(2).getProcessedAmount().compareTo(BigDecimal.ONE), 0);
+        Assert.assertEquals(payment1.getTransactions().get(3).getAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(3).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+        Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testPartialPaymentByPaymentPluginThenChargeback() throws Exception {
+        // 2012-05-01T00:03:42.000Z
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        final AccountData accountData = getAccountData(0);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // Trigger a partial payment on the next invoice
+        paymentPlugin.overrideNextProcessedAmount(BigDecimal.TEN);
+
+        // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        Invoice invoice2 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // Invoice is partially paid
+        Payment payment1 = paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 31), new BigDecimal("249.95"), TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().size(), 1);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("239.95")), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+        // Trigger chargeback
+        payment1 = createChargeBackAndCheckForCompletion(account, payment1, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().size(), 2);
+        Assert.assertEquals(payment1.getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+        Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testAUTO_PAY_OFFThenPartialPayment() throws Exception {
+        // 2012-05-01T00:03:42.000Z
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        final AccountData accountData = getAccountData(0);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // Put the account in AUTO_PAY_OFF to make sure payment system does not try to pay the initial invoice
+        add_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
+
+        // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE);
+
+        Invoice invoice2 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // Invoice is not paid
+        Assert.assertEquals(paymentApi.getAccountPayments(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext).size(), 0);
+        Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+        // Trigger partial payment
+        final Payment payment1 = createPaymentAndCheckForCompletion(account, invoice2, BigDecimal.TEN, account.getCurrency(), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 31), BigDecimal.TEN, TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
+        Assert.assertEquals(payment1.getTransactions().size(), 1);
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+        Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("239.95")), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+
+        // Remove AUTO_PAY_OFF and verify the invoice is fully paid
+        remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        final Payment payment2 = paymentChecker.checkPayment(account.getId(), 2, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 31), new BigDecimal("239.95"), TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
+        Assert.assertEquals(payment2.getTransactions().size(), 1);
+        Assert.assertEquals(payment2.getPurchasedAmount().compareTo(new BigDecimal("239.95")), 0);
+        Assert.assertEquals(payment2.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("239.95")), 0);
+        invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+        Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testPaymentDifferentCurrencyByPaymentPlugin() throws Exception {
+        // 2012-05-01T00:03:42.000Z
+        clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
+
+        final AccountData accountData = getAccountData(0);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final DefaultEntitlement baseEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
+
+        // Trigger a payment on the next invoice with a different currency ($249.95 <-> 225.44€)
+        paymentPlugin.overrideNextProcessedAmount(new BigDecimal("225.44"));
+        paymentPlugin.overrideNextProcessedCurrency(Currency.EUR);
+
+        // 2012-05-31 => DAY 30 have to get out of trial {I0, P0}
+        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        Invoice invoice2 = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+        invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
+
+        // Invoice is fully paid
+        Payment payment1 = paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 31), new BigDecimal("249.95"), TransactionStatus.SUCCESS, invoice2.getId(), Currency.USD));
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().size(), 1);
+        Assert.assertEquals(payment1.getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getCurrency(), Currency.USD);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("225.44")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedCurrency(), Currency.EUR);
+        Assert.assertEquals(invoice2.getBalance().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
+
+        // Trigger chargeback in the original currency
+        payment1 = createChargeBackAndCheckForCompletion(account, payment1, new BigDecimal("225.44"), Currency.EUR, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().size(), 2);
+        Assert.assertEquals(payment1.getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getCurrency(), Currency.USD);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedAmount().compareTo(new BigDecimal("225.44")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(0).getProcessedCurrency(), Currency.EUR);
+        Assert.assertEquals(payment1.getTransactions().get(1).getAmount().compareTo(new BigDecimal("225.44")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getCurrency(), Currency.EUR);
+        Assert.assertEquals(payment1.getTransactions().get(1).getProcessedAmount().compareTo(new BigDecimal("225.44")), 0);
+        Assert.assertEquals(payment1.getTransactions().get(1).getProcessedCurrency(), Currency.EUR);
+        invoice2 = invoiceUserApi.getInvoice(invoice2.getId(), callContext);
+        Assert.assertEquals(invoice2.getBalance().compareTo(new BigDecimal("249.95")), 0);
+        Assert.assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(invoice2.getBalance()), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testPartialRefundsOnPartialPayments() throws Exception {
         final AccountData accountData = getAccountData(1);
         final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
         accountChecker.checkAccount(account.getId(), accountData, callContext);
@@ -61,8 +343,9 @@ public class TestInvoicePayment extends TestIntegrationBase {
         final InvoiceItem item1 = invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), callContext).get(0);
         assertListenerStatus();
 
+        // Trigger first partial payment ($4) on first invoice
         final Invoice invoice = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
-        final Payment payment1 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("4.00"), account.getCurrency(),  NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        Payment payment1 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("4.00"), account.getCurrency(), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
 
         Invoice invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
         assertTrue(invoice1.getBalance().compareTo(new BigDecimal("6.00")) == 0);
@@ -72,7 +355,8 @@ public class TestInvoicePayment extends TestIntegrationBase {
         BigDecimal accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
         assertTrue(accountBalance.compareTo(new BigDecimal("6.00")) == 0);
 
-        final Payment payment2 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("6.00"), account.getCurrency(),  NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        // Trigger second partial payment ($6) on first invoice
+        Payment payment2 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("6.00"), account.getCurrency(), NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
 
         invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
         assertTrue(invoice1.getBalance().compareTo(BigDecimal.ZERO) == 0);
@@ -82,35 +366,60 @@ public class TestInvoicePayment extends TestIntegrationBase {
         accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
         assertTrue(accountBalance.compareTo(BigDecimal.ZERO) == 0);
 
-/*
-        This does not work since item is paid across multiple payments and so the mount is bigger than the payment.
-
-        // Now, issue refund with item adjustment on first invoice/item
-        paymentApi.createRefundWithItemsAdjustments(account, payment1.getId(), Sets.<UUID>newHashSet(item1.getId()), callContext);
-
+        // Refund first payment with item adjustment
+        final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
+        iias.put(item1.getId(), new BigDecimal("4.00"));
+        payment1 = refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, new BigDecimal("4.00"), Currency.USD, iias, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
         invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
         assertTrue(invoice1.getBalance().compareTo(BigDecimal.ZERO) == 0);
-
         accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
         assertTrue(accountBalance.compareTo(BigDecimal.ZERO) == 0);
 
-*/
-
-        refundPaymentAndCheckForCompletion(account, payment1, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
-
+        // Refund second payment with item adjustment
+        iias.put(item1.getId(), new BigDecimal("6.00"));
+        payment2 = refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment2, new BigDecimal("6.00"), Currency.USD, iias, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
         invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
-        assertTrue(invoice1.getBalance().compareTo(new BigDecimal("4.00")) == 0);
-
+        assertTrue(invoice1.getBalance().compareTo(BigDecimal.ZERO) == 0);
         accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
-        assertTrue(accountBalance.compareTo(new BigDecimal("4.00")) == 0);
+        assertTrue(accountBalance.compareTo(BigDecimal.ZERO) == 0);
 
+        Assert.assertEquals(invoice1.getPayments().size(), 4);
+
+        // Verify links for payment 1
+        Assert.assertEquals(invoice1.getPayments().get(0).getAmount().compareTo(new BigDecimal("4.00")), 0);
+        Assert.assertNull(invoice1.getPayments().get(0).getLinkedInvoicePaymentId());
+        Assert.assertNull(invoice1.getPayments().get(0).getPaymentCookieId());
+        Assert.assertEquals(invoice1.getPayments().get(0).getPaymentId(), payment1.getId());
+        Assert.assertEquals(invoice1.getPayments().get(0).getType(), InvoicePaymentType.ATTEMPT);
+        Assert.assertTrue(invoice1.getPayments().get(0).isSuccess());
+
+        // Verify links for payment 2
+        Assert.assertEquals(invoice1.getPayments().get(1).getAmount().compareTo(new BigDecimal("6.00")), 0);
+        Assert.assertNull(invoice1.getPayments().get(1).getLinkedInvoicePaymentId());
+        Assert.assertNull(invoice1.getPayments().get(1).getPaymentCookieId());
+        Assert.assertEquals(invoice1.getPayments().get(1).getPaymentId(), payment2.getId());
+        Assert.assertEquals(invoice1.getPayments().get(1).getType(), InvoicePaymentType.ATTEMPT);
+        Assert.assertTrue(invoice1.getPayments().get(1).isSuccess());
+
+        // Verify links for refund 1
+        Assert.assertEquals(invoice1.getPayments().get(2).getAmount().compareTo(new BigDecimal("-4.00")), 0);
+        Assert.assertEquals(invoice1.getPayments().get(2).getLinkedInvoicePaymentId(), invoice1.getPayments().get(0).getId());
+        Assert.assertEquals(invoice1.getPayments().get(2).getPaymentCookieId(), payment1.getTransactions().get(1).getExternalKey());
+        Assert.assertEquals(invoice1.getPayments().get(2).getPaymentId(), payment1.getId());
+        Assert.assertEquals(invoice1.getPayments().get(2).getType(), InvoicePaymentType.REFUND);
+        Assert.assertTrue(invoice1.getPayments().get(2).isSuccess());
+
+        // Verify links for refund 2
+        Assert.assertEquals(invoice1.getPayments().get(3).getAmount().compareTo(new BigDecimal("-6.00")), 0);
+        Assert.assertEquals(invoice1.getPayments().get(3).getLinkedInvoicePaymentId(), invoice1.getPayments().get(1).getId());
+        Assert.assertEquals(invoice1.getPayments().get(3).getPaymentCookieId(), payment2.getTransactions().get(1).getExternalKey());
+        Assert.assertEquals(invoice1.getPayments().get(3).getPaymentId(), payment2.getId());
+        Assert.assertEquals(invoice1.getPayments().get(3).getType(), InvoicePaymentType.REFUND);
+        Assert.assertTrue(invoice1.getPayments().get(3).isSuccess());
     }
 
-    //
-
     @Test(groups = "slow")
     public void testWithPaymentFailure() throws Exception {
-
         clock.setDay(new LocalDate(2012, 4, 1));
 
         final AccountData accountData = getAccountData(1);
@@ -129,34 +438,48 @@ public class TestInvoicePayment extends TestIntegrationBase {
         assertEquals(invoices.size(), 2);
 
         final Invoice invoice1 = invoices.get(0).getInvoiceItems().get(0).getInvoiceItemType() == InvoiceItemType.RECURRING ?
-                           invoices.get(0) : invoices.get(1);
+                                 invoices.get(0) : invoices.get(1);
         assertTrue(invoice1.getBalance().compareTo(new BigDecimal("249.95")) == 0);
         assertTrue(invoice1.getPaidAmount().compareTo(BigDecimal.ZERO) == 0);
         assertTrue(invoice1.getChargedAmount().compareTo(new BigDecimal("249.95")) == 0);
         assertEquals(invoice1.getPayments().size(), 1);
+        assertEquals(invoice1.getPayments().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(invoice1.getPayments().get(0).getCurrency(), Currency.USD);
         assertFalse(invoice1.getPayments().get(0).isSuccess());
 
-        BigDecimal accountBalance1 = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+        final BigDecimal accountBalance1 = invoiceUserApi.getAccountBalance(account.getId(), callContext);
         assertTrue(accountBalance1.compareTo(new BigDecimal("249.95")) == 0);
 
         final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
         assertEquals(payments.size(), 1);
+        assertEquals(payments.get(0).getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payments.get(0).getTransactions().size(), 1);
+        assertEquals(payments.get(0).getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
+        assertEquals(payments.get(0).getTransactions().get(0).getCurrency(), Currency.USD);
+        assertEquals(payments.get(0).getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payments.get(0).getTransactions().get(0).getProcessedCurrency(), Currency.USD);
 
         // Trigger the payment retry
         busHandler.pushExpectedEvents(NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
         clock.addDays(8);
         assertListenerStatus();
 
-        Invoice invoice2 = invoiceUserApi.getInvoice(invoice1.getId(), callContext);
+        final Invoice invoice2 = invoiceUserApi.getInvoice(invoice1.getId(), callContext);
         assertTrue(invoice2.getBalance().compareTo(BigDecimal.ZERO) == 0);
         assertTrue(invoice2.getPaidAmount().compareTo(new BigDecimal("249.95")) == 0);
         assertTrue(invoice2.getChargedAmount().compareTo(new BigDecimal("249.95")) == 0);
         assertEquals(invoice2.getPayments().size(), 1);
         assertTrue(invoice2.getPayments().get(0).isSuccess());
 
-        BigDecimal accountBalance2 = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+        final BigDecimal accountBalance2 = invoiceUserApi.getAccountBalance(account.getId(), callContext);
         assertTrue(accountBalance2.compareTo(BigDecimal.ZERO) == 0);
-    }
-
 
+        final List<Payment> payments2 = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+        assertEquals(payments2.size(), 1);
+        assertEquals(payments2.get(0).getTransactions().size(), 2);
+        assertEquals(payments2.get(0).getTransactions().get(1).getAmount().compareTo(new BigDecimal("249.95")), 0);
+        assertEquals(payments2.get(0).getTransactions().get(1).getCurrency(), Currency.USD);
+        assertEquals(payments2.get(0).getTransactions().get(1).getProcessedAmount().compareTo(new BigDecimal("249.95")), 0);
+        assertEquals(payments2.get(0).getTransactions().get(1).getProcessedCurrency(), Currency.USD);
+    }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
index 77bc381..7a10f54 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/InvoiceChecker.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -159,17 +161,11 @@ public class InvoiceChecker {
             if (expectedLocalCTD == null) {
                 assertNull(subscription.getChargedThroughDate());
             } else {
-                assertTrue(expectedLocalCTD.compareTo(subscription.getChargedThroughDate().toLocalDate()) == 0);
-                /*
-                final DateTime expectedCTD = expectedLocalCTD.toDateTime(new LocalTime(subscription.getStartDate().getMillis(), DateTimeZone.UTC), DateTimeZone.UTC);
-                final String msg = String.format("Checking CTD for entitlement %s : expectedLocalCTD = %s => expectedCTD = %s, got %s",
-                                                 entitlementId, expectedLocalCTD, expectedCTD, subscription.getChargedThroughDate());
-                log.info(msg);
-                assertNotNull(subscription.getChargedThroughDate());
-                assertTrue(subscription.getChargedThroughDate().compareTo(expectedCTD) == 0, msg);
-                */
+                final String msg = String.format("Checking CTD for entitlement %s : expectedLocalCTD = %s, got %s",
+                                                 entitlementId, expectedLocalCTD, subscription.getChargedThroughDate().toLocalDate());
+                assertTrue(expectedLocalCTD.compareTo(subscription.getChargedThroughDate().toLocalDate()) == 0, msg);
             }
-        } catch (EntitlementApiException e) {
+        } catch (final EntitlementApiException e) {
             fail("Failed to retrieve entitlement for " + entitlementId);
         }
     }
@@ -182,7 +178,7 @@ public class InvoiceChecker {
         private final InvoiceItemType type;
         private final BigDecimal amount;
 
-        public ExpectedInvoiceItemCheck(final InvoiceItemType type, final BigDecimal amount, boolean checkDates) {
+        public ExpectedInvoiceItemCheck(final InvoiceItemType type, final BigDecimal amount, final boolean checkDates) {
             this.checkDates = checkDates;
             this.type = type;
             this.startDate = null;
@@ -195,7 +191,7 @@ public class InvoiceChecker {
         }
 
         public ExpectedInvoiceItemCheck(final LocalDate startDate, final LocalDate endDate,
-                                        final InvoiceItemType type, final BigDecimal amount, boolean checkDates) {
+                                        final InvoiceItemType type, final BigDecimal amount, final boolean checkDates) {
             this.checkDates = checkDates;
             this.startDate = startDate;
             this.endDate = endDate;
@@ -227,6 +223,17 @@ public class InvoiceChecker {
         public BigDecimal getAmount() {
             return amount;
         }
-    }
 
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("ExpectedInvoiceItemCheck{");
+            sb.append("checkDates=").append(checkDates);
+            sb.append(", startDate=").append(startDate);
+            sb.append(", endDate=").append(endDate);
+            sb.append(", type=").append(type);
+            sb.append(", amount=").append(amount);
+            sb.append('}');
+            return sb.toString();
+        }
+    }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java
index 69cfe82..efaf1ea 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -24,6 +24,8 @@ import java.util.UUID;
 
 import org.joda.time.LocalDate;
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.invoice.api.InvoicePaymentApi;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentTransaction;
@@ -46,15 +48,17 @@ public class PaymentChecker {
     private static final Logger log = LoggerFactory.getLogger(PaymentChecker.class);
 
     private final PaymentApi paymentApi;
+    private final InvoicePaymentApi invoicePaymentApi;
     private final AuditChecker auditChecker;
 
     @Inject
-    public PaymentChecker(final PaymentApi paymentApi, final AuditChecker auditChecker) {
+    public PaymentChecker(final PaymentApi paymentApi, final InvoicePaymentApi invoicePaymentApi, final AuditChecker auditChecker) {
         this.paymentApi = paymentApi;
+        this.invoicePaymentApi = invoicePaymentApi;
         this.auditChecker = auditChecker;
     }
 
-    public Payment checkPayment(final UUID accountId, final int paymentOrderingNumber, final CallContext context, ExpectedPaymentCheck expected) throws PaymentApiException {
+    public Payment checkPayment(final UUID accountId, final int paymentOrderingNumber, final CallContext context, final ExpectedPaymentCheck expected) throws PaymentApiException {
         final List<Payment> payments = paymentApi.getAccountPayments(accountId, false, ImmutableList.<PluginProperty>of(), context);
         Assert.assertEquals(payments.size(), paymentOrderingNumber);
         final Payment payment = payments.get(paymentOrderingNumber - 1);
@@ -78,10 +82,19 @@ public class PaymentChecker {
 
     private void checkPayment(final UUID accountId, final Payment payment, final CallContext context, final ExpectedPaymentCheck expected) {
         Assert.assertEquals(payment.getAccountId(), accountId);
+        Assert.assertEquals(payment.getCurrency(), expected.getCurrency());
+
+        if (expected.getInvoiceId() != null) {
+            for (final InvoicePayment invoicePayment : invoicePaymentApi.getInvoicePayments(payment.getId(), context)) {
+                Assert.assertEquals(invoicePayment.getInvoiceId(), expected.getInvoiceId());
+            }
+        }
+
         final PaymentTransaction transaction = getPurchaseTransaction(payment);
-        Assert.assertTrue(transaction.getAmount().compareTo(expected.getAmount()) == 0);
+        Assert.assertTrue(transaction.getAmount().compareTo(expected.getAmount()) == 0, "Actual amount " + transaction.getAmount() + ", expected amount " + expected.getAmount());
         Assert.assertEquals(transaction.getTransactionStatus(), expected.getStatus());
-        Assert.assertEquals(payment.getCurrency(), expected.getCurrency());
+        Assert.assertEquals(transaction.getEffectiveDate().toLocalDate().compareTo(expected.getPaymentDate()), 0);
+
         auditChecker.checkPaymentCreated(payment, context);
     }
 
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
index a4d2148..93462b9 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
@@ -288,44 +288,55 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         // Get the latest state from disk
         refresh(callContext);
 
-        if (eventsStream.isEntitlementCancelled()) {
-            throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, getId(), EntitlementState.CANCELLED);
-        }
+        final EntitlementContext pluginContext = new DefaultEntitlementContext(OperationType.CANCEL_SUBSCRIPTION,
+                                                                               getAccountId(),
+                                                                               null,
+                                                                               getBundleId(),
+                                                                               getExternalKey(),
+                                                                               null,
+                                                                               localCancelDate,
+                                                                               properties,
+                                                                               callContext);
 
-        final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
-        final DateTime effectiveCancelDate = dateHelper.fromLocalDateAndReferenceTime(localCancelDate, getSubscriptionBase().getStartDate(), contextWithValidAccountRecordId);
-        try {
-            if (overrideBillingEffectiveDate) {
-                getSubscriptionBase().cancelWithDate(effectiveCancelDate, callContext);
-            } else {
-                getSubscriptionBase().cancel(callContext);
-            }
-        } catch (final SubscriptionBaseApiException e) {
-            throw new EntitlementApiException(e);
-        }
 
-        final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveCancelDate);
-        final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
-        final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveCancelDate, notificationEvents, callContext, contextWithValidAccountRecordId);
+        final WithEntitlementPlugin<Entitlement> cancelEntitlementWithPlugin = new WithEntitlementPlugin<Entitlement>() {
 
-        // Record the new state first, then insert the notifications to avoid race conditions
-        setBlockingStates(newBlockingState, addOnsBlockingStates, contextWithValidAccountRecordId);
-        for (final NotificationEvent notificationEvent : notificationEvents) {
-            recordFutureNotification(effectiveCancelDate, notificationEvent, contextWithValidAccountRecordId);
-        }
+            @Override
+            public Entitlement doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
+                if (eventsStream.isEntitlementCancelled()) {
+                    throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, getId(), EntitlementState.CANCELLED);
+                }
 
-        return entitlementApi.getEntitlementForId(getId(), callContext);
-    }
+                final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
+                final DateTime effectiveCancelDate = dateHelper.fromLocalDateAndReferenceTime(localCancelDate, getSubscriptionBase().getStartDate(), contextWithValidAccountRecordId);
+                try {
+                    if (overrideBillingEffectiveDate) {
+                        getSubscriptionBase().cancelWithDate(effectiveCancelDate, callContext);
+                    } else {
+                        getSubscriptionBase().cancel(callContext);
+                    }
+                } catch (final SubscriptionBaseApiException e) {
+                    throw new EntitlementApiException(e);
+                }
 
-    @Override
-    public Entitlement cancelEntitlementWithPolicyOverrideBillingPolicy(final EntitlementActionPolicy entitlementPolicy, final BillingActionPolicy billingPolicy, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
-        // Get the latest state from disk - required to have the latest CTD
-        refresh(callContext);
+                final BlockingState newBlockingState = new DefaultBlockingState(getId(), BlockingStateType.SUBSCRIPTION, DefaultEntitlementApi.ENT_STATE_CANCELLED, EntitlementService.ENTITLEMENT_SERVICE_NAME, true, true, false, effectiveCancelDate);
+                final Collection<NotificationEvent> notificationEvents = new ArrayList<NotificationEvent>();
+                final Collection<BlockingState> addOnsBlockingStates = computeAddOnBlockingStates(effectiveCancelDate, notificationEvents, callContext, contextWithValidAccountRecordId);
 
-        final LocalDate cancellationDate = getLocalDateFromEntitlementPolicy(entitlementPolicy);
-        return cancelEntitlementWithDateOverrideBillingPolicy(cancellationDate, billingPolicy, properties, callContext);
+                // Record the new state first, then insert the notifications to avoid race conditions
+                setBlockingStates(newBlockingState, addOnsBlockingStates, contextWithValidAccountRecordId);
+                for (final NotificationEvent notificationEvent : notificationEvents) {
+                    recordFutureNotification(effectiveCancelDate, notificationEvent, contextWithValidAccountRecordId);
+                }
+
+                return entitlementApi.getEntitlementForId(getId(), callContext);
+            }
+        };
+
+        return pluginExecution.executeWithPlugin(cancelEntitlementWithPlugin, pluginContext);
     }
 
+
     @Override
     public void uncancelEntitlement(final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
 
@@ -368,6 +379,16 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         }
     }
 
+    @Override
+    public Entitlement cancelEntitlementWithPolicyOverrideBillingPolicy(final EntitlementActionPolicy entitlementPolicy, final BillingActionPolicy billingPolicy, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
+        // Get the latest state from disk - required to have the latest CTD
+        refresh(callContext);
+
+        final LocalDate cancellationDate = getLocalDateFromEntitlementPolicy(entitlementPolicy);
+        return cancelEntitlementWithDateOverrideBillingPolicy(cancellationDate, billingPolicy, properties, callContext);
+    }
+
+
     // See also EntitlementInternalApi#cancel for the bulk API
     @Override
     public Entitlement cancelEntitlementWithDateOverrideBillingPolicy(final LocalDate localCancelDate, final BillingActionPolicy billingPolicy, final Iterable<PluginProperty> properties, final CallContext callContext) throws EntitlementApiException {
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
index 0173d7c..59f2c89 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
@@ -168,11 +168,6 @@ public class DefaultEntitlementApiBase {
             public Void doCall(final EntitlementApi entitlementApi, final EntitlementContext updatedPluginContext) throws EntitlementApiException {
                 try {
 
-                    final BlockingState currentState = blockingStateDao.getBlockingStateForService(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalCallContext);
-                    if (currentState != null && currentState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_BLOCKED)) {
-                        throw new EntitlementApiException(ErrorCode.ENT_ALREADY_BLOCKED, bundleId);
-                    }
-
                     final SubscriptionBaseBundle bundle = subscriptionInternalApi.getBundleFromId(bundleId, internalCallContext);
                     final ImmutableAccountData account = accountApi.getImmutableAccountDataById(bundle.getAccountId(), internalCallContext);
                     final SubscriptionBase baseSubscription = subscriptionInternalApi.getBaseSubscription(bundleId, internalCallContext);
@@ -235,13 +230,6 @@ public class DefaultEntitlementApiBase {
                         return null;
                     }
 
-                    final BlockingState currentState = blockingStateDao.getBlockingStateForService(bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE, EntitlementService.ENTITLEMENT_SERVICE_NAME, internalCallContext);
-                    if (currentState == null || currentState.getStateName().equals(DefaultEntitlementApi.ENT_STATE_CLEAR)) {
-                        // Nothing to do.
-                        log.warn("Current state is {}, nothing to resume", currentState);
-                        return null;
-                    }
-
                     final UUID blockingId = blockUnblockBundle(bundleId, DefaultEntitlementApi.ENT_STATE_CLEAR, EntitlementService.ENTITLEMENT_SERVICE_NAME, localEffectiveDate, false, false, false, baseSubscription, internalCallContext);
 
                     // Should we send one event per entitlement in the bundle?
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java
index 971786e..007c423 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultInternalBlockingApi.java
@@ -21,6 +21,7 @@ import java.util.UUID;
 
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.overdue.OverdueService;
 import org.killbill.clock.Clock;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
@@ -46,12 +47,7 @@ public class DefaultInternalBlockingApi implements BlockingInternalApi {
 
     @Override
     public BlockingState getBlockingStateForService(final UUID overdueableId, final BlockingStateType blockingStateType, final String serviceName, final InternalTenantContext context) {
-        final BlockingState blockingStateForService = dao.getBlockingStateForService(overdueableId, blockingStateType, serviceName, context);
-        if (blockingStateForService == null) {
-            return DefaultBlockingState.getClearState(blockingStateType, serviceName, clock);
-        } else {
-            return blockingStateForService;
-        }
+        return dao.getBlockingStateForService(overdueableId, blockingStateType, serviceName, context);
     }
 
     @Override
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
index 9a397ae..74fda0c 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -311,14 +311,6 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
         final Entitlement telescopicEntitlement2 = entitlementApi.getEntitlementForId(telescopicEntitlement.getId(), callContext);
         assertEquals(telescopicEntitlement2.getState(), EntitlementState.BLOCKED);
 
-        // Check we can't block in a blocked state
-        try {
-            entitlementApi.pause(baseEntitlement.getBundleId(), new LocalDate(clock.getUTCNow()), ImmutableList.<PluginProperty>of(), callContext);
-            Assert.fail("Should not have succeeded to block in a blocked state");
-        } catch (EntitlementApiException e) {
-            assertEquals(e.getCode(), ErrorCode.ENT_ALREADY_BLOCKED.getCode());
-        }
-
         final List<Entitlement> bundleEntitlements2 = entitlementApi.getAllEntitlementsForBundle(telescopicEntitlement2.getBundleId(), callContext);
         assertEquals(bundleEntitlements2.size(), 2);
         for (final Entitlement cur : bundleEntitlements2) {
@@ -339,8 +331,11 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
         entitlementApi.resume(baseEntitlement.getBundleId(), new LocalDate(clock.getUTCNow()), ImmutableList.<PluginProperty>of(), callContext);
         assertListenerStatus();
 
-        // Verify call is idempotent
+        // Verify call is idempotent : The current semantics is to post the RESUME because we went through the operation, but not the BLOCK because the DAO logic
+        // filtered the event as the subscription was already resumed.
+        testListener.pushExpectedEvents(NextEvent.RESUME);
         entitlementApi.resume(baseEntitlement.getBundleId(), new LocalDate(clock.getUTCNow()), ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
 
         // Verify blocking state
         final Entitlement baseEntitlement3 = entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext);
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestProxyBlockingStateDao.java b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestProxyBlockingStateDao.java
index 549f02b..8c93f3a 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestProxyBlockingStateDao.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/dao/TestProxyBlockingStateDao.java
@@ -78,7 +78,7 @@ public class TestProxyBlockingStateDao extends EntitlementTestSuiteNoDB {
         final BlockingState bs4 = new DefaultBlockingState(UUID.randomUUID(),
                                                            blockedId,
                                                            blockingStateType,
-                                                           DefaultBlockingState.CLEAR_STATE_NAME,
+                                                           "OD4",
                                                            service,
                                                            false,
                                                            false,
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
index 80472a9..56565d2 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
@@ -20,7 +20,6 @@ package org.killbill.billing.invoice.api.svcs;
 
 import java.math.BigDecimal;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -111,11 +110,6 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
     }
 
     @Override
-    public InvoicePayment getInvoicePaymentForRefund(final UUID paymentId, final InternalTenantContext context) throws InvoiceApiException {
-        return getInvoicePayment(paymentId, InvoicePaymentType.REFUND, context);
-    }
-
-    @Override
     public InvoicePayment getInvoicePaymentForChargeback(final UUID paymentId, final InternalTenantContext context) throws InvoiceApiException {
         return getInvoicePayment(paymentId, InvoicePaymentType.CHARGED_BACK, context);
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index c81c6e8..9e1cd9d 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -256,7 +256,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
         }
 
         return new ExternalChargeInvoiceItem(externalChargeItem.getId(), externalChargeItem.getInvoiceId(), externalChargeItem.getAccountId(),
-                                             externalChargeItem.getPlanName(), externalChargeItem.getStartDate(),
+                                             externalChargeItem.getDescription(), externalChargeItem.getStartDate(),
                                              externalChargeItem.getAmount(), externalChargeItem.getCurrency());
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index f22f497..5166e98 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -542,7 +542,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 }
                 // We expect the code to correctly pass the account currency -- the payment code, more generic accept chargeBack in different currencies,
                 // but this is only for direct payment (no invoice)
-                Preconditions.checkArgument(invoicePayment.getCurrency() == currency);
+                Preconditions.checkArgument(invoicePayment.getCurrency() == currency, String.format("Invoice payment currency %s doesn't match chargeback currency %s", invoicePayment.getCurrency(), currency));
 
                 final UUID invoicePaymentId = invoicePayment.getId();
                 final BigDecimal maxChargedBackAmount = invoiceDaoHelper.getRemainingAmountPaidFromTransaction(invoicePaymentId, entitySqlDaoWrapperFactory, context);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
index e80ffa1..acf13dc 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
@@ -178,7 +178,7 @@ public class InvoiceDaoHelper {
             public boolean apply(final InvoiceModelDao in) {
                 final BigDecimal balance = InvoiceModelDaoHelper.getBalance(in);
                 log.debug("Computed balance={} for invoice={}", balance, in);
-                return (balance.compareTo(BigDecimal.ZERO) >= 1) && (upToDate == null || !in.getTargetDate().isAfter(upToDate));
+                return (!in.isWrittenOff() && balance.compareTo(BigDecimal.ZERO) >= 1) && (upToDate == null || !in.getTargetDate().isAfter(upToDate));
             }
         });
         return new ArrayList<InvoiceModelDao>(unpaidInvoices);
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
index 1a3907c..f7cda2b 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
@@ -72,7 +72,6 @@ CREATE TABLE invoice_payments (
     PRIMARY KEY(record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
 CREATE UNIQUE INDEX invoice_payments_id ON invoice_payments(id);
-CREATE UNIQUE INDEX idx_invoice_payments ON invoice_payments(payment_id, type);
 CREATE INDEX invoice_payments_invoice_id ON invoice_payments(invoice_id);
 CREATE INDEX invoice_payments_reversals ON invoice_payments(linked_invoice_payment_id);
 CREATE INDEX invoice_payments_tenant_account_record_id ON invoice_payments(tenant_record_id, account_record_id);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index a572def..2fd989a 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -70,9 +70,11 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
 
         // Post an external charge
         final BigDecimal externalChargeAmount = BigDecimal.TEN;
-        final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
+        final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, "description", clock.getUTCToday(), externalChargeAmount, accountCurrency);
         final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), callContext).get(0);
         verifyExternalChargeOnNewInvoice(accountBalance, null, externalChargeAmount, externalChargeInvoiceItem);
+
+        assertEquals(externalChargeInvoiceItem.getDescription(), "description");
     }
 
     @Test(groups = "slow")
@@ -124,7 +126,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
         verifyExternalChargeOnExistingInvoice(invoiceBalance, null, externalChargeAmount, externalChargeInvoiceItem);
     }
 
-    @Test(groups = "slow", enabled = false)
+    @Test(groups = "slow")
     public void testOriginalAmountCharged() throws Exception {
 
         final Invoice initialInvoice = invoiceUserApi.getInvoice(invoiceId, callContext);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestProRation.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestProRation.java
index e56b43f..f1f0399 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestProRation.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/annual/TestProRation.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,17 +18,22 @@
 
 package org.killbill.billing.invoice.proRations.inAdvance.annual;
 
-import static org.killbill.billing.invoice.TestInvoiceHelper.*;
-
 import java.math.BigDecimal;
 
 import org.joda.time.LocalDate;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.invoice.model.InvalidDateSequenceException;
 import org.killbill.billing.invoice.proRations.inAdvance.ProRationInAdvanceTestBase;
 import org.killbill.billing.util.currency.KillBillMoney;
+import org.testng.annotations.Test;
+
+import static org.killbill.billing.invoice.TestInvoiceHelper.FORTY_SEVEN;
+import static org.killbill.billing.invoice.TestInvoiceHelper.ONE;
+import static org.killbill.billing.invoice.TestInvoiceHelper.SEVEN;
+import static org.killbill.billing.invoice.TestInvoiceHelper.THREE_HUNDRED_AND_FIFTY_FOUR;
+import static org.killbill.billing.invoice.TestInvoiceHelper.THREE_HUNDRED_AND_FOURTY_NINE;
+import static org.killbill.billing.invoice.TestInvoiceHelper.THREE_HUNDRED_AND_SIXTY_FIVE;
+import static org.killbill.billing.invoice.TestInvoiceHelper.THREE_HUNDRED_AND_SIXTY_SIX;
 
 public class TestProRation extends ProRationInAdvanceTestBase {
 
@@ -55,14 +62,17 @@ public class TestProRation extends ProRationInAdvanceTestBase {
         testCalculateNumberOfBillingCycles(startDate, targetDate, 4, expectedValue);
     }
 
-    // TODO Test fails, needs to be investigated
-    @Test(groups = "fast", enabled = false)
+    @Test(groups = "fast")
     public void testSinglePlanDoubleProRation() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 1, 10);
         final LocalDate endDate = invoiceUtil.buildDate(2012, 3, 4);
         final LocalDate targetDate = invoiceUtil.buildDate(2012, 4, 5);
 
-        final BigDecimal expectedValue = BigDecimal.ZERO;
+        // SEVEN is number of days between startDate and expected first billing cycle date (2011, 1, 17);
+        // FORTY_SEVEN is number of days between the second billing cycle date (2012, 1, 17) and the end date (2012, 3, 4);
+        // 2011 has 365 days but 2012 has 366 days
+        final BigDecimal expectedValue = ONE.add(SEVEN.divide(THREE_HUNDRED_AND_SIXTY_FIVE, KillBillMoney.ROUNDING_METHOD))
+                                            .add(FORTY_SEVEN.divide(THREE_HUNDRED_AND_SIXTY_SIX, KillBillMoney.ROUNDING_METHOD));
         testCalculateNumberOfBillingCycles(startDate, endDate, targetDate, 17, expectedValue);
     }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index 4e2d5f6..3cc1c21 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -128,6 +128,7 @@ public class TestInvoiceHelper {
     public static final BigDecimal THIRTY_THREE = new BigDecimal("33.0").setScale(KillBillMoney.MAX_SCALE);
 
     public static final BigDecimal FORTY = new BigDecimal("40.0").setScale(KillBillMoney.MAX_SCALE);
+    public static final BigDecimal FORTY_SEVEN = new BigDecimal("47.0").setScale(KillBillMoney.MAX_SCALE);
     public static final BigDecimal SIXTY_SIX = new BigDecimal("66.0").setScale(KillBillMoney.MAX_SCALE);
     public static final BigDecimal SEVENTY_FIVE = new BigDecimal("75.0").setScale(KillBillMoney.MAX_SCALE);
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
index 79968cc..e286e85 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
@@ -383,13 +383,14 @@ public class PaymentResource extends ComboPaymentResource {
                            @ApiResponse(code = 404, message = "Account or payment not found")})
     public Response captureAuthorization(final PaymentTransactionJson json,
                                          @PathParam("paymentId") final String paymentIdStr,
+                                         @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                          @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                          @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                          @HeaderParam(HDR_REASON) final String reason,
                                          @HeaderParam(HDR_COMMENT) final String comment,
                                          @javax.ws.rs.core.Context final UriInfo uriInfo,
                                          @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return captureAuthorizationInternal(json, paymentIdStr, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return captureAuthorizationInternal(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
     @TimedResource(name = "captureAuthorization")
@@ -399,17 +400,19 @@ public class PaymentResource extends ComboPaymentResource {
     @ApiOperation(value = "Capture an existing authorization")
     @ApiResponses(value = {@ApiResponse(code = 404, message = "Account or payment not found")})
     public Response captureAuthorizationByExternalKey(final PaymentTransactionJson json,
+                                                      @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                                       @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                                       @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                                       @HeaderParam(HDR_REASON) final String reason,
                                                       @HeaderParam(HDR_COMMENT) final String comment,
                                                       @javax.ws.rs.core.Context final UriInfo uriInfo,
                                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return captureAuthorizationInternal(json, null, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return captureAuthorizationInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
     private Response captureAuthorizationInternal(final PaymentTransactionJson json,
                                                   @Nullable final String paymentIdStr,
+                                                  final List<String> paymentControlPluginNames,
                                                   final List<String> pluginPropertiesString,
                                                   final String createdBy,
                                                   final String reason,
@@ -427,8 +430,10 @@ public class PaymentResource extends ComboPaymentResource {
         final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
         final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency());
 
-        final Payment payment = paymentApi.createCapture(account, initialPayment.getId(), json.getAmount(), currency,
-                                                         json.getTransactionExternalKey(), pluginProperties, callContext);
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+
+        final Payment payment = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
+                                                         json.getTransactionExternalKey(), pluginProperties, paymentOptions, callContext);
         return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
     }
 
@@ -442,13 +447,14 @@ public class PaymentResource extends ComboPaymentResource {
                            @ApiResponse(code = 404, message = "Account or payment not found")})
     public Response refundPayment(final PaymentTransactionJson json,
                                   @PathParam("paymentId") final String paymentIdStr,
+                                  @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                   @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                   @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                   @HeaderParam(HDR_REASON) final String reason,
                                   @HeaderParam(HDR_COMMENT) final String comment,
                                   @javax.ws.rs.core.Context final UriInfo uriInfo,
                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return refundPaymentInternal(json, paymentIdStr, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return refundPaymentInternal(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
     @TimedResource(name = "refundPayment")
@@ -459,18 +465,20 @@ public class PaymentResource extends ComboPaymentResource {
     @ApiOperation(value = "Refund an existing payment")
     @ApiResponses(value = {@ApiResponse(code = 404, message = "Account or payment not found")})
     public Response refundPaymentByExternalKey(final PaymentTransactionJson json,
+                                               @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                                @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                                @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                                @HeaderParam(HDR_REASON) final String reason,
                                                @HeaderParam(HDR_COMMENT) final String comment,
                                                @javax.ws.rs.core.Context final UriInfo uriInfo,
                                                @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return refundPaymentInternal(json, null, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return refundPaymentInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
 
     }
 
     private Response refundPaymentInternal(final PaymentTransactionJson json,
                                            @Nullable final String paymentIdStr,
+                                           final List<String> paymentControlPluginNames,
                                            final List<String> pluginPropertiesString,
                                            final String createdBy,
                                            final String reason,
@@ -488,8 +496,10 @@ public class PaymentResource extends ComboPaymentResource {
         final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
         final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency());
 
-        final Payment payment = paymentApi.createRefund(account, initialPayment.getId(), json.getAmount(), currency,
-                                                        json.getTransactionExternalKey(), pluginProperties, callContext);
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+
+        final Payment payment = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
+                                                        json.getTransactionExternalKey(), pluginProperties, paymentOptions, callContext);
         return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
 
     }
@@ -504,13 +514,14 @@ public class PaymentResource extends ComboPaymentResource {
                            @ApiResponse(code = 404, message = "Account or payment not found")})
     public Response voidPayment(final PaymentTransactionJson json,
                                 @PathParam("paymentId") final String paymentIdStr,
+                                @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                 @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                 @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                 @HeaderParam(HDR_REASON) final String reason,
                                 @HeaderParam(HDR_COMMENT) final String comment,
                                 @javax.ws.rs.core.Context final UriInfo uriInfo,
                                 @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return voidPaymentInternal(json, paymentIdStr, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return voidPaymentInternal(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
     @TimedResource(name = "voidPayment")
@@ -520,17 +531,19 @@ public class PaymentResource extends ComboPaymentResource {
     @ApiOperation(value = "Void an existing payment")
     @ApiResponses(value = {@ApiResponse(code = 404, message = "Account or payment not found")})
     public Response voidPaymentByExternalKey(final PaymentTransactionJson json,
+                                             @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                              @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                              @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                              @HeaderParam(HDR_REASON) final String reason,
                                              @HeaderParam(HDR_COMMENT) final String comment,
                                              @javax.ws.rs.core.Context final UriInfo uriInfo,
                                              @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return voidPaymentInternal(json, null, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return voidPaymentInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
     private Response voidPaymentInternal(final PaymentTransactionJson json,
                                          @Nullable final String paymentIdStr,
+                                         final List<String> paymentControlPluginNames,
                                          final List<String> pluginPropertiesString,
                                          final String createdBy,
                                          final String reason,
@@ -544,7 +557,10 @@ public class PaymentResource extends ComboPaymentResource {
         final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
 
         final String transactionExternalKey = json != null ? json.getTransactionExternalKey() : null;
-        final Payment payment = paymentApi.createVoid(account, initialPayment.getId(), transactionExternalKey, pluginProperties, callContext);
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+
+        final Payment payment = paymentApi.createVoidWithPaymentControl(account, initialPayment.getId(), transactionExternalKey,
+                                                                        pluginProperties, paymentOptions, callContext);
         return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
     }
 
@@ -558,13 +574,14 @@ public class PaymentResource extends ComboPaymentResource {
                            @ApiResponse(code = 404, message = "Account not found")})
     public Response chargebackPayment(final PaymentTransactionJson json,
                                       @PathParam("paymentId") final String paymentIdStr,
+                                      @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                       @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                       @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                       @HeaderParam(HDR_REASON) final String reason,
                                       @HeaderParam(HDR_COMMENT) final String comment,
                                       @javax.ws.rs.core.Context final UriInfo uriInfo,
                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return chargebackPaymentInternal(json, paymentIdStr, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return chargebackPaymentInternal(json, paymentIdStr, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
     @TimedResource(name = "chargebackPayment")
@@ -575,17 +592,19 @@ public class PaymentResource extends ComboPaymentResource {
     @ApiOperation(value = "Record a chargeback")
     @ApiResponses(value = {@ApiResponse(code = 404, message = "Account not found")})
     public Response chargebackPaymentByExternalKey(final PaymentTransactionJson json,
+                                                   @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                                    @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                                    @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                                    @HeaderParam(HDR_REASON) final String reason,
                                                    @HeaderParam(HDR_COMMENT) final String comment,
                                                    @javax.ws.rs.core.Context final UriInfo uriInfo,
                                                    @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
-        return chargebackPaymentInternal(json, null, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
+        return chargebackPaymentInternal(json, null, paymentControlPluginNames, pluginPropertiesString, createdBy, reason, comment, uriInfo, request);
     }
 
     private Response chargebackPaymentInternal(final PaymentTransactionJson json,
                                                @Nullable final String paymentIdStr,
+                                               final List<String> paymentControlPluginNames,
                                                final List<String> pluginPropertiesString,
                                                final String createdBy,
                                                final String reason,
@@ -602,8 +621,10 @@ public class PaymentResource extends ComboPaymentResource {
         final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
         final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency());
 
-        final Payment payment = paymentApi.createChargeback(account, initialPayment.getId(), json.getAmount(), currency,
-                                                            json.getTransactionExternalKey(), callContext);
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+
+        final Payment payment = paymentApi.createChargebackWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
+                                                            json.getTransactionExternalKey(), paymentOptions, callContext);
         return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
     }
 
diff --git a/junction/src/test/java/org/killbill/billing/junction/TestDefaultBlockingState.java b/junction/src/test/java/org/killbill/billing/junction/TestDefaultBlockingState.java
index 2bc9e33..08616d2 100644
--- a/junction/src/test/java/org/killbill/billing/junction/TestDefaultBlockingState.java
+++ b/junction/src/test/java/org/killbill/billing/junction/TestDefaultBlockingState.java
@@ -45,7 +45,7 @@ public class TestDefaultBlockingState extends JunctionTestSuiteNoDB {
         final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(),
                                                            UUID.randomUUID(),
                                                            BlockingStateType.ACCOUNT,
-                                                           DefaultBlockingState.CLEAR_STATE_NAME,
+                                                           "OD2",
                                                            "test",
                                                            false,
                                                            false,
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java
index 81c4002..a2701e4 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/api/DefaultOverdueInternalApi.java
@@ -26,6 +26,7 @@ import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.junction.BlockingInternalApi;
 import org.killbill.billing.overdue.OverdueInternalApi;
@@ -50,6 +51,7 @@ import org.killbill.clock.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.MoreObjects;
 import com.google.inject.Inject;
 
 public class DefaultOverdueInternalApi implements OverdueInternalApi {
@@ -83,7 +85,8 @@ public class DefaultOverdueInternalApi implements OverdueInternalApi {
     public OverdueState getOverdueStateFor(final ImmutableAccountData overdueable, final TenantContext context) throws OverdueException {
         try {
             final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
-            final String stateName = accessApi.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContextFactory.createInternalTenantContext(context)).getStateName();
+            final BlockingState blockingStateForService = accessApi.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContextFactory.createInternalTenantContext(context));
+            final String stateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
             final OverdueConfig overdueConfig = overdueConfigCache.getOverdueConfig(internalTenantContext);
             final OverdueStateSet states = ((DefaultOverdueConfig) overdueConfig).getOverdueStatesAccount();
             return states.findState(stateName);
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
index 0628bae..eb71506 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
@@ -21,27 +21,26 @@ import javax.xml.bind.annotation.XmlAccessorType;
 
 import org.joda.time.LocalDate;
 import org.joda.time.Period;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.overdue.api.OverdueApiException;
 import org.killbill.billing.overdue.api.OverdueState;
 import org.killbill.billing.overdue.config.api.BillingState;
 import org.killbill.billing.overdue.config.api.OverdueStateSet;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
 import org.killbill.xmlloader.ValidatingConfig;
 import org.killbill.xmlloader.ValidationErrors;
-import org.killbill.billing.junction.DefaultBlockingState;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public abstract class DefaultOverdueStateSet extends ValidatingConfig<DefaultOverdueConfig> implements OverdueStateSet {
 
     private static final Period ZERO_PERIOD = new Period();
-    private final DefaultOverdueState clearState = new DefaultOverdueState().setName(DefaultBlockingState.CLEAR_STATE_NAME).setClearState(true);
+    private final DefaultOverdueState clearState = new DefaultOverdueState().setName(OverdueWrapper.CLEAR_STATE_NAME).setClearState(true);
 
     public abstract DefaultOverdueState[] getStates();
 
     @Override
     public OverdueState findState(final String stateName) throws OverdueApiException {
-        if (stateName.equals(DefaultBlockingState.CLEAR_STATE_NAME)) {
+        if (stateName.equals(OverdueWrapper.CLEAR_STATE_NAME)) {
             return clearState;
         }
         for (final DefaultOverdueState state : getStates()) {
@@ -52,7 +51,6 @@ public abstract class DefaultOverdueStateSet extends ValidatingConfig<DefaultOve
         throw new OverdueApiException(ErrorCode.CAT_NO_SUCH_OVEDUE_STATE, stateName);
     }
 
-
     /* (non-Javadoc)
      * @see org.killbill.billing.catalog.overdue.OverdueBillingState#findClearState()
      */
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index 8f1515f..47b4071 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -28,9 +28,12 @@ import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
 import org.killbill.billing.events.InvoicePaymentErrorInternalEvent;
 import org.killbill.billing.events.InvoicePaymentInfoInternalEvent;
 import org.killbill.billing.overdue.OverdueInternalApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.CallOrigin;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.bus.api.BusEvent;
 import org.slf4j.Logger;
@@ -46,28 +49,45 @@ public class OverdueListener {
 
     private final OverdueInternalApi overdueInternalApi;
     private final InternalCallContextFactory internalCallContextFactory;
+    private final CacheControllerDispatcher cacheControllerDispatcher;
+
+    private final NonEntityDao nonEntityDao;
 
     @Inject
     public OverdueListener(final OverdueInternalApi overdueInternalApi,
+                           final NonEntityDao nonEntityDao,
+                           final CacheControllerDispatcher cacheControllerDispatcher,
                            final InternalCallContextFactory internalCallContextFactory) {
         this.overdueInternalApi = overdueInternalApi;
+        this.nonEntityDao = nonEntityDao;
+        this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
+
+
+
     @AllowConcurrentEvents
     @Subscribe
-    public void handle_OVERDUE_ENFORCEMENT_OFF_Insert(final ControlTagCreationInternalEvent event) {
+    public void handleTagInsert(final ControlTagCreationInternalEvent event) {
         if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
             final InternalCallContext callContext = createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
             overdueInternalApi.scheduleOverdueClear(event.getObjectId(), callContext);
+        } else if (event.getTagDefinition().getName().equals(ControlTagType.WRITTEN_OFF.toString()) && event.getObjectType() == ObjectType.INVOICE) {
+            final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+            insertBusEventIntoNotificationQueue(accountId, event);
         }
     }
 
+
     @AllowConcurrentEvents
     @Subscribe
-    public void handle_OVERDUE_ENFORCEMENT_OFF_Removal(final ControlTagDeletionInternalEvent event) {
+    public void handleTagRemoval(final ControlTagDeletionInternalEvent event) {
         if (event.getTagDefinition().getName().equals(ControlTagType.OVERDUE_ENFORCEMENT_OFF.toString()) && event.getObjectType() == ObjectType.ACCOUNT) {
             insertBusEventIntoNotificationQueue(event.getObjectId(), event);
+        } else if (event.getTagDefinition().getName().equals(ControlTagType.WRITTEN_OFF.toString()) && event.getObjectType() == ObjectType.INVOICE) {
+            final UUID accountId = nonEntityDao.retrieveIdFromObject(event.getSearchKey1(), ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+            insertBusEventIntoNotificationQueue(accountId, event);
         }
     }
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
index a7c14f8..95a2b7d 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapper.java
@@ -21,6 +21,7 @@ package org.killbill.billing.overdue.wrapper;
 import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.junction.BlockingInternalApi;
 import org.killbill.billing.overdue.OverdueService;
@@ -39,8 +40,13 @@ import org.killbill.commons.locker.LockFailedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.MoreObjects;
+
 public class OverdueWrapper {
 
+
+    public static final String CLEAR_STATE_NAME = "__KILLBILL__CLEAR__OVERDUE_STATE__";
+
     private static final Logger log = LoggerFactory.getLogger(OverdueWrapper.class);
 
     // Should we introduce a config?
@@ -93,8 +99,8 @@ public class OverdueWrapper {
 
     private OverdueState refreshWithLock(final InternalCallContext context) throws OverdueException, OverdueApiException {
         final BillingState billingState = billingState(context);
-        final String previousOverdueStateName = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context).getStateName();
-
+        final BlockingState blockingStateForService = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context);
+        final String previousOverdueStateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
         final OverdueState currentOverdueState = overdueStateSet.findState(previousOverdueStateName);
         final OverdueState nextOverdueState = overdueStateSet.calculateOverdueState(billingState, clock.getToday(billingState.getAccountTimeZone()));
 
@@ -120,7 +126,8 @@ public class OverdueWrapper {
     }
 
     private void clearWithLock(final InternalCallContext context) throws OverdueException, OverdueApiException {
-        final String previousOverdueStateName = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context).getStateName();
+        final BlockingState blockingStateForService = api.getBlockingStateForService(overdueable.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, context);
+        final String previousOverdueStateName = blockingStateForService != null ? blockingStateForService.getStateName() : OverdueWrapper.CLEAR_STATE_NAME;
         final OverdueState previousOverdueState = overdueStateSet.findState(previousOverdueStateName);
         overdueStateApplicator.clear(overdueable, previousOverdueState, overdueStateSet.getClearState(), context);
     }
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
index 5c77f5c..2222377 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
@@ -25,6 +25,7 @@ import java.util.concurrent.Callable;
 import org.killbill.billing.account.api.ImmutableAccountData;
 import org.killbill.billing.overdue.api.OverdueState;
 import org.killbill.billing.overdue.config.DefaultOverdueConfig;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
 import org.mockito.Mockito;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -49,7 +50,7 @@ public class TestOverdueStateApplicator extends OverdueTestSuiteWithEmbeddedDB {
         Mockito.when(account.getId()).thenReturn(UUID.randomUUID());
 
         final OverdueStateSet overdueStateSet = config.getOverdueStatesAccount();
-        final OverdueState clearState = config.getOverdueStatesAccount().findState(DefaultBlockingState.CLEAR_STATE_NAME);
+        final OverdueState clearState = config.getOverdueStatesAccount().findState(OverdueWrapper.CLEAR_STATE_NAME);
         OverdueState state;
 
         state = config.getOverdueStatesAccount().findState("OD1");
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
index 142f39d..df5a469 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
@@ -21,6 +21,7 @@ package org.killbill.billing.overdue.glue;
 import java.util.List;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.entitlement.api.BlockingState;
@@ -37,6 +38,7 @@ import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
 import org.killbill.billing.overdue.caching.MockOverdueConfigCache;
 import org.killbill.billing.overdue.caching.OverdueCacheInvalidationCallback;
 import org.killbill.billing.overdue.caching.OverdueConfigCache;
+import org.killbill.billing.overdue.wrapper.OverdueWrapper;
 import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.tenant.api.TenantInternalApi.CacheInvalidationCallback;
 import org.killbill.billing.util.email.EmailModule;
@@ -46,6 +48,7 @@ import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
 import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
+import org.killbill.clock.Clock;
 import org.killbill.clock.ClockMock;
 
 import com.google.inject.name.Names;
@@ -90,12 +93,13 @@ public class TestOverdueModule extends DefaultOverdueModule {
             return blockingState;
         }
 
+
         @Override
         public BlockingState getBlockingStateForService(final UUID blockableId, final BlockingStateType blockingStateType, final String serviceName, final InternalTenantContext context) {
             if (blockingState != null && blockingState.getBlockedId().equals(blockableId)) {
                 return blockingState;
             } else {
-                return DefaultBlockingState.getClearState(blockingStateType, serviceName, new ClockMock());
+                return new DefaultBlockingState(null, blockingStateType, OverdueWrapper.CLEAR_STATE_NAME, serviceName, false, false, false, null);
             }
         }
 
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java b/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java
index c6e2231..2c22056 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java
@@ -76,7 +76,7 @@ public class TestOverdueWrapper extends OverdueTestSuiteWithEmbeddedDB {
 
         final InputStream is = new ByteArrayInputStream(testOverdueHelper.getConfigXml().getBytes());
         final DefaultOverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, DefaultOverdueConfig.class);
-        state = config.getOverdueStatesAccount().findState(DefaultBlockingState.CLEAR_STATE_NAME);
+        state = config.getOverdueStatesAccount().findState(OverdueWrapper.CLEAR_STATE_NAME);
         account = testOverdueHelper.createImmutableAccountData(clock.getUTCToday().minusDays(31));
         wrapper = overdueWrapperFactory.createOverdueWrapperFor(account, internalCallContext);
         final OverdueState result = wrapper.refresh(internalCallContext);
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
index f6c86e6..14e7ab0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
@@ -72,21 +72,26 @@ public class DefaultPayment extends EntityBase implements Payment {
 
     private static BigDecimal getAmountForType(final Iterable<PaymentTransaction> transactions, final TransactionType transactiontype) {
         BigDecimal result = BigDecimal.ZERO;
-        final Iterable<PaymentTransaction> filtered = Iterables.filter(transactions, new Predicate<PaymentTransaction>() {
-            @Override
-            public boolean apply(final PaymentTransaction input) {
-                return input.getTransactionType() == transactiontype && TransactionStatus.SUCCESS.equals(input.getTransactionStatus());
+        BigDecimal processedResult = BigDecimal.ZERO;
+        boolean shouldUseProcessedAmount = true;
+
+        for (final PaymentTransaction transaction : transactions) {
+            if (transaction.getTransactionType() != transactiontype || !TransactionStatus.SUCCESS.equals(transaction.getTransactionStatus())) {
+                continue;
             }
-        });
-        if (TransactionType.AUTHORIZE.equals(transactiontype) && filtered.iterator().hasNext()) {
-            // HACK - For multi-step AUTH, don't sum the individual transactions
-            result = filtered.iterator().next().getAmount();
-        } else {
-            for (final PaymentTransaction dpt : filtered) {
-                result = result.add(dpt.getAmount());
+
+            result = result.add(transaction.getAmount());
+
+            shouldUseProcessedAmount = shouldUseProcessedAmount && transaction.getCurrency().equals(transaction.getProcessedCurrency()) && transaction.getProcessedAmount() != null;
+            processedResult = shouldUseProcessedAmount ? processedResult.add(transaction.getProcessedAmount()) : BigDecimal.ZERO;
+
+            // For multi-step AUTH, don't sum the individual transactions
+            if (TransactionType.AUTHORIZE.equals(transactiontype)) {
+                break;
             }
         }
-        return result;
+
+        return shouldUseProcessedAmount ? processedResult : result;
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
index 15dc191..c71407d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
@@ -211,11 +211,23 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
         // Recompute new lastSuccessPaymentState. This is important to be able to allow new operations on the state machine (for e.g an AUTH_SUCCESS would now allow a CAPTURE operation)
         final String lastSuccessPaymentState = paymentStateMachineHelper.isSuccessState(newPaymentState) ? newPaymentState : null;
 
-        // Update the processedAmount, processedCurrency if we got a paymentTransactionInfoPlugin from the plugin and if this is a non error state
-        final BigDecimal processedAmount = (paymentTransactionInfoPlugin != null && isPendingOrFinalTransactionStatus(transactionStatus)) ?
-                                           paymentTransactionInfoPlugin.getAmount() : paymentTransaction.getProcessedAmount();
-        final Currency processedCurrency = (paymentTransactionInfoPlugin != null && isPendingOrFinalTransactionStatus(transactionStatus)) ?
-                                           paymentTransactionInfoPlugin.getCurrency() : paymentTransaction.getProcessedCurrency();
+        // Update processedAmount and processedCurrency
+        final BigDecimal processedAmount;
+        if (TransactionStatus.SUCCESS.equals(transactionStatus) || TransactionStatus.PENDING.equals(transactionStatus)) {
+            if (paymentTransactionInfoPlugin == null || paymentTransactionInfoPlugin.getAmount() == null) {
+                processedAmount = paymentTransaction.getProcessedAmount();
+            } else {
+                processedAmount = paymentTransactionInfoPlugin.getAmount();
+            }
+        } else {
+            processedAmount = BigDecimal.ZERO;
+        }
+        final Currency processedCurrency;
+        if (paymentTransactionInfoPlugin == null || paymentTransactionInfoPlugin.getCurrency() == null) {
+            processedCurrency = paymentTransaction.getProcessedCurrency();
+        } else {
+            processedCurrency = paymentTransactionInfoPlugin.getCurrency();
+        }
 
         // Update the gatewayErrorCode, gatewayError if we got a paymentTransactionInfoPlugin
         final String gatewayErrorCode = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayErrorCode() : paymentTransaction.getGatewayErrorCode();
@@ -239,12 +251,6 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
         return (newTransactionStatus != TransactionStatus.UNKNOWN) ? newTransactionStatus : currentTransactionStatus;
     }
 
-    private boolean isPendingOrFinalTransactionStatus(final TransactionStatus transactionStatus) {
-        return (transactionStatus == TransactionStatus.PENDING ||
-                transactionStatus == TransactionStatus.SUCCESS ||
-                transactionStatus == TransactionStatus.PAYMENT_FAILURE);
-    }
-
     private PaymentPluginApi getPaymentPluginApi(final PaymentModelDao item, final String pluginName) {
         final PaymentPluginApi pluginApi = pluginRegistry.getServiceForName(pluginName);
         Preconditions.checkState(pluginApi != null, "Janitor IncompletePaymentTransactionTask cannot retrieve PaymentPluginApi for plugin %s (payment id %s), skipping", pluginName, item.getId());
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
index 2b3a215..c6545dc 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
@@ -30,6 +30,7 @@ import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentMethodModelDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
@@ -89,7 +90,10 @@ public class PaymentAutomatonDAOHelper {
             if (existingTransactions.isEmpty()) {
                 throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_SUCCESS_PAYMENT, paymentStateContext.getPaymentId());
             }
-            if (paymentStateContext.getCurrency() != null && existingTransactions.get(0).getCurrency() != paymentStateContext.getCurrency()) {
+            if (paymentStateContext.getCurrency() != null &&
+                existingTransactions.get(0).getCurrency() != paymentStateContext.getCurrency() &&
+                !TransactionType.CHARGEBACK.equals(paymentStateContext.getTransactionType())) {
+                // Note that we allow chargebacks in a different currency
                 throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "currency", " should be " + existingTransactions.get(0).getCurrency() + " to match other existing transactions");
             }
 
@@ -103,8 +107,22 @@ public class PaymentAutomatonDAOHelper {
 
     public void processPaymentInfoPlugin(final TransactionStatus transactionStatus, @Nullable final PaymentTransactionInfoPlugin paymentInfoPlugin,
                                          final String currentPaymentStateName) {
-        final BigDecimal processedAmount = paymentInfoPlugin == null ? null : paymentInfoPlugin.getAmount();
-        final Currency processedCurrency = paymentInfoPlugin == null ? null : paymentInfoPlugin.getCurrency();
+        final BigDecimal processedAmount;
+        if (TransactionStatus.SUCCESS.equals(transactionStatus) || TransactionStatus.PENDING.equals(transactionStatus)) {
+            if (paymentInfoPlugin == null || paymentInfoPlugin.getAmount() == null) {
+                processedAmount = paymentStateContext.getAmount();
+            } else {
+                processedAmount = paymentInfoPlugin.getAmount();
+            }
+        } else {
+            processedAmount = BigDecimal.ZERO;
+        }
+        final Currency processedCurrency;
+        if (paymentInfoPlugin == null || paymentInfoPlugin.getCurrency() == null) {
+            processedCurrency = paymentStateContext.getCurrency();
+        } else {
+            processedCurrency = paymentInfoPlugin.getCurrency();
+        }
         final String gatewayErrorCode = paymentInfoPlugin == null ? null : paymentInfoPlugin.getGatewayErrorCode();
         final String gatewayErrorMsg = paymentInfoPlugin == null ? null : paymentInfoPlugin.getGatewayError();
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java
index a2993a6..cf477d5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackOperation.java
@@ -17,14 +17,11 @@
 
 package org.killbill.billing.payment.core.sm.payments;
 
-import java.math.BigDecimal;
-
 import org.killbill.automaton.OperationResult;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper;
 import org.killbill.billing.payment.core.sm.PaymentStateContext;
-import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
@@ -50,28 +47,6 @@ public class ChargebackOperation extends PaymentOperation {
     @Override
     protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
         logger.debug("Starting CHARGEBACK for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
-
-        final PaymentPluginStatus status;
-        if (!paymentStateContext.getOnLeavingStateExistingTransactions().isEmpty()) {
-            final Iterable<PaymentTransactionModelDao> purchaseTransactions = getOnLeavingStateExistingTransactionsForType(TransactionType.PURCHASE);
-            final Iterable<PaymentTransactionModelDao> captureTransactions = getOnLeavingStateExistingTransactionsForType(TransactionType.CAPTURE);
-            final Iterable<PaymentTransactionModelDao> refundTransactions = getOnLeavingStateExistingTransactionsForType(TransactionType.REFUND);
-            final Iterable<PaymentTransactionModelDao> chargebackTransactions = getOnLeavingStateExistingTransactionsForType(TransactionType.CHARGEBACK);
-
-            final BigDecimal purchasedAmount = getSumAmount(purchaseTransactions);
-            final BigDecimal capturedAmount = getSumAmount(captureTransactions);
-            final BigDecimal refundedAmount = getSumAmount(refundTransactions);
-            final BigDecimal chargebackAmount = getSumAmount(chargebackTransactions);
-            final BigDecimal chargebackAvailableAmount = purchasedAmount.add(capturedAmount).subtract(refundedAmount.add(chargebackAmount));
-
-            if (paymentStateContext.getAmount().compareTo(chargebackAvailableAmount) > 0) {
-                status = PaymentPluginStatus.ERROR;
-            } else {
-                status = PaymentPluginStatus.PROCESSED;
-            }
-        } else {
-            status = PaymentPluginStatus.PROCESSED;
-        }
         return new DefaultNoOpPaymentInfoPlugin(paymentStateContext.getPaymentId(),
                                                 paymentStateContext.getTransactionId(),
                                                 TransactionType.CHARGEBACK,
@@ -79,7 +54,7 @@ public class ChargebackOperation extends PaymentOperation {
                                                 paymentStateContext.getCurrency(),
                                                 null,
                                                 null,
-                                                status,
+                                                PaymentPluginStatus.PROCESSED,
                                                 null,
                                                 null);
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java
index 612fed7..b15be00 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java
@@ -1,7 +1,8 @@
 /*
- * Copyright 2014 Groupon, Inc
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Groupon licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -103,6 +104,21 @@ public class PaymentModelDao extends EntityModelDaoBase implements EntityModelDa
     }
 
     @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("PaymentModelDao{");
+        sb.append("accountId=").append(accountId);
+        sb.append(", paymentNumber=").append(paymentNumber);
+        sb.append(", paymentMethodId=").append(paymentMethodId);
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", stateName='").append(stateName).append('\'');
+        sb.append(", lastSuccessStateName='").append(lastSuccessStateName).append('\'');
+        sb.append(", createdDate=").append(createdDate);
+        sb.append(", updatedDate=").append(updatedDate);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
     public boolean equals(final Object o) {
         if (this == o) {
             return true;
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentTransactionModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentTransactionModelDao.java
index ac0f0da..3f11cf9 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentTransactionModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentTransactionModelDao.java
@@ -1,7 +1,8 @@
 /*
- * Copyright 2014 Groupon, Inc
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Groupon licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -225,6 +226,27 @@ public class PaymentTransactionModelDao extends EntityModelDaoBase implements En
     }
 
     @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("PaymentTransactionModelDao{");
+        sb.append("attemptId=").append(attemptId);
+        sb.append(", paymentId=").append(paymentId);
+        sb.append(", transactionExternalKey='").append(transactionExternalKey).append('\'');
+        sb.append(", transactionType=").append(transactionType);
+        sb.append(", effectiveDate=").append(effectiveDate);
+        sb.append(", transactionStatus=").append(transactionStatus);
+        sb.append(", amount=").append(amount);
+        sb.append(", currency=").append(currency);
+        sb.append(", processedAmount=").append(processedAmount);
+        sb.append(", processedCurrency=").append(processedCurrency);
+        sb.append(", gatewayErrorCode='").append(gatewayErrorCode).append('\'');
+        sb.append(", gatewayErrorMsg='").append(gatewayErrorMsg).append('\'');
+        sb.append(", createdDate=").append(createdDate);
+        sb.append(", updatedDate=").append(updatedDate);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
     public int hashCode() {
         int result = super.hashCode();
         result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
index b5be8c5..d8012d2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/CallableWithRequestData.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -17,23 +17,27 @@
 
 package org.killbill.billing.payment.dispatcher;
 
+import java.util.Random;
 import java.util.concurrent.Callable;
 
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.util.ThreadContext;
+import org.killbill.billing.util.UUIDs;
 import org.killbill.commons.request.Request;
 import org.killbill.commons.request.RequestData;
 
 public class CallableWithRequestData<T> implements Callable<T> {
 
     private final RequestData requestData;
+    private final Random random;
     private final SecurityManager securityManager;
     private final Subject subject;
     private final Callable<T> delegate;
 
-    public CallableWithRequestData(final RequestData requestData, final SecurityManager securityManager, final Subject subject, final Callable<T> delegate) {
+    public CallableWithRequestData(final RequestData requestData, final Random random, final SecurityManager securityManager, final Subject subject, final Callable<T> delegate) {
         this.requestData = requestData;
+        this.random = random;
         this.securityManager = securityManager;
         this.subject = subject;
         this.delegate = delegate;
@@ -43,11 +47,13 @@ public class CallableWithRequestData<T> implements Callable<T> {
     public T call() throws Exception {
         try {
             Request.setPerThreadRequestData(requestData);
+            UUIDs.setRandom(random);
             ThreadContext.bind(securityManager);
             ThreadContext.bind(subject);
             return delegate.call();
         } finally {
             Request.resetPerThreadRequestData();
+            UUIDs.setRandom(null);
             ThreadContext.unbindSecurityManager();
             ThreadContext.unbindSubject();
         }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
index a457e17..fa65e1b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -27,6 +27,7 @@ import java.util.concurrent.TimeoutException;
 
 import org.apache.shiro.util.ThreadContext;
 import org.killbill.billing.payment.core.PaymentExecutors;
+import org.killbill.billing.util.UUIDs;
 import org.killbill.commons.profiling.Profiling;
 import org.killbill.commons.profiling.ProfilingData;
 import org.killbill.commons.request.Request;
@@ -57,7 +58,11 @@ public class PluginDispatcher<ReturnType> {
         final ExecutorService pluginExecutor = paymentExecutors.getPluginExecutorService();
 
         // Wrap existing callable to keep the original requestId
-        final Callable<PluginDispatcherReturnType<ReturnType>> callableWithRequestData = new CallableWithRequestData(Request.getPerThreadRequestData(), ThreadContext.getSecurityManager(), ThreadContext.getSubject(), task);
+        final Callable<PluginDispatcherReturnType<ReturnType>> callableWithRequestData = new CallableWithRequestData(Request.getPerThreadRequestData(),
+                                                                                                                     UUIDs.getRandom(),
+                                                                                                                     ThreadContext.getSecurityManager(),
+                                                                                                                     ThreadContext.getSubject(),
+                                                                                                                     task);
 
         final Future<PluginDispatcherReturnType<ReturnType>> future = pluginExecutor.submit(callableWithRequestData);
         final PluginDispatcherReturnType<ReturnType> pluginDispatcherResult = future.get(timeout, unit);
diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
index 86b22f2..7ccaba6 100644
--- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
@@ -33,6 +33,7 @@ import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
 import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult;
 import org.killbill.billing.control.plugin.api.PaymentApiType;
@@ -152,11 +153,19 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                     final UUID invoiceId = getInvoiceId(pluginProperties);
                     existingInvoicePayment = invoiceApi.getInvoicePaymentForAttempt(paymentControlContext.getPaymentId(), internalContext);
                     if (existingInvoicePayment != null && existingInvoicePayment.isSuccess()) {
+                        // Only one successful purchase per payment (the invoice could be linked to multiple successful payments though)
                         log.info("onSuccessCall was already completed for payment purchase: " + paymentControlContext.getPaymentId());
                     } else {
-                        log.debug("Notifying invoice of successful payment: id={}, amount={}, currency={}, invoiceId={}", paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), paymentControlContext.getCurrency(), invoiceId);
+                        final BigDecimal invoicePaymentAmount;
+                        if (paymentControlContext.getCurrency() == paymentControlContext.getProcessedCurrency()) {
+                            invoicePaymentAmount = paymentControlContext.getProcessedAmount();
+                        } else {
+                            log.warn("Currency {} of invoice payment {} doesn't match invoice currency {}, assuming it is a full payment" , paymentControlContext.getProcessedCurrency(), paymentControlContext.getPaymentId(), paymentControlContext.getCurrency());
+                            invoicePaymentAmount = paymentControlContext.getAmount();
+                        }
+                        log.debug("Notifying invoice of successful payment: id={}, amount={}, currency={}, invoiceId={}", paymentControlContext.getPaymentId(), invoicePaymentAmount, paymentControlContext.getCurrency(), invoiceId);
                         invoiceApi.notifyOfPayment(invoiceId,
-                                                   paymentControlContext.getAmount(),
+                                                   invoicePaymentAmount,
                                                    paymentControlContext.getCurrency(),
                                                    paymentControlContext.getProcessedCurrency(),
                                                    paymentControlContext.getPaymentId(),
@@ -167,23 +176,34 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                     break;
 
                 case REFUND:
-                    existingInvoicePayment = invoiceApi.getInvoicePaymentForRefund(paymentControlContext.getPaymentId(), internalContext);
-                    if (existingInvoicePayment != null) {
-                        log.info("onSuccessCall was already completed for payment refund: " + paymentControlContext.getPaymentId());
-                    } else {
-                        final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(pluginProperties);
-                        final PluginProperty prop = getPluginProperty(pluginProperties, PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
-                        final boolean isAdjusted = prop != null ? Boolean.valueOf((String) prop.getValue()) : false;
-                        invoiceApi.createRefund(paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), isAdjusted, idWithAmount, paymentControlContext.getTransactionExternalKey(), internalContext);
-                    }
+                    final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(pluginProperties);
+                    final PluginProperty prop = getPluginProperty(pluginProperties, PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
+                    final boolean isAdjusted = prop != null ? Boolean.valueOf((String) prop.getValue()) : false;
+                    invoiceApi.createRefund(paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), isAdjusted, idWithAmount, paymentControlContext.getTransactionExternalKey(), internalContext);
                     break;
 
                 case CHARGEBACK:
                     existingInvoicePayment = invoiceApi.getInvoicePaymentForChargeback(paymentControlContext.getPaymentId(), internalContext);
                     if (existingInvoicePayment != null) {
+                        // We don't support partial chargebacks (yet?)
                         log.info("onSuccessCall was already completed for payment chargeback: " + paymentControlContext.getPaymentId());
                     } else {
-                        invoiceApi.createChargeback(paymentControlContext.getPaymentId(), paymentControlContext.getProcessedAmount(), paymentControlContext.getProcessedCurrency(), internalContext);
+                        final InvoicePayment linkedInvoicePayment = invoiceApi.getInvoicePaymentForAttempt(paymentControlContext.getPaymentId(), internalContext);
+
+                        final BigDecimal amount;
+                        final Currency currency;
+                        if (linkedInvoicePayment.getCurrency().equals(paymentControlContext.getProcessedCurrency()) && paymentControlContext.getProcessedAmount() != null) {
+                            amount = paymentControlContext.getProcessedAmount();
+                            currency = paymentControlContext.getProcessedCurrency();
+                        } else if (linkedInvoicePayment.getCurrency().equals(paymentControlContext.getCurrency()) && paymentControlContext.getAmount() != null) {
+                            amount = paymentControlContext.getAmount();
+                            currency = paymentControlContext.getCurrency();
+                        } else {
+                            amount = linkedInvoicePayment.getAmount();
+                            currency = linkedInvoicePayment.getCurrency();
+                        }
+
+                        invoiceApi.createChargeback(paymentControlContext.getPaymentId(), amount, currency, internalContext);
                     }
                     break;
 
@@ -209,7 +229,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                 try {
                     log.debug("Notifying invoice of failed payment: id={}, amount={}, currency={}, invoiceId={}", paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), paymentControlContext.getCurrency(), invoiceId);
                     invoiceApi.notifyOfPayment(invoiceId,
-                                               paymentControlContext.getAmount(),
+                                               BigDecimal.ZERO,
                                                paymentControlContext.getCurrency(),
                                                // processed currency may be null so we use currency; processed currency will be updated if/when payment succeeds
                                                paymentControlContext.getCurrency(),
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index d5c1aa5..d6e293e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -182,7 +182,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
         assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
         assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.AED);
-        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.AED);
 
         assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
@@ -405,7 +405,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
         assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
         assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.USD);
-        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0); // This is weird...
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.USD);
 
         assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
index 650bd00..41155e8 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
@@ -1,8 +1,8 @@
 /*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Groupon licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -21,6 +21,7 @@ import java.math.BigDecimal;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -45,8 +46,6 @@ import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.memory.MemoryGlobalLocker;
 import org.mockito.Mockito;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -56,13 +55,13 @@ import com.jayway.awaitility.Awaitility;
 
 public class TestPluginOperation extends PaymentTestSuiteNoDB {
 
-    private final static String PLUGIN_NAME_PLACEHOLDER = "pluginName";
+    private static final String PLUGIN_NAME_PLACEHOLDER = "pluginName";
+
+    private static final int TIMEOUT = 5;
 
     private final GlobalLocker locker = new MemoryGlobalLocker();
     private final Account account = Mockito.mock(Account.class);
 
-    private static final Logger logger = LoggerFactory.getLogger(TestPluginOperation.class);
-
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
@@ -70,11 +69,10 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testWithAccountLock() throws Exception {
-        testLocking(true);
+    public void testAccountLock() throws Exception {
+        testLocking();
     }
 
-
     @Test(groups = "fast")
     public void testOperationThrowsPaymentApiException() throws Exception {
         final CallbackTest callback = new CallbackTest(new PaymentApiException(ErrorCode.__UNKNOWN_ERROR_CODE));
@@ -103,30 +101,31 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
         }
     }
 
-    private void testLocking(final boolean withAccountLock) throws Exception {
+    private void testLocking() throws Exception {
         final Semaphore available = new Semaphore(1, true);
         final CallbackTest callback = new CallbackTest(available);
-        final PaymentOperation pluginOperation = getPluginOperation(withAccountLock);
+        final PaymentOperation pluginOperation = getPluginOperation(true);
 
         // Take the only permit
         available.acquire();
 
         // Start the plugin operation in the background (will block)
         runPluginOperationInBackground(pluginOperation, callback, false);
+        Awaitility.await()
+                  .until(new Callable<Boolean>() {
+                      @Override
+                      public Boolean call() throws Exception {
+                          return callback.getStartCount() == 1;
+                      }
+                  });
 
         // The operation should be blocked here because we have the semaphore
-        Assert.assertEquals(available.getQueueLength(), 1);
         Assert.assertEquals(callback.getRunCount(), 0);
 
-        if (withAccountLock) {
-            // If the account is locked, trying to run the operation again will throw LockFailedException
-            runPluginOperationInBackground(pluginOperation, callback, true);
-        } else {
-            // If the account is not locked, it will just block
-            runPluginOperationInBackground(pluginOperation, callback, false);
-            Assert.assertEquals(available.getQueueLength(), 2);
-            Assert.assertEquals(callback.getRunCount(), 0);
-        }
+        // Trying to run the operation again will throw LockFailedException
+        Awaitility.await().atMost(2 * TIMEOUT, TimeUnit.SECONDS).untilTrue(runPluginOperationInBackground(pluginOperation, callback, true));
+
+        Assert.assertEquals(callback.getRunCount(), 0);
 
         // Release the semaphore
         available.release();
@@ -136,37 +135,41 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
                   .until(new Callable<Boolean>() {
                       @Override
                       public Boolean call() throws Exception {
-                          return callback.getRunCount() == (withAccountLock ? 1 : 2);
+                          return callback.getRunCount() == 1;
                       }
                   });
 
         // Verify the final state
-        Assert.assertEquals(available.getQueueLength(), 0);
-        Assert.assertEquals(callback.getRunCount(), withAccountLock ? 1 : 2);
+        Assert.assertEquals(available.availablePermits(), 1);
     }
 
-    private void runPluginOperationInBackground(final PaymentOperation pluginOperation, final CallbackTest callback, final boolean shouldFailBecauseOfLockFailure) throws Exception {
-        final AtomicBoolean threadStarted = new AtomicBoolean(false);
+    private AtomicBoolean runPluginOperationInBackground(final PaymentOperation pluginOperation, final CallbackTest callback, final boolean shouldFailBecauseOfLockFailure) throws Exception {
+        final AtomicBoolean threadRunning = new AtomicBoolean(false);
+        final AtomicBoolean threadHasRun = new AtomicBoolean(false);
         final Thread t1 = new Thread(new Runnable() {
             @Override
             public void run() {
-                threadStarted.set(true);
-
-                if (shouldFailBecauseOfLockFailure) {
-                    try {
-                        pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
-                        Assert.fail();
-                    } catch (final OperationException e) {
-                        Assert.assertTrue(e.getCause() instanceof PaymentApiException);
-                        // No better error code for lock failures...
-                        Assert.assertEquals(((PaymentApiException) e.getCause()).getCode(), ErrorCode.PAYMENT_INTERNAL_ERROR.getCode());
-                    }
-                } else {
-                    try {
-                        pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
-                    } catch (final OperationException e) {
-                        Assert.fail(e.getMessage());
+                threadRunning.set(true);
+
+                try {
+                    if (shouldFailBecauseOfLockFailure) {
+                        try {
+                            pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
+                            Assert.fail();
+                        } catch (final OperationException e) {
+                            Assert.assertTrue(e.getCause() instanceof PaymentApiException);
+                            // No better error code for lock failures...
+                            Assert.assertEquals(((PaymentApiException) e.getCause()).getCode(), ErrorCode.PAYMENT_INTERNAL_ERROR.getCode());
+                        }
+                    } else {
+                        try {
+                            pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
+                        } catch (final OperationException e) {
+                            Assert.fail(e.getMessage());
+                        }
                     }
+                } finally {
+                    threadHasRun.set(true);
                 }
             }
         });
@@ -174,7 +177,9 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
         t1.start();
 
         // Make sure the thread has started
-        Awaitility.await().untilTrue(threadStarted);
+        Awaitility.await().untilTrue(threadRunning);
+
+        return threadHasRun;
     }
 
     private PaymentOperation getPluginOperation() throws PaymentApiException {
@@ -182,7 +187,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
     }
 
     private PaymentOperation getPluginOperation(final boolean shouldLockAccount) throws PaymentApiException {
-        return getPluginOperation(shouldLockAccount, Integer.MAX_VALUE);
+        return getPluginOperation(shouldLockAccount, TIMEOUT);
     }
 
     private PaymentOperation getPluginOperation(final boolean shouldLockAccount, final int timeoutSeconds) throws PaymentApiException {
@@ -209,6 +214,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
 
     private static final class CallbackTest implements DispatcherCallback<PluginDispatcherReturnType<OperationResult>, PaymentApiException> {
 
+        private final AtomicInteger startCount = new AtomicInteger(0);
         private final AtomicInteger runCount = new AtomicInteger(0);
 
         private final Semaphore available;
@@ -242,9 +248,11 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
 
         @Override
         public PluginDispatcherReturnType<OperationResult> doOperation() throws PaymentApiException {
+            startCount.incrementAndGet();
+
             try {
                 if (available != null) {
-                    available.acquire();
+                    available.acquireUninterruptibly();
                 }
 
                 if (sleepTimeMillis != null) {
@@ -272,6 +280,10 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
         public int getRunCount() {
             return runCount.get();
         }
+
+        public int getStartCount() {
+            return startCount.get();
+        }
     }
 
     private static final class PluginOperationTest extends PaymentOperation {
diff --git a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
index 06077cc..f2cd9c8 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -25,6 +27,7 @@ import org.killbill.billing.ErrorCode;
 import org.killbill.billing.payment.PaymentTestSuiteNoDB;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.billing.util.UUIDs;
 import org.killbill.commons.request.Request;
 import org.killbill.commons.request.RequestData;
 import org.testng.Assert;
@@ -134,6 +137,7 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
         };
 
         final CallableWithRequestData<PluginDispatcherReturnType<String>> callable = new CallableWithRequestData<PluginDispatcherReturnType<String>>(new RequestData(requestId),
+                                                                                                                                                     UUIDs.getRandom(),
                                                                                                                                                      null,
                                                                                                                                                      null,
                                                                                                                                                      delegate);
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java b/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java
index ae2c6aa..46a82ab 100644
--- a/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/MockEntitlementModuleForPayment.java
@@ -43,7 +43,7 @@ public class MockEntitlementModuleForPayment extends MockEntitlementModule {
     public void installBlockingApi() {
         super.installBlockingApi();
 
-        final BlockingState blockingState = new DefaultBlockingState(null, BlockingStateType.ACCOUNT, DefaultBlockingState.CLEAR_STATE_NAME, "test", false, false, false, new DateTime(DateTimeZone.UTC));
+        final BlockingState blockingState = new DefaultBlockingState(null, BlockingStateType.ACCOUNT, "CLEAR_STATE_NAME", "test", false, false, false, new DateTime(DateTimeZone.UTC));
         Mockito.when(blockingApi.getBlockingAllForAccount(Mockito.<InternalTenantContext>any())).thenReturn(ImmutableList.<BlockingState>of(blockingState));
         Mockito.when(blockingApi.getBlockingStateForService(Mockito.<UUID>any(), Mockito.<BlockingStateType>any(), Mockito.anyString(), Mockito.<InternalTenantContext>any())).thenReturn(blockingState);
     }
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
index 8788e73..cc8c6e1 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -27,6 +27,9 @@ import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.Nullable;
 
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
@@ -46,6 +49,7 @@ import org.killbill.billing.util.entity.DefaultPagination;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.clock.Clock;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -68,6 +72,8 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
     private final AtomicBoolean makeNextInvoiceFailWithException = new AtomicBoolean(false);
     private final AtomicBoolean makeAllInvoicesFailWithError = new AtomicBoolean(false);
     private final AtomicInteger makePluginWaitSomeMilliseconds = new AtomicInteger(0);
+    private final AtomicReference<BigDecimal> overrideNextProcessedAmount = new AtomicReference<BigDecimal>();
+    private final AtomicReference<Currency> overrideNextProcessedCurrency = new AtomicReference<Currency>();
 
     private final Map<String, InternalPaymentInfo> payments = new ConcurrentHashMap<String, InternalPaymentInfo>();
     private final Map<String, List<PaymentTransactionInfoPlugin>> paymentTransactions = new ConcurrentHashMap<String, List<PaymentTransactionInfoPlugin>>();
@@ -192,6 +198,7 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
         makeAllInvoicesFailWithError.set(false);
         makeNextInvoiceFailWithError.set(false);
         makePluginWaitSomeMilliseconds.set(0);
+        overrideNextProcessedAmount.set(null);
         paymentMethods.clear();
         payments.clear();
         paymentTransactions.clear();
@@ -214,6 +221,14 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
         makePluginWaitSomeMilliseconds.set(milliseconds);
     }
 
+    public void overrideNextProcessedAmount(final BigDecimal amount) {
+        overrideNextProcessedAmount.set(amount);
+    }
+
+    public void overrideNextProcessedCurrency(final Currency currency) {
+        overrideNextProcessedCurrency.set(currency);
+    }
+
     public void updatePaymentTransactions(final UUID paymentId, final List<PaymentTransactionInfoPlugin> newTransactions) {
         if (paymentTransactions.containsKey(paymentId.toString())) {
             paymentTransactions.put (paymentId.toString(), newTransactions);
@@ -343,7 +358,7 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
         return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency, properties);
     }
 
-    private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType type, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> pluginProperties) throws PaymentPluginApiException {
+    private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType type, final BigDecimal amount, @Nullable final Currency currency, final Iterable<PluginProperty> pluginProperties) throws PaymentPluginApiException {
         if (makePluginWaitSomeMilliseconds.get() > 0) {
             try {
                 Thread.sleep(makePluginWaitSomeMilliseconds.get());
@@ -379,7 +394,13 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
             payments.put(kbPaymentId.toString(), info);
         }
 
-        final PaymentTransactionInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, type, amount, currency, clock.getUTCNow(), clock.getUTCNow(), status, errorCode, error);
+        final BigDecimal processedAmount = MoreObjects.firstNonNull(overrideNextProcessedAmount.getAndSet(null), amount);
+        Currency processedCurrency = overrideNextProcessedCurrency.getAndSet(null);
+        if (processedCurrency == null) {
+            processedCurrency = currency;
+        }
+
+        final PaymentTransactionInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, type, processedAmount, processedCurrency, clock.getUTCNow(), clock.getUTCNow(), status, errorCode, error);
         List<PaymentTransactionInfoPlugin> existingTransactions = paymentTransactions.get(kbPaymentId.toString());
         if (existingTransactions == null) {
             existingTransactions = new ArrayList<PaymentTransactionInfoPlugin>();
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
index f62e802..0ba6343 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -80,6 +80,8 @@ import static org.testng.Assert.fail;
 
 public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
 
+    private static final int TIMEOUT = 10;
+
     final PaymentOptions INVOICE_PAYMENT = new PaymentOptions() {
         @Override
         public boolean isExternalPayment() {
@@ -205,7 +207,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
     }
 
     @Test(groups = "slow")
-    public void testCreateSuccessRefundPaymentControlWithItemAdjustments() throws PaymentApiException, InvoiceApiException, EventBusException {
+    public void testCreateSuccessRefundPaymentControlWithItemAdjustments() throws Exception {
 
         final BigDecimal requestedAmount = BigDecimal.TEN;
         final UUID subscriptionId = UUID.randomUUID();
@@ -263,13 +265,14 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(attempt2.getStateName(), "INIT");
 
         clock.addDays(1);
-        try {
-            Thread.sleep(1500);
-        } catch (InterruptedException e) {
-        }
 
-        final PaymentAttemptModelDao attempt3 = paymentDao.getPaymentAttempt(refundAttempt.getId(), internalCallContext);
-        assertEquals(attempt3.getStateName(), "SUCCESS");
+        await().atMost(TIMEOUT, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                final PaymentAttemptModelDao attempt3 = paymentDao.getPaymentAttempt(refundAttempt.getId(), internalCallContext);
+                return "SUCCESS".equals(attempt3.getStateName());
+            }
+        });
     }
 
     @Test(groups = "slow")
@@ -418,7 +421,7 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
         Assert.assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
     }
 
-    // The test will check that when a PENDING entry stays PENDING, we go through all our retries and evebtually give up (no infinite loop of retries)
+    // The test will check that when a PENDING entry stays PENDING, we go through all our retries and eventually give up (no infinite loop of retries)
     @Test(groups = "slow")
     public void testPendingEntriesThatDontMove() throws Exception {
 
@@ -450,23 +453,24 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
         testListener.assertListenerStatus();
 
         // 15s,1m,3m,1h,1d,1d,1d,1d,1d
-        for (TimeSpan cur : paymentConfig.getIncompleteTransactionsRetries()) {
+        for (final TimeSpan cur : paymentConfig.getIncompleteTransactionsRetries()) {
             // Verify there is a notification to retry updating the value
             assertEquals(getPendingNotificationCnt(internalCallContext), 1);
 
             clock.addDeltaFromReality(cur.getMillis() + 1);
 
-            // We add a sleep here to make sure the notification gets processed. Note that calling assertNotificationsCompleted would not work
-            // because there is a point in time where the notification  queue is empty (showing notification was processed), but the processing of the notification
+            assertNotificationsCompleted(internalCallContext, 5);
+            // We add a sleep here to make sure the notification gets processed. Note that calling assertNotificationsCompleted alone would not work
+            // because there is a point in time where the notification queue is empty (showing notification was processed), but the processing of the notification
             // will itself enter a new notification, and so the synchronization is difficult without writing *too much code*.
             Thread.sleep(1000);
+            assertNotificationsCompleted(internalCallContext, 5);
 
-            //assertNotificationsCompleted(internalCallContext, 5);
             final Payment updatedPayment = paymentApi.getPayment(payment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
             Assert.assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
         }
 
-        await().atMost(5, TimeUnit.SECONDS).until(new Callable<Boolean>() {
+        await().atMost(TIMEOUT, TimeUnit.SECONDS).until(new Callable<Boolean>() {
             @Override
             public Boolean call() throws Exception {
                 return getPendingNotificationCnt(internalCallContext) == 0;
@@ -515,8 +519,12 @@ public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
             await().atMost(timeoutSec, SECONDS).until(new Callable<Boolean>() {
                 @Override
                 public Boolean call() throws Exception {
-                    final List<NotificationEventWithMetadata<NotificationEvent>> notifications = notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId());
-                    return notifications.isEmpty();
+                    for (final NotificationEventWithMetadata<NotificationEvent> notificationEvent : notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, Janitor.QUEUE_NAME).getFutureOrInProcessingNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId())) {
+                        if (!notificationEvent.getEffectiveDate().isAfter(clock.getUTCNow())) {
+                            return false;
+                        }
+                    }
+                    return true;
                 }
             });
         } catch (final Exception e) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
index cbc23e5..f289f57 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
@@ -58,6 +58,8 @@ import static org.testng.Assert.fail;
 
 public class TestRetryService extends PaymentTestSuiteNoDB {
 
+    private static final int TIMEOUT = 10;
+
     private MockPaymentProviderPlugin mockPaymentProviderPlugin;
 
     @Override
@@ -174,7 +176,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
         moveClockForFailureType(FailureType.PAYMENT_FAILURE, 0);
 
         try {
-            await().atMost(5, SECONDS).until(new Callable<Boolean>() {
+            await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {
                 @Override
                 public Boolean call() throws Exception {
                     final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
@@ -260,7 +262,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
             final int curFailureCondition = curFailure;
 
             try {
-                await().atMost(5, SECONDS).until(new Callable<Boolean>() {
+                await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {
                     @Override
                     public Boolean call() throws Exception {
                         final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
@@ -345,7 +347,7 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
             final int curFailureCondition = curFailure;
 
             try {
-                await().atMost(5, SECONDS).until(new Callable<Boolean>() {
+                await().atMost(TIMEOUT, SECONDS).until(new Callable<Boolean>() {
                     @Override
                     public Boolean call() throws Exception {
                         final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 4d4317a..f79d4d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.80</version>
+        <version>0.81</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.16.2-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
index 3686716..9e3624c 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
@@ -40,7 +40,7 @@ import static org.testng.Assert.assertNotNull;
 
 public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedDB {
 
-    protected final int DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC = 5;
+    protected final int DEFAULT_WAIT_COMPLETION_TIMEOUT_SEC = 10;
 
     protected static final String PLUGIN_NAME = "noop";
 
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
index 09d899a..93b7374 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
@@ -156,7 +156,7 @@ public class TestPayment extends TestJaxrsBase {
         refundTransaction.setTransactionExternalKey(refundTransactionExternalKey);
         refundTransaction.setAmount(purchaseAmount);
         refundTransaction.setCurrency(authPayment.getCurrency());
-        final Payment refundPayment = killBillClient.refundPayment(refundTransaction, pluginProperties, createdBy, reason, comment);
+        final Payment refundPayment = killBillClient.refundPayment(refundTransaction, null, pluginProperties, createdBy, reason, comment);
         verifyPaymentWithPendingRefund(account, paymentMethodId, paymentExternalKey, purchaseTransactionExternalKey, purchaseAmount, refundTransactionExternalKey, refundPayment);
 
         // We cannot complete using just the payment id as JAX-RS doesn't know which transaction to complete
@@ -221,7 +221,7 @@ public class TestPayment extends TestJaxrsBase {
 
         // Void payment using externalKey
         final String voidTransactionExternalKey = UUID.randomUUID().toString();
-        final Payment voidPayment = killBillClient.voidPayment(null, paymentExternalKey, voidTransactionExternalKey, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+        final Payment voidPayment = killBillClient.voidPayment(null, paymentExternalKey, voidTransactionExternalKey, null, ImmutableMap.<String, String>of(), createdBy, reason, comment);
         verifyPaymentTransaction(accountJson, voidPayment.getPaymentId(), paymentExternalKey, voidPayment.getTransactions().get(1),
                                  voidTransactionExternalKey, null, "VOID", "SUCCESS");
     }
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
index 9184c07..390e4ed 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPushNotification.java
@@ -71,7 +71,7 @@ public class TestPushNotification extends TestJaxrsBase {
     }
 
     private boolean waitForCallbacksToComplete() throws InterruptedException {
-        long remainingMs = 20000;
+        long remainingMs = 30000;
         do {
             if (callbackCompleted) {
                 break;
diff --git a/util/src/test/java/org/killbill/billing/util/callcontext/TestCallContext.java b/util/src/test/java/org/killbill/billing/util/callcontext/TestCallContext.java
index 5798beb..43a57cf 100644
--- a/util/src/test/java/org/killbill/billing/util/callcontext/TestCallContext.java
+++ b/util/src/test/java/org/killbill/billing/util/callcontext/TestCallContext.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -34,6 +36,14 @@ public class TestCallContext implements CallContext {
         this(userName, new DefaultClock().getUTCNow(), new DefaultClock().getUTCNow());
     }
 
+    public TestCallContext(final CallContext context, final DateTime utcNow) {
+        this.userName = context.getUserName();
+        this.createdDate = utcNow;
+        this.updatedDate = utcNow;
+        this.userToken = context.getUserToken();
+        this.tenantId = context.getTenantId();
+    }
+
     public TestCallContext(final String userName, final DateTime createdDate, final DateTime updatedDate) {
         this.userName = userName;
         this.createdDate = createdDate;