killbill-aplcache

Merge remote-tracking branch 'origin/fix-for-477' into

5/23/2016 6:51:52 PM

Changes

NEWS 3(+3 -0)

Details

diff --git a/api/src/main/java/org/killbill/billing/callcontext/InternalCallContext.java b/api/src/main/java/org/killbill/billing/callcontext/InternalCallContext.java
index bc78b08..7c9b1cd 100644
--- a/api/src/main/java/org/killbill/billing/callcontext/InternalCallContext.java
+++ b/api/src/main/java/org/killbill/billing/callcontext/InternalCallContext.java
@@ -97,6 +97,11 @@ public class InternalCallContext extends InternalTenantContext {
              utcNow);
     }
 
+    public InternalCallContext(final InternalCallContext context, final DateTime updatedDate) {
+        this(context.getTenantRecordId(), context.getAccountRecordId(), context.getFixedOffsetTimeZone(), context.getReferenceDateTime(), context.getUserToken(), context.getCreatedBy(), context.getCallOrigin(),
+             context.getContextUserType(), context.getReasonCode(), context.getComments(), context.getCreatedDate(), updatedDate);
+    }
+
     // TODO should not be needed if all services are using internal API
     // Unfortunately not true as some APIs ae hidden in object -- e.g OverdueStateApplicator is doing subscription.cancelEntitlementWithDateOverrideBillingPolicy
     public CallContext toCallContext(final UUID tenantId) {
diff --git a/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java b/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
index 89f710c..2d23d02 100644
--- a/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
+++ b/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
@@ -28,10 +28,12 @@ import org.killbill.clock.ClockUtil;
 public class TimeAwareContext {
 
     private final DateTimeZone fixedOffsetTimeZone;
+    private final DateTime referenceDateTime;
     private final LocalTime referenceTime;
 
     public TimeAwareContext(@Nullable final DateTimeZone fixedOffsetTimeZone, @Nullable final DateTime referenceDateTime) {
         this.fixedOffsetTimeZone = fixedOffsetTimeZone;
+        this.referenceDateTime = referenceDateTime;
         this.referenceTime = computeReferenceTime(referenceDateTime);
     }
 
@@ -53,6 +55,10 @@ public class TimeAwareContext {
         }
     }
 
+    DateTime getReferenceDateTime() {
+        return referenceDateTime;
+    }
+
     // For convenience (used in tests)
 
     //@VisibleForTesting
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 bb740e3..836d7d6 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
@@ -226,7 +226,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
 
         // Trigger chargeback
         payment1 = createChargeBackAndCheckForCompletion(account, payment1, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
-        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(BigDecimal.ZERO), 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);
@@ -321,7 +321,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
 
         // 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.getPurchasedAmount().compareTo(new BigDecimal("24.51")), 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);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
index 35cd19f..95a7a37 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
@@ -61,7 +61,7 @@ public abstract class ExceptionMapperBase {
     }
 
     private Response doFallback(final Exception exception, final UriInfo uriInfo) {
-        if (exception.getCause() == null || !(exception.getCause() instanceof BillingExceptionBase)) {
+        if (!(exception.getCause() instanceof BillingExceptionBase)) {
             return buildBadRequestResponse(exception, uriInfo);
         }
 
@@ -145,12 +145,13 @@ public abstract class ExceptionMapperBase {
     }
 
     protected Response buildPluginTimeoutResponse(final Exception e, final UriInfo uriInfo) {
-        final Response.ResponseBuilder responseBuilder = Response.status(Status.ACCEPTED);
+        // 504 - Gateway Timeout
+        final Response.ResponseBuilder responseBuilder = Response.status(504);
         serializeException(e, uriInfo, responseBuilder);
         return new LoggingResponse(e, responseBuilder.build());
     }
 
-    private void serializeException(final Exception e, final UriInfo uriInfo, final Response.ResponseBuilder responseBuilder) {
+    protected void serializeException(final Exception e, final UriInfo uriInfo, final Response.ResponseBuilder responseBuilder) {
         final boolean withStackTrace = uriInfo.getQueryParameters() != null && "true".equals(uriInfo.getQueryParameters().getFirst(QUERY_WITH_STACK_TRACE));
         final BillingExceptionJson billingExceptionJson = new BillingExceptionJson(e, withStackTrace);
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
index efa1828..799e95f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
@@ -19,6 +19,7 @@ package org.killbill.billing.jaxrs.mappers;
 import javax.inject.Singleton;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.ExceptionMapper;
 import javax.ws.rs.ext.Provider;
@@ -64,14 +65,24 @@ public class PaymentApiExceptionMapper extends ExceptionMapperBase implements Ex
             return buildBadRequestResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_PLUGIN_TIMEOUT.getCode()) {
             return buildPluginTimeoutResponse(exception, uriInfo);
+        } else if (exception.getCode() == ErrorCode.PAYMENT_PLUGIN_GET_PAYMENT_INFO.getCode()) {
+            return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_UPD_PAYMENT_METHOD.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_INVALID_PARAMETER.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
+        } else if (exception.getCode() == ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode()) {
+            return buildPaymentAbortedResponse(exception, uriInfo);
         } else {
             return fallback(exception, uriInfo);
         }
     }
+
+    private Response buildPaymentAbortedResponse(final PaymentApiException exception, final UriInfo uriInfo) {
+        final Response.ResponseBuilder responseBuilder = Response.status(422);
+        serializeException(exception, uriInfo, responseBuilder);
+        return new LoggingResponse(exception, responseBuilder.build());
+    }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index a2f5910..290c0dc 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -864,8 +864,14 @@ public class AccountResource extends JaxRsResourceBase {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Trigger a payment using the account external key (authorization, purchase or credit)")
-    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account external key supplied"),
-                           @ApiResponse(code = 404, message = "Account not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 400, message = "Invalid account external key supplied"),
+                           @ApiResponse(code = 404, message = "Account not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response processPaymentByExternalKey(@MetricTag(tag = "type", property = "transactionType") final PaymentTransactionJson json,
                                                 @QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
                                                 @QueryParam(QUERY_PAYMENT_METHOD_ID) final String paymentMethodIdStr,
@@ -888,8 +894,14 @@ public class AccountResource extends JaxRsResourceBase {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Trigger a payment (authorization, purchase or credit)")
-    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account id supplied"),
-                           @ApiResponse(code = 404, message = "Account not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 400, message = "Invalid account id supplied"),
+                           @ApiResponse(code = 404, message = "Account not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response processPayment(@MetricTag(tag = "type", property = "transactionType") final PaymentTransactionJson json,
                                    @PathParam(QUERY_ACCOUNT_ID) final String accountIdStr,
                                    @QueryParam(QUERY_PAYMENT_METHOD_ID) final String paymentMethodIdStr,
@@ -963,11 +975,7 @@ public class AccountResource extends JaxRsResourceBase {
             default:
                 return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " is not allowed for an account").build();
         }
-        // Aborted payment?
-        if (result == null) {
-            return Response.noContent().build();
-        }
-        return uriBuilder.buildResponse(PaymentResource.class, "getPayment", result.getId(), uriInfo.getBaseUri().toString());
+        return createPaymentResponse(uriInfo, result, transactionType, json.getTransactionExternalKey());
     }
 
     /*
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 5699087..0f4de38 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -25,6 +25,7 @@ import java.net.URI;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -35,12 +36,13 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.StreamingOutput;
 import javax.ws.rs.core.UriInfo;
 
-import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.joda.time.format.DateTimeFormat;
 import org.joda.time.format.DateTimeFormatter;
@@ -57,6 +59,8 @@ import org.killbill.billing.entitlement.api.SubscriptionApi;
 import org.killbill.billing.entitlement.api.SubscriptionApiException;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.killbill.billing.jaxrs.json.BillingExceptionJson;
+import org.killbill.billing.jaxrs.json.BillingExceptionJson.StackTraceElementJson;
 import org.killbill.billing.jaxrs.json.BlockingStateJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.JsonBase;
@@ -97,6 +101,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.MoreObjects;
@@ -106,6 +111,7 @@ import com.google.common.base.Strings;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 
 public abstract class JaxRsResourceBase implements JaxrsResource {
 
@@ -539,9 +545,89 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         Preconditions.checkArgument(actual == expected, errorMessage);
     }
 
-    protected void logDeprecationParameterWarningIfNeeded(@Nullable final String deprecatedParam, final String...replacementParams) {
+    protected void logDeprecationParameterWarningIfNeeded(@Nullable final String deprecatedParam, final String... replacementParams) {
         if (deprecatedParam != null) {
             log.warn(String.format("Parameter %s is being deprecated: Instead use parameters %s", deprecatedParam, Joiner.on(",").join(replacementParams)));
         }
     }
+
+    protected Response createPaymentResponse(final UriInfo uriInfo, final Payment payment, final TransactionType transactionType, @Nullable final String transactionExternalKey) {
+        final PaymentTransaction createdTransaction = findCreatedTransaction(payment, transactionType, transactionExternalKey);
+        Preconditions.checkNotNull(createdTransaction, "No transaction of type '%s' found", transactionType);
+
+        final ResponseBuilder responseBuilder;
+        final BillingExceptionJson exception;
+        switch (createdTransaction.getTransactionStatus()) {
+            case PENDING:
+            case SUCCESS:
+                return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+            case PAYMENT_FAILURE:
+                // 402 - Payment Required
+                responseBuilder = Response.status(402);
+                exception = createBillingException(String.format("Payment decline by gateway. Error message: %s", createdTransaction.getGatewayErrorMsg()));
+                break;
+            case PAYMENT_SYSTEM_OFF:
+                // 503 - Service Unavailable
+                responseBuilder = Response.status(Status.SERVICE_UNAVAILABLE);
+                exception = createBillingException("Payment system is off.");
+                break;
+            case UNKNOWN:
+                // 503 - Service Unavailable
+                responseBuilder = Response.status(Status.SERVICE_UNAVAILABLE);
+                exception = createBillingException("Payment in unknown status, failed to receive gateway response.");
+                break;
+            case PLUGIN_FAILURE:
+                // 502 - Bad Gateway
+                responseBuilder = Response.status(502);
+                exception = createBillingException("Failed to submit payment transaction");
+                break;
+            default:
+                // Should never happen
+                responseBuilder = Response.serverError();
+                exception = createBillingException("This should never have happened!!!");
+        }
+        addExceptionToResponse(responseBuilder, exception);
+        return responseBuilder.location(getPaymentLocation(uriInfo, payment)).build();
+    }
+
+    private void addExceptionToResponse(final ResponseBuilder responseBuilder, final BillingExceptionJson exception) {
+        try {
+            responseBuilder.entity(mapper.writeValueAsString(exception)).type(MediaType.APPLICATION_JSON);
+        } catch (JsonProcessingException e) {
+            log.warn("Unable to serialize exception", exception);
+            responseBuilder.entity(e.toString()).type(MediaType.TEXT_PLAIN_TYPE);
+        }
+    }
+
+    private BillingExceptionJson createBillingException(final String message) {
+        final BillingExceptionJson exception;
+        exception = new BillingExceptionJson(PaymentApiException.class.getName(), null, message, null, null, Collections.<StackTraceElementJson>emptyList());
+        return exception;
+    }
+
+    private URI getPaymentLocation(final UriInfo uriInfo, final Payment payment) {
+        return uriBuilder.buildLocation(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+    }
+
+    private PaymentTransaction findCreatedTransaction(final Payment payment, final TransactionType transactionType, @Nullable final String transactionExternalKey) {
+        // Make sure we start looking from the latest transaction created
+        final List<PaymentTransaction> reversedTransactions = Lists.reverse(payment.getTransactions());
+        final Iterable<PaymentTransaction> matchingTransactions = Iterables.filter(reversedTransactions, new Predicate<PaymentTransaction>() {
+            @Override
+            public boolean apply(final PaymentTransaction input) {
+                return input.getTransactionType() == transactionType;
+            }
+        });
+
+        if (transactionExternalKey != null) {
+            for (final PaymentTransaction transaction : matchingTransactions) {
+                if (transactionExternalKey.equals(transaction.getExternalKey())) {
+                    return transaction;
+                }
+            }
+        }
+
+        // If nothing is found, return the latest transaction of given type
+        return Iterables.getFirst(matchingTransactions, null);
+    }
 }
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 0978be1..5a424b3 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
@@ -232,8 +232,14 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Complete an existing transaction")
-    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid paymentId supplied"),
-                           @ApiResponse(code = 404, message = "Account or payment not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 400, message = "Invalid paymentId supplied"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response completeTransaction(@MetricTag(tag = "type", property = "transactionType") final PaymentTransactionJson json,
                                         @PathParam("paymentId") final String paymentIdStr,
                                         @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
@@ -251,7 +257,13 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Complete an existing transaction")
-    @ApiResponses(value = {@ApiResponse(code = 404, message = "Account or payment not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response completeTransactionByExternalKey(@MetricTag(tag = "type", property = "transactionType") final PaymentTransactionJson json,
                                                      @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                                      @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@@ -291,35 +303,36 @@ public class PaymentResource extends ComboPaymentResource {
                                                                                    json != null ? json.getTransactionExternalKey() : null,
                                                                                    json != null ? json.getTransactionType() : null);
 
-            final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
-            switch (pendingTransaction.getTransactionType()) {
+        final PaymentOptions paymentOptions = createControlPluginApiPaymentOptions(paymentControlPluginNames);
+        final Payment result;
+        switch (pendingTransaction.getTransactionType()) {
             case AUTHORIZE:
-                paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
+                result = paymentApi.createAuthorizationWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
                                                                  initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
                                                                  pluginProperties, paymentOptions, callContext);
                 break;
             case CAPTURE:
-                paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, pendingTransaction.getExternalKey(),
+                result = paymentApi.createCaptureWithPaymentControl(account, initialPayment.getId(), amount, currency, pendingTransaction.getExternalKey(),
                                                            pluginProperties, paymentOptions, callContext);
                 break;
             case PURCHASE:
-                paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
+                result = paymentApi.createPurchaseWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
                                                             initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
                                                             pluginProperties, paymentOptions, callContext);
                 break;
             case CREDIT:
-                paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
+                result = paymentApi.createCreditWithPaymentControl(account, initialPayment.getPaymentMethodId(), initialPayment.getId(), amount, currency,
                                                           initialPayment.getExternalKey(), pendingTransaction.getExternalKey(),
                                                           pluginProperties, paymentOptions, callContext);
                 break;
             case REFUND:
-                paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency,
+                result = paymentApi.createRefundWithPaymentControl(account, initialPayment.getId(), amount, currency,
                                                           pendingTransaction.getExternalKey(), pluginProperties, paymentOptions, callContext);
                 break;
             default:
                 return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + pendingTransaction.getTransactionType() + " cannot be completed").build();
         }
-        return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", initialPayment.getId());
+        return createPaymentResponse(uriInfo, result, pendingTransaction.getTransactionType(), pendingTransaction.getExternalKey());
     }
 
     @TimedResource(name = "captureAuthorization")
@@ -328,8 +341,14 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Capture an existing authorization")
-    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid paymentId supplied"),
-                           @ApiResponse(code = 404, message = "Account or payment not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 400, message = "Invalid paymentId supplied"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response captureAuthorization(final PaymentTransactionJson json,
                                          @PathParam("paymentId") final String paymentIdStr,
                                          @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
@@ -347,7 +366,13 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Capture an existing authorization")
-    @ApiResponses(value = {@ApiResponse(code = 404, message = "Account or payment not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response captureAuthorizationByExternalKey(final PaymentTransactionJson json,
                                                       @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                                       @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@@ -383,7 +408,7 @@ public class PaymentResource extends ComboPaymentResource {
 
         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());
+        return createPaymentResponse(uriInfo, payment, TransactionType.CAPTURE, json.getTransactionExternalKey());
     }
 
     @TimedResource(name = "refundPayment")
@@ -392,8 +417,14 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Refund an existing payment")
-    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid paymentId supplied"),
-                           @ApiResponse(code = 404, message = "Account or payment not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 400, message = "Invalid paymentId supplied"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response refundPayment(final PaymentTransactionJson json,
                                   @PathParam("paymentId") final String paymentIdStr,
                                   @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
@@ -412,7 +443,13 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Refund an existing payment")
-    @ApiResponses(value = {@ApiResponse(code = 404, message = "Account or payment not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response refundPaymentByExternalKey(final PaymentTransactionJson json,
                                                @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                                @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@@ -449,8 +486,8 @@ public class PaymentResource extends ComboPaymentResource {
 
         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());
 
+        return createPaymentResponse(uriInfo, payment, TransactionType.REFUND, json.getTransactionExternalKey());
     }
 
     @TimedResource(name = "voidPayment")
@@ -459,8 +496,14 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Void an existing payment")
-    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid paymentId supplied"),
-                           @ApiResponse(code = 404, message = "Account or payment not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 400, message = "Invalid paymentId supplied"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response voidPayment(final PaymentTransactionJson json,
                                 @PathParam("paymentId") final String paymentIdStr,
                                 @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
@@ -478,7 +521,13 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Void an existing payment")
-    @ApiResponses(value = {@ApiResponse(code = 404, message = "Account or payment not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response voidPaymentByExternalKey(final PaymentTransactionJson json,
                                              @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                              @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@@ -510,7 +559,7 @@ public class PaymentResource extends ComboPaymentResource {
 
         final Payment payment = paymentApi.createVoidWithPaymentControl(account, initialPayment.getId(), transactionExternalKey,
                                                                         pluginProperties, paymentOptions, callContext);
-        return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+        return createPaymentResponse(uriInfo, payment, TransactionType.VOID, json.getTransactionExternalKey());
     }
 
     @TimedResource(name = "chargebackPayment")
@@ -519,8 +568,14 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Record a chargeback")
-    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid paymentId supplied"),
-                           @ApiResponse(code = 404, message = "Account not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 400, message = "Invalid paymentId supplied"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response chargebackPayment(final PaymentTransactionJson json,
                                       @PathParam("paymentId") final String paymentIdStr,
                                       @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
@@ -539,7 +594,13 @@ public class PaymentResource extends ComboPaymentResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Record a chargeback")
-    @ApiResponses(value = {@ApiResponse(code = 404, message = "Account not found")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 404, message = "Account or payment not found"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response chargebackPaymentByExternalKey(final PaymentTransactionJson json,
                                                    @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                                    @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
@@ -574,7 +635,7 @@ public class PaymentResource extends ComboPaymentResource {
 
         final Payment payment = paymentApi.createChargebackWithPaymentControl(account, initialPayment.getId(), json.getAmount(), currency,
                                                             json.getTransactionExternalKey(), paymentOptions, callContext);
-        return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
+        return createPaymentResponse(uriInfo, payment, TransactionType.CHARGEBACK, json.getTransactionExternalKey());
     }
 
     @TimedResource
@@ -583,7 +644,13 @@ public class PaymentResource extends ComboPaymentResource {
     @Produces(APPLICATION_JSON)
     @Path("/" + COMBO)
     @ApiOperation(value = "Combo api to create a new payment transaction on a existing (or not) account ")
-    @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid data for Account or PaymentMethod")})
+    @ApiResponses(value = {@ApiResponse(code = 201, message = "Payment transaction created successfully"),
+                           @ApiResponse(code = 400, message = "Invalid data for Account or PaymentMethod"),
+                           @ApiResponse(code = 402, message = "Transaction declined by gateway"),
+                           @ApiResponse(code = 422, message = "Payment is aborted by a control plugin"),
+                           @ApiResponse(code = 502, message = "Failed to submit payment transaction"),
+                           @ApiResponse(code = 503, message = "Payment in unknown status, failed to receive gateway response"),
+                           @ApiResponse(code = 504, message = "Payment operation timeout")})
     public Response createComboPayment(@MetricTag(tag = "type", property = "transactionType") final ComboPaymentTransactionJson json,
                                        @QueryParam(QUERY_PAYMENT_CONTROL_PLUGIN_NAME) final List<String> paymentControlPluginNames,
                                        @HeaderParam(HDR_CREATED_BY) final String createdBy,
@@ -628,7 +695,7 @@ public class PaymentResource extends ComboPaymentResource {
             default:
                 return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " is not allowed for an account").build();
         }
-        return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", result.getId());
+        return createPaymentResponse(uriInfo, result, transactionType, paymentTransactionJson.getTransactionExternalKey());
     }
 
     @Override
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
index ee1d165..a40529f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -31,12 +31,16 @@ import org.killbill.billing.jaxrs.resources.JaxrsResource;
 public class JaxrsUriBuilder {
 
     public Response buildResponse(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass, final String getMethodName, final Object objectId) {
+        final URI location = buildLocation(uriInfo, theClass, getMethodName, objectId);
+        return Response.created(location).build();
+    }
+
+    public URI buildLocation(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass, final String getMethodName, final Object objectId) {
         final UriBuilder uriBuilder = getUriBuilder(theClass, getMethodName).scheme(uriInfo.getAbsolutePath().getScheme())
                                                                             .host(uriInfo.getAbsolutePath().getHost())
                                                                             .port(uriInfo.getAbsolutePath().getPort());
 
-        final URI location = objectId != null ? uriBuilder.build(objectId) : uriBuilder.build();
-        return Response.created(location).build();
+        return objectId != null ? uriBuilder.build(objectId) : uriBuilder.build();
     }
 
     public URI nextPage(final Class<? extends JaxrsResource> theClass, final String getMethodName, final Long nextOffset, final Long limit, final Map<String, String> params) {

NEWS 3(+3 -0)

diff --git a/NEWS b/NEWS
index ad12397..ad512cc 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+0.16.5
+    See https://github.com/killbill/killbill/releases/tag/killbill-0.16.5
+
 0.16.4
     See https://github.com/killbill/killbill/releases/tag/killbill-0.16.4
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
index be60575..54bfd9d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
@@ -17,33 +17,21 @@
 
 package org.killbill.billing.payment.api;
 
-import java.math.BigDecimal;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.UUID;
-
-import javax.annotation.Nullable;
 
 import org.killbill.billing.ErrorCode;
-import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.config.definition.PaymentConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 
 public class DefaultApiBase {
 
-    private static final Logger log = LoggerFactory.getLogger(DefaultApiBase.class);
-    private static final Joiner JOINER = Joiner.on(",");
-
-    protected final PaymentConfig paymentConfig;
+    private final PaymentConfig paymentConfig;
     protected final InternalCallContextFactory internalCallContextFactory;
 
     public DefaultApiBase(final PaymentConfig paymentConfig, final InternalCallContextFactory internalCallContextFactory) {
@@ -51,75 +39,7 @@ public class DefaultApiBase {
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
-    protected void logAPICall(final String transactionType,
-                              final Account account,
-                              final UUID paymentMethodId,
-                              @Nullable final UUID paymentId,
-                              @Nullable final UUID transactionId,
-                              @Nullable final BigDecimal amount,
-                              @Nullable final Currency currency,
-                              @Nullable final String paymentExternalKey,
-                              @Nullable final String paymentTransactionExternalKey,
-                              @Nullable final TransactionStatus transactionStatus,
-                              @Nullable final List<String> paymentControlPluginNames) {
-        if (log.isInfoEnabled()) {
-            final StringBuilder logLine = new StringBuilder();
-            logLine.append("PaymentApi: transactionType='")
-                   .append(transactionType)
-                   .append("', accountId='")
-                   .append(account.getId())
-                   .append("'");
-            if (paymentMethodId != null) {
-                logLine.append(", paymentMethodId='")
-                       .append(paymentMethodId)
-                       .append("'");
-            }
-            if (paymentExternalKey != null) {
-                logLine.append(", paymentExternalKey='")
-                       .append(paymentExternalKey)
-                       .append("'");
-            }
-            if (paymentTransactionExternalKey != null) {
-                logLine.append(", paymentTransactionExternalKey='")
-                       .append(paymentTransactionExternalKey)
-                       .append("'");
-            }
-            if (paymentId != null) {
-                logLine.append(", paymentId='")
-                       .append(paymentId)
-                       .append("'");
-            }
-            if (transactionId != null) {
-                logLine.append(", transactionId='")
-                       .append(transactionId)
-                       .append("'");
-            }
-            if (amount != null) {
-                logLine.append(", amount='")
-                       .append(amount)
-                       .append("'");
-            }
-            if (currency != null) {
-                logLine.append(", currency='")
-                       .append(currency)
-                       .append("'");
-            }
-            if (transactionStatus != null) {
-                logLine.append(", transactionStatus='")
-                       .append(transactionStatus)
-                       .append("'");
-            }
-            if (paymentControlPluginNames != null) {
-                logLine.append(", paymentControlPluginNames='")
-                       .append(JOINER.join(paymentControlPluginNames))
-                       .append("'");
-            }
-            log.info(logLine.toString());
-        }
-    }
-
     protected List<String> toPaymentControlPluginNames(final PaymentOptions paymentOptions, final CallContext callContext) {
-
         final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContextWithoutAccountRecordId(callContext);
 
         // Special path for JAX-RS InvoicePayment endpoints (see JaxRsResourceBase)
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 1e7962a..33b8730 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
@@ -19,6 +19,7 @@ package org.killbill.billing.payment.api;
 
 import java.math.BigDecimal;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
@@ -79,9 +80,11 @@ public class DefaultPayment extends EntityBase implements Payment {
             }
         }
 
+        final BigDecimal chargebackAmount = getChargebackAmount(transactions);
+
         this.authAmount = getAmountForType(nonVoidedTransactions, TransactionType.AUTHORIZE);
-        this.captureAmount = getAmountForType(nonVoidedTransactions, TransactionType.CAPTURE);
-        this.purchasedAmount = getAmountForType(nonVoidedTransactions, TransactionType.PURCHASE);
+        this.captureAmount = getAmountForType(nonVoidedTransactions, TransactionType.CAPTURE).add(chargebackAmount.negate()).max(BigDecimal.ZERO);
+        this.purchasedAmount = getAmountForType(nonVoidedTransactions, TransactionType.PURCHASE).add(chargebackAmount.negate()).max(BigDecimal.ZERO);
         this.creditAmount = getAmountForType(nonVoidedTransactions, TransactionType.CREDIT);
         this.refundAmount = getAmountForType(nonVoidedTransactions, TransactionType.REFUND);
 
@@ -96,6 +99,27 @@ public class DefaultPayment extends EntityBase implements Payment {
         this.currency = !transactions.isEmpty() ? transactions.get(0).getCurrency() : null;
     }
 
+    private static BigDecimal getChargebackAmount(final Iterable<PaymentTransaction> transactions) {
+        final Collection<String> successfulChargebackExternalKeys = new HashSet<String>();
+
+        for (final PaymentTransaction transaction : transactions) {
+            // We are looking for the last chargeback in state SUCCESS for a given external key
+            if (TransactionType.CHARGEBACK.equals(transaction.getTransactionType()) && TransactionStatus.SUCCESS.equals(transaction.getTransactionStatus())) {
+                successfulChargebackExternalKeys.add(transaction.getExternalKey());
+            } else if (TransactionType.CHARGEBACK.equals(transaction.getTransactionType()) && TransactionStatus.PAYMENT_FAILURE.equals(transaction.getTransactionStatus())) {
+                successfulChargebackExternalKeys.remove(transaction.getExternalKey());
+            }
+        }
+
+        return getAmountForType(Iterables.<PaymentTransaction>filter(transactions, new Predicate<PaymentTransaction>() {
+                                    @Override
+                                    public boolean apply(final PaymentTransaction input) {
+                                        return successfulChargebackExternalKeys.contains(input.getExternalKey());
+                                    }
+                                }),
+                                TransactionType.CHARGEBACK);
+    }
+
     private static BigDecimal getAmountForType(final Iterable<PaymentTransaction> transactions, final TransactionType transactiontype) {
         BigDecimal result = BigDecimal.ZERO;
         BigDecimal processedResult = BigDecimal.ZERO;
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
index a28b6e6..d176726 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.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
@@ -42,11 +42,15 @@ import org.killbill.billing.util.entity.Pagination;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 
 public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
 
+    private static final Joiner JOINER = Joiner.on(",");
+
     private static final boolean SHOULD_LOCK_ACCOUNT = true;
     private static final boolean IS_API_PAYMENT = true;
     private static final UUID NULL_ATTEMPT_ID = null;
@@ -77,26 +81,29 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.AUTHORIZE.name();
-        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = paymentProcessor.createAuthorization(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                                     SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   null);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = paymentProcessor.createAuthorization(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                         SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           null);
+        }
     }
 
     @Override
@@ -117,26 +124,30 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.AUTHORIZE.name();
-        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = pluginControlPaymentProcessor.createAuthorization(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                                                  properties, paymentControlPluginNames, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   paymentControlPluginNames);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = pluginControlPaymentProcessor.createAuthorization(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                                      properties, paymentControlPluginNames, callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           paymentControlPluginNames);
+        }
     }
 
     @Override
@@ -150,26 +161,30 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.CAPTURE.name();
-        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = paymentProcessor.createCapture(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
-                                                               SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   null);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = paymentProcessor.createCapture(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                                                   SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           null);
+        }
     }
 
     @Override
@@ -187,26 +202,30 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.CAPTURE.name();
-        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = pluginControlPaymentProcessor.createCapture(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
-                                                                            properties, paymentControlPluginNames, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   paymentControlPluginNames);
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = pluginControlPaymentProcessor.createCapture(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                                                                properties, paymentControlPluginNames, callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           paymentControlPluginNames);
+        }
 
-        return payment;
     }
 
     @Override
@@ -221,26 +240,30 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.PURCHASE.name();
-        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = paymentProcessor.createPurchase(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                                SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   null);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = paymentProcessor.createPurchase(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                    SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           null);
+        }
     }
 
     @Override
@@ -268,25 +291,29 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
                                            paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
 
         final String transactionType = TransactionType.PURCHASE.name();
-        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
-
-        final Payment payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                                             properties, paymentControlPluginNames, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   paymentControlPluginNames);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+
+        try {
+            logEnterAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
+
+            payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                                 properties, paymentControlPluginNames, callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           paymentControlPluginNames);
+        }
     }
 
     @Override
@@ -298,26 +325,29 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.VOID.name();
-        logAPICall(transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, null);
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, null);
 
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = paymentProcessor.createVoid(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey,
-                                                            SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   null);
-
-        return payment;
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = paymentProcessor.createVoid(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey,
+                                                                SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           null);
+        }
 
     }
 
@@ -333,26 +363,29 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.VOID.name();
-        logAPICall(transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = pluginControlPaymentProcessor.createVoid(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey,
-                                                                         properties, paymentControlPluginNames, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   paymentControlPluginNames);
-
-        return payment;
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = pluginControlPaymentProcessor.createVoid(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey,
+                                                                             properties, paymentControlPluginNames, callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           paymentControlPluginNames);
+        }
     }
 
     @Override
@@ -366,26 +399,29 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.REFUND.name();
-        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = paymentProcessor.createRefund(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
-                                                              SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   null);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = paymentProcessor.createRefund(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                                                  SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           null);
+        }
     }
 
     @Override
@@ -405,26 +441,29 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.REFUND.name();
-        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = pluginControlPaymentProcessor.createRefund(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
-                                                                           properties, paymentControlPluginNames, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   paymentControlPluginNames);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = pluginControlPaymentProcessor.createRefund(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                                                               properties, paymentControlPluginNames, callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           paymentControlPluginNames);
+        }
     }
 
     @Override
@@ -440,26 +479,30 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.CREDIT.name();
-        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                              SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   null);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                  SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           null);
+        }
     }
 
     @Override
@@ -480,26 +523,29 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(properties, "plugin properties");
 
         final String transactionType = TransactionType.CREDIT.name();
-        logAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
-                                                                           properties, paymentControlPluginNames, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   paymentControlPluginNames);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                                               properties, paymentControlPluginNames, callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           paymentControlPluginNames);
+        }
     }
 
     @Override
@@ -508,31 +554,35 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentTransactionId, "paymentTransactionId");
 
         final String transactionType = "NOTIFY_STATE_CHANGE";
-        logAPICall(transactionType, account, null, null, paymentTransactionId, null, null, null, null, null, null);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = paymentProcessor.notifyPendingPaymentOfStateChanged(account, paymentTransactionId, isSuccess, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = Iterables.<PaymentTransaction>tryFind(payment.getTransactions(),
-                                                                                            new Predicate<PaymentTransaction>() {
-                                                                                                @Override
-                                                                                                public boolean apply(final PaymentTransaction transaction) {
-                                                                                                    return transaction.getId().equals(paymentTransactionId);
-                                                                                                }
-                                                                                            }).orNull();
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction == null ? null : paymentTransaction.getId(),
-                   paymentTransaction == null ? null : paymentTransaction.getProcessedAmount(),
-                   paymentTransaction == null ? null : paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction == null ? null : paymentTransaction.getExternalKey(),
-                   paymentTransaction == null ? null : paymentTransaction.getTransactionStatus(),
-                   null);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, null, paymentTransactionId, null, null, null, null, null, null);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = paymentProcessor.notifyPendingPaymentOfStateChanged(account, paymentTransactionId, isSuccess, callContext, internalCallContext);
+
+            paymentTransaction = Iterables.<PaymentTransaction>tryFind(payment.getTransactions(),
+                                                                                                new Predicate<PaymentTransaction>() {
+                                                                                                    @Override
+                                                                                                    public boolean apply(final PaymentTransaction transaction) {
+                                                                                                        return transaction.getId().equals(paymentTransactionId);
+                                                                                                    }
+                                                                                                }).orNull();
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           null);
+        }
     }
 
     @Override
@@ -548,26 +598,29 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentId, "paymentId");
 
         final String transactionType = TransactionType.CHARGEBACK.name();
-        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
-
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = paymentProcessor.createChargeback(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, amount, currency, true,
-                                                                  callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   null);
-
-        return payment;
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, null);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = paymentProcessor.createChargeback(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, amount, currency, true,
+                                                                      callContext, internalCallContext);
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           null);
+        }
     }
 
     @Override
@@ -583,26 +636,70 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         checkNotNullParameter(paymentId, "paymentId");
 
         final String transactionType = TransactionType.CHARGEBACK.name();
-        logAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, null, paymentControlPluginNames);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = pluginControlPaymentProcessor.createChargeback(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey, amount, currency,
+                                                                                   paymentControlPluginNames, callContext, internalCallContext);
+
+            paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           paymentControlPluginNames);
+        }
+    }
 
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        final Payment payment = pluginControlPaymentProcessor.createChargeback(IS_API_PAYMENT, account, paymentId, paymentTransactionExternalKey, amount, currency,
-                                                                               paymentControlPluginNames, callContext, internalCallContext);
-
-        final PaymentTransaction paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
-        logAPICall(transactionType,
-                   account,
-                   payment.getPaymentMethodId(),
-                   payment.getId(),
-                   paymentTransaction.getId(),
-                   paymentTransaction.getProcessedAmount(),
-                   paymentTransaction.getProcessedCurrency(),
-                   payment.getExternalKey(),
-                   paymentTransaction.getExternalKey(),
-                   paymentTransaction.getTransactionStatus(),
-                   paymentControlPluginNames);
+    //@Override TODO 0.17
+    public Payment createChargebackReversal(final Account account, final UUID paymentId, final String paymentTransactionExternalKey, final CallContext callContext) throws PaymentApiException {
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentId, "paymentId");
+        checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey");
 
-        return payment;
+        final String transactionType = TransactionType.CHARGEBACK.name();
+        Payment payment = null;
+        PaymentTransaction paymentTransaction = null;
+        try {
+            logEnterAPICall(transactionType, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, null, null);
+
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+            payment = paymentProcessor.createChargebackReversal(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, null, null, true, callContext, internalCallContext);
+
+            // See https://github.com/killbill/killbill/issues/552
+            paymentTransaction = Iterables.<PaymentTransaction>find(Lists.<PaymentTransaction>reverse(payment.getTransactions()),
+                                                                    new Predicate<PaymentTransaction>() {
+                                                                        @Override
+                                                                        public boolean apply(final PaymentTransaction input) {
+                                                                            return paymentTransactionExternalKey.equals(input.getExternalKey());
+                                                                        }
+                                                                    });
+
+            return payment;
+        } finally {
+            logExitAPICall(transactionType,
+                           account,
+                           payment != null ? payment.getPaymentMethodId() : null,
+                           payment != null ? payment.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getId() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null,
+                           paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null,
+                           payment != null ? payment.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getExternalKey() : null,
+                           paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null,
+                           null);
+        }
     }
 
     @Override
@@ -726,4 +823,124 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
 
         return paymentMethods;
     }
+
+    private void logEnterAPICall(final String transactionType,
+                                   final Account account,
+                                   @Nullable final UUID paymentMethodId,
+                                   @Nullable final UUID paymentId,
+                                   @Nullable final UUID transactionId,
+                                   @Nullable final BigDecimal amount,
+                                   @Nullable final Currency currency,
+                                   @Nullable final String paymentExternalKey,
+                                   @Nullable final String paymentTransactionExternalKey,
+                                   @Nullable final TransactionStatus transactionStatus,
+                                   @Nullable final List<String> paymentControlPluginNames) {
+        logAPICallInternal("ENTERING ",
+                           transactionType,
+                           account,
+                           paymentMethodId,
+                           paymentId,
+                           transactionId,
+                           amount,
+                           currency,
+                           paymentExternalKey,
+                           paymentTransactionExternalKey,
+                           transactionStatus,
+                           paymentControlPluginNames);
+    }
+
+    private void logExitAPICall(final String transactionType,
+                                  final Account account,
+                                  @Nullable final UUID paymentMethodId,
+                                  @Nullable final UUID paymentId,
+                                  @Nullable final UUID transactionId,
+                                  @Nullable final BigDecimal amount,
+                                  @Nullable final Currency currency,
+                                  @Nullable final String paymentExternalKey,
+                                  @Nullable final String paymentTransactionExternalKey,
+                                  @Nullable final TransactionStatus transactionStatus,
+                                  @Nullable final List<String> paymentControlPluginNames) {
+        logAPICallInternal("EXITING ",
+                           transactionType,
+                           account,
+                           paymentMethodId,
+                           paymentId,
+                           transactionId,
+                           amount,
+                           currency,
+                           paymentExternalKey,
+                           paymentTransactionExternalKey,
+                           transactionStatus,
+                           paymentControlPluginNames);
+    }
+
+
+
+    private void logAPICallInternal(final String prefixMsg,
+                                    final String transactionType,
+                                    final Account account,
+                                    final UUID paymentMethodId,
+                                    @Nullable final UUID paymentId,
+                                    @Nullable final UUID transactionId,
+                                    @Nullable final BigDecimal amount,
+                                    @Nullable final Currency currency,
+                                    @Nullable final String paymentExternalKey,
+                                    @Nullable final String paymentTransactionExternalKey,
+                                    @Nullable final TransactionStatus transactionStatus,
+                                    @Nullable final List<String> paymentControlPluginNames) {
+        if (log.isInfoEnabled()) {
+            final StringBuilder logLine = new StringBuilder(prefixMsg);
+            logLine.append("PaymentApi: transactionType='")
+                   .append(transactionType)
+                   .append("', accountId='")
+                   .append(account.getId())
+                   .append("'");
+            if (paymentMethodId != null) {
+                logLine.append(", paymentMethodId='")
+                       .append(paymentMethodId)
+                       .append("'");
+            }
+            if (paymentExternalKey != null) {
+                logLine.append(", paymentExternalKey='")
+                       .append(paymentExternalKey)
+                       .append("'");
+            }
+            if (paymentTransactionExternalKey != null) {
+                logLine.append(", paymentTransactionExternalKey='")
+                       .append(paymentTransactionExternalKey)
+                       .append("'");
+            }
+            if (paymentId != null) {
+                logLine.append(", paymentId='")
+                       .append(paymentId)
+                       .append("'");
+            }
+            if (transactionId != null) {
+                logLine.append(", transactionId='")
+                       .append(transactionId)
+                       .append("'");
+            }
+            if (amount != null) {
+                logLine.append(", amount='")
+                       .append(amount)
+                       .append("'");
+            }
+            if (currency != null) {
+                logLine.append(", currency='")
+                       .append(currency)
+                       .append("'");
+            }
+            if (transactionStatus != null) {
+                logLine.append(", transactionStatus='")
+                       .append(transactionStatus)
+                       .append("'");
+            }
+            if (paymentControlPluginNames != null) {
+                logLine.append(", paymentControlPluginNames='")
+                       .append(JOINER.join(paymentControlPluginNames))
+                       .append("'");
+            }
+            log.info(logLine.toString());
+        }
+    }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
index 997b015..20a0655 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
@@ -34,6 +34,7 @@ import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
 import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.PaymentGatewayProcessor;
 import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
+import org.killbill.billing.payment.core.sm.control.PaymentControlApiAbortException;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.payment.plugin.api.GatewayNotification;
@@ -145,6 +146,8 @@ public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentG
                                                                                                                        PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
                                                                                                                        null, null, true, paymentControlPluginNames, properties, callContext);
 
+                                                     } catch (final PaymentControlApiAbortException e) {
+                                                         throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_API_ABORTED, e.getPluginName());
                                                      } catch (final PaymentControlApiException e) {
                                                          throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e);
                                                      }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index 7aa74e5..e4d9edf 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.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
@@ -145,6 +145,11 @@ public class PaymentProcessor extends ProcessorBase {
         return performOperation(isApiPayment, attemptId, TransactionType.CHARGEBACK, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, shouldLockAccountAndDispatch, null, PLUGIN_PROPERTIES, callContext, internalCallContext);
     }
 
+    public Payment createChargebackReversal(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final BigDecimal amount, final Currency currency, final boolean shouldLockAccountAndDispatch,
+                                    final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return performOperation(isApiPayment, attemptId, TransactionType.CHARGEBACK, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, shouldLockAccountAndDispatch, OperationResult.FAILURE, PLUGIN_PROPERTIES, callContext, internalCallContext);
+    }
+
     public Payment notifyPendingPaymentOfStateChanged(final Account account, final UUID transactionId, final boolean isSuccess, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         final PaymentTransactionModelDao transactionModelDao = paymentDao.getPaymentTransaction(transactionId, internalCallContext);
         if (transactionModelDao.getTransactionStatus() != TransactionStatus.PENDING) {
@@ -364,7 +369,7 @@ public class PaymentProcessor extends ProcessorBase {
         try {
             return plugin.getPaymentInfo(paymentModelDao.getAccountId(), paymentModelDao.getId(), properties, context);
         } catch (final PaymentPluginApiException e) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_GET_PAYMENT_INFO, paymentModelDao.getId(), e.toString());
+            throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_GET_PAYMENT_INFO, paymentModelDao.getId(), e.toString());
         }
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
index 7f0ba27..1ee36fc 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
@@ -117,7 +117,7 @@ public class ControlPluginRunner {
                 inputPluginProperties = prevResult.getAdjustedPluginProperties();
             }
             if (prevResult.isAborted()) {
-                break;
+                throw new PaymentControlApiAbortException(pluginName);
             }
             inputPaymentControlContext = new DefaultPaymentControlContext(account,
                                                                           inputPaymentMethodId,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
index 22f53e1..4f05018 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
@@ -18,8 +18,6 @@
 package org.killbill.billing.payment.core.sm.control;
 
 import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
 
 import javax.annotation.Nullable;
 
@@ -27,6 +25,7 @@ import org.joda.time.DateTime;
 import org.killbill.automaton.Operation.OperationCallback;
 import org.killbill.automaton.OperationException;
 import org.killbill.automaton.OperationResult;
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
 import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult;
 import org.killbill.billing.control.plugin.api.PaymentApiType;
@@ -47,12 +46,10 @@ import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
-import org.killbill.commons.locker.LockFailedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Joiner;
-import com.google.common.base.MoreObjects;
 
 public abstract class OperationControlCallback extends OperationCallbackBase<Payment, PaymentApiException> implements OperationCallback {
 
@@ -103,13 +100,12 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
                                                                                                      paymentStateControlContext.isApiPayment(),
                                                                                                      paymentStateContext.getCallContext());
 
-                final PriorPaymentControlResult pluginResult;
                 try {
-                    pluginResult = executePluginPriorCalls(paymentStateControlContext.getPaymentControlPluginNames(), paymentControlContext);
-                    if (pluginResult != null && pluginResult.isAborted()) {
-                        // Transition to ABORTED
-                        return PluginDispatcher.createPluginDispatcherReturnType(OperationResult.EXCEPTION);
-                    }
+                    executePluginPriorCalls(paymentStateControlContext.getPaymentControlPluginNames(), paymentControlContext);
+                } catch (final PaymentControlApiAbortException e) {
+                    // Transition to ABORTED
+                    final PaymentApiException paymentAbortedException = new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_API_ABORTED, e.getPluginName());
+                    throw new OperationException(paymentAbortedException, OperationResult.EXCEPTION);
                 } catch (final PaymentControlApiException e) {
                     // Transition to ABORTED and throw PaymentControlApiException to caller.
                     throw new OperationException(e, OperationResult.EXCEPTION);
@@ -149,33 +145,19 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
                     throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(paymentControlContext));
                 } catch (final RuntimeException e) {
                     // Attempts to set the retry date in context if needed.
-                    executePluginOnFailureCallsAndSetRetryDate(paymentControlContext);
-                    throw new OperationException(e, OperationResult.EXCEPTION);
+                    throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(paymentControlContext));
                 }
             }
         });
     }
 
     @Override
-    protected OperationException unwrapExceptionFromDispatchedTask(final Exception e) {
-        // If this is an ExecutionException we attempt to extract the cause first
-        final Throwable originalExceptionOrCausePossiblyOperationException = e instanceof ExecutionException ? MoreObjects.firstNonNull(e.getCause(), e) : e;
-
-        // Unwrap OperationException too (doOperationCallback wraps exceptions in OperationException)
-        final Throwable originalExceptionOrCause = originalExceptionOrCausePossiblyOperationException instanceof OperationException ? MoreObjects.firstNonNull(originalExceptionOrCausePossiblyOperationException.getCause(), originalExceptionOrCausePossiblyOperationException) : originalExceptionOrCausePossiblyOperationException;
-
-        if (originalExceptionOrCause instanceof OperationException) {
-            return (OperationException) originalExceptionOrCause;
-        } else if (originalExceptionOrCause instanceof LockFailedException) {
-            logger.warn("Failed to lock accountId='{}'", paymentStateContext.getAccount().getId());
-        } else if (originalExceptionOrCause instanceof TimeoutException) {
-            logger.warn("Call TIMEOUT for accountId='{}'", paymentStateContext.getAccount().getId());
-        } else if (originalExceptionOrCause instanceof InterruptedException) {
-            logger.warn("Call was interrupted for accountId='{}'", paymentStateContext.getAccount().getId());
-        } else {
-            logger.warn("Operation failed for accountId='{}'", paymentStateContext.getAccount().getId(), e);
+    protected OperationException unwrapExceptionFromDispatchedTask(final PaymentApiException e) {
+        if (e.getCause() instanceof OperationException) {
+            return (OperationException) e.getCause();
         }
-        return new OperationException(originalExceptionOrCause, getOperationResultOnException(paymentStateContext));
+        logger.warn("Operation failed for accountId='{}' accountExternalKey='{}' error='{}'", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+        return new OperationException(e, getOperationResultOnException(paymentStateContext));
     }
 
     private OperationResult getOperationResultOnException(final PaymentStateContext paymentStateContext) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentControlApiAbortException.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentControlApiAbortException.java
new file mode 100644
index 0000000..33b4df4
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PaymentControlApiAbortException.java
@@ -0,0 +1,35 @@
+/*
+ * 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
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm.control;
+
+import org.killbill.billing.control.plugin.api.PaymentControlApiException;
+
+/**
+ * Created by arodrigues on 5/6/16.
+ */
+public class PaymentControlApiAbortException extends PaymentControlApiException {
+    private final String pluginName;
+
+    public PaymentControlApiAbortException(final String pluginName) {
+        this.pluginName = pluginName;
+    }
+
+    public String getPluginName() {
+        return pluginName;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
index 5547c22..39060ad 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
@@ -18,14 +18,14 @@
 package org.killbill.billing.payment.core.sm;
 
 import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
 
 import org.killbill.automaton.OperationException;
 import org.killbill.automaton.OperationResult;
 import org.killbill.billing.account.api.Account;
+import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.ProcessorBase.CallableWithAccountLock;
 import org.killbill.billing.payment.core.ProcessorBase.DispatcherCallback;
+import org.killbill.billing.payment.dispatcher.PaymentPluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.util.config.definition.PaymentConfig;
@@ -67,18 +67,9 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
                                                                                                                                            account.getExternalKey(),
                                                                                                                                            paymentConfig,
                                                                                                                                            callback);
-            logger.debug("Calling plugin(s) {}", pluginNames);
-            final OperationResult operationResult = paymentPluginDispatcher.dispatchWithTimeout(task);
-            logger.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, account.getExternalKey(), operationResult);
+            final OperationResult operationResult = PaymentPluginDispatcher.dispatchWithExceptionHandling(account, pluginNames, task, paymentPluginDispatcher);
             return operationResult;
-        } catch (final ExecutionException e) {
-            throw unwrapExceptionFromDispatchedTask(e);
-        } catch (final TimeoutException e) {
-            logger.warn("TimeoutException while executing the plugin(s) {}", pluginNames);
-            throw unwrapExceptionFromDispatchedTask(e);
-        } catch (final InterruptedException e) {
-            Thread.currentThread().interrupt();
-            logger.warn("InterruptedException while executing the following plugin(s): {}", pluginNames);
+        } catch (final PaymentApiException e) {
             throw unwrapExceptionFromDispatchedTask(e);
         }
     }
@@ -91,5 +82,5 @@ public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOpe
     //
     protected abstract CallbackOperationResult doCallSpecificOperationCallback() throws CallbackOperationException;
 
-    protected abstract OperationException unwrapExceptionFromDispatchedTask(final Exception e);
+    protected abstract OperationException unwrapExceptionFromDispatchedTask(final PaymentApiException e);
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
index f9742b1..99c913b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -222,8 +222,6 @@ public class PaymentAutomatonRunner {
                 throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
             } else if (e.getCause() instanceof PaymentApiException) {
                 throw (PaymentApiException) e.getCause();
-            } else if (e.getCause() instanceof TimeoutException) {
-                throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, Objects.firstNonNull(e.getMessage(), ""));
             } else {
                 throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
             }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java
index d1e02c3..2aabf93 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java
@@ -1,6 +1,6 @@
 /*
- * 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
@@ -17,13 +17,40 @@
 
 package org.killbill.billing.payment.core.sm.payments;
 
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper;
 import org.killbill.billing.payment.core.sm.PaymentStateContext;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 
 public class ChargebackInitiated extends PaymentLeavingStateCallback {
 
     public ChargebackInitiated(final PaymentAutomatonDAOHelper daoHelper, final PaymentStateContext paymentStateContext) throws PaymentApiException {
         super(daoHelper, paymentStateContext);
     }
+
+    @Override
+    protected void validateUniqueTransactionExternalKey(final Iterable<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
+        // If no key specified, system will allocate a unique one later, there is nothing to check
+        if (paymentStateContext.getPaymentTransactionExternalKey() == null) {
+            return;
+        }
+
+        // The main difference with the default implementation is that an existing transaction in a SUCCESS state can exist (chargeback reversal)
+        if (Iterables.any(existingPaymentTransactions, new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao input) {
+                // An existing transaction for a different payment (to do really well, we should also check on paymentExternalKey which is not available here)
+                return (paymentStateContext.getPaymentId() != null && input.getPaymentId().compareTo(paymentStateContext.getPaymentId()) != 0) ||
+                       // Or, an existing transaction for a different account.
+                       (!input.getAccountRecordId().equals(paymentStateContext.getInternalCallContext().getAccountRecordId()));
+
+            }
+        })) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS, paymentStateContext.getPaymentTransactionExternalKey());
+        }
+    }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
index 4f16b73..433ed5b 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
@@ -19,8 +19,6 @@ package org.killbill.billing.payment.core.sm.payments;
 
 import java.math.BigDecimal;
 import java.util.Iterator;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
 
 import org.killbill.automaton.Operation.OperationCallback;
 import org.killbill.automaton.OperationException;
@@ -44,11 +42,9 @@ import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
 import org.killbill.billing.util.config.definition.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
-import org.killbill.commons.locker.LockFailedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -86,35 +82,15 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
         } else {
             try {
                 return doSimpleOperationCallback();
-            } catch (final Exception e) {
-                // We need to unwrap OperationException (see doSimpleOperationCallback below)
-                throw unwrapExceptionFromDispatchedTask(e);
+            } catch (final OperationException e) {
+                throw convertToUnknownTransactionStatusAndErroredPaymentState(e);
             }
         }
     }
 
     @Override
-    protected OperationException unwrapExceptionFromDispatchedTask(final Exception e) {
-        // If this is an ExecutionException we attempt to extract the cause first
-        final Throwable originalExceptionOrCausePossiblyOperationException = e instanceof ExecutionException ? MoreObjects.firstNonNull(e.getCause(), e) : e;
-
-        // Unwrap OperationException too (doOperationCallback wraps exceptions in OperationException)
-        final Throwable originalExceptionOrCause = originalExceptionOrCausePossiblyOperationException instanceof OperationException ? MoreObjects.firstNonNull(originalExceptionOrCausePossiblyOperationException.getCause(), originalExceptionOrCausePossiblyOperationException) : originalExceptionOrCausePossiblyOperationException;
-
-        //
-        // Any case of exception (checked or runtime) should lead to a TransactionStatus.UNKNOWN (and a XXX_ERRORED payment state).
-        // In order to reach that state we create PaymentTransactionInfoPlugin with an PaymentPluginStatus.UNDEFINED status (and an OperationResult.EXCEPTION).
-        //
-        if (originalExceptionOrCause instanceof LockFailedException) {
-            logger.warn("Failed to lock accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey());
-        } else if (originalExceptionOrCause instanceof TimeoutException) {
-            logger.warn("Plugin call TIMEOUT for accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey());
-        } else if (originalExceptionOrCause instanceof InterruptedException) {
-            logger.warn("Plugin call was interrupted for accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey());
-        } else {
-            logger.warn("Payment plugin call threw an exception for accountExternalKey='{}'", paymentStateContext.getAccount().getExternalKey(), originalExceptionOrCause);
-        }
-        return convertToUnknownTransactionStatusAndErroredPaymentState(originalExceptionOrCause);
+    protected OperationException unwrapExceptionFromDispatchedTask(final PaymentApiException e) {
+        return convertToUnknownTransactionStatusAndErroredPaymentState(e);
     }
 
     //
@@ -123,7 +99,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
     // - Construct a PaymentTransactionInfoPlugin whose PaymentPluginStatus = UNDEFINED to end up with a paymentTransactionStatus = UNKNOWN and have a chance to
     //   be fixed by Janitor.
     //
-    private OperationException convertToUnknownTransactionStatusAndErroredPaymentState(final Throwable e) {
+    private OperationException convertToUnknownTransactionStatusAndErroredPaymentState(final Exception e) {
 
         final PaymentTransactionInfoPlugin paymentInfoPlugin = new DefaultNoOpPaymentInfoPlugin(paymentStateContext.getPaymentId(),
                                                                                                 paymentStateContext.getTransactionId(),
@@ -136,6 +112,12 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
                                                                                                 null,
                                                                                                 null);
         paymentStateContext.setPaymentTransactionInfoPlugin(paymentInfoPlugin);
+        if (e.getCause() instanceof OperationException) {
+            return (OperationException) e.getCause();
+        }
+        if (e instanceof OperationException) {
+            return (OperationException) e;
+        }
         return new OperationException(e, OperationResult.EXCEPTION);
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
index dd94638..dd4f298 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
@@ -214,7 +214,7 @@ public class PaymentStateMachineHelper {
 
     // A better way would be to change the xml to add attributes to the state (e.g isTerminal, isSuccess, isInit,...)
     public boolean isSuccessState(final String stateName) {
-        return stateName.endsWith("SUCCESS");
+        return stateName.endsWith("SUCCESS") || stateName.startsWith("CHARGEBACK");
     }
 
     public final State fetchNextState(final String prevStateName, final boolean isSuccess) throws MissingEntryException {
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
index 2053ba1..3be74e7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
@@ -115,7 +115,7 @@ public class DefaultPaymentDao implements PaymentDao {
             public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
                 final String transactionIdStr = transactionId != null ? transactionId.toString() : null;
                 final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
-                transactional.updateAttempt(paymentAttemptId.toString(), transactionIdStr, state, context);
+                transactional.updateAttempt(paymentAttemptId.toString(), transactionIdStr, state, contextWithUpdatedDate(context));
                 return null;
             }
         });
@@ -129,7 +129,7 @@ public class DefaultPaymentDao implements PaymentDao {
             public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
                 final String transactionIdStr = transactionId != null ? transactionId.toString() : null;
                 final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
-                transactional.updateAttemptWithProperties(paymentAttemptId.toString(), transactionIdStr, state, pluginProperties, context);
+                transactional.updateAttemptWithProperties(paymentAttemptId.toString(), transactionIdStr, state, pluginProperties, contextWithUpdatedDate(context));
                 return null;
             }
         });
@@ -289,7 +289,7 @@ public class DefaultPaymentDao implements PaymentDao {
                 transactional.create(paymentTransaction, context);
                 final PaymentTransactionModelDao paymentTransactionModelDao = transactional.getById(paymentTransaction.getId().toString(), context);
 
-                entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentForNewTransaction(paymentId.toString(), context);
+                entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentForNewTransaction(paymentId.toString(), contextWithUpdatedDate(context));
 
                 return paymentTransactionModelDao;
             }
@@ -307,14 +307,15 @@ public class DefaultPaymentDao implements PaymentDao {
 
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                final InternalCallContext contextWithUpdatedDate = contextWithUpdatedDate(context);
                 entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).updateTransactionStatus(transactionId.toString(),
                                                                                                    processedAmount, processedCurrency == null ? null : processedCurrency.toString(),
                                                                                                    transactionStatus == null ? null : transactionStatus.toString(),
-                                                                                                   gatewayErrorCode, gatewayErrorMsg, context);
+                                                                                                   gatewayErrorCode, gatewayErrorMsg, contextWithUpdatedDate);
                 if (lastPaymentSuccessStateName != null) {
-                    entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updateLastSuccessPaymentStateName(paymentId.toString(), currentPaymentStateName, lastPaymentSuccessStateName, context);
+                    entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updateLastSuccessPaymentStateName(paymentId.toString(), currentPaymentStateName, lastPaymentSuccessStateName, contextWithUpdatedDate);
                 } else {
-                    entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentStateName(paymentId.toString(), currentPaymentStateName, context);
+                    entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentStateName(paymentId.toString(), currentPaymentStateName, contextWithUpdatedDate);
                 }
                 postPaymentEventFromTransaction(accountId, transactionStatus, transactionType, paymentId, transactionId, processedAmount, processedCurrency, clock.getUTCNow(), gatewayErrorCode, entitySqlDaoWrapperFactory, context);
                 return null;
@@ -504,12 +505,12 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     private void deletedPaymentMethodInTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID paymentMethodId, final InternalCallContext context) {
-        entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).markPaymentMethodAsDeleted(paymentMethodId.toString(), context);
+        entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).markPaymentMethodAsDeleted(paymentMethodId.toString(), contextWithUpdatedDate(context));
     }
 
     private void undeletedPaymentMethodInTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final UUID paymentMethodId, final InternalCallContext context) {
         final PaymentMethodSqlDao paymentMethodSqlDao = entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class);
-        paymentMethodSqlDao.unmarkPaymentMethodAsDeleted(paymentMethodId.toString(), context);
+        paymentMethodSqlDao.unmarkPaymentMethodAsDeleted(paymentMethodId.toString(), contextWithUpdatedDate(context));
     }
 
     @Override
@@ -518,11 +519,14 @@ public class DefaultPaymentDao implements PaymentDao {
 
             @Override
             public List<PaymentMethodModelDao> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+
+                final InternalCallContext contextWithUpdatedDate = contextWithUpdatedDate(context);
+
                 final PaymentMethodSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class);
                 // Look at all payment methods, including deleted ones. We assume that newPaymentMethods (payment methods returned by the plugin)
                 // is the full set of non-deleted payment methods in the plugin. If a payment method was marked as deleted on our side,
                 // but is still existing in the plugin, we will un-delete it.
-                final List<PaymentMethodModelDao> allPaymentMethodsForAccount = transactional.getForAccountIncludedDelete(context);
+                final List<PaymentMethodModelDao> allPaymentMethodsForAccount = transactional.getForAccountIncludedDelete(contextWithUpdatedDate);
 
                 // Consider only the payment methods for the plugin we are refreshing
                 final Collection<PaymentMethodModelDao> existingPaymentMethods = Collections2.filter(allPaymentMethodsForAccount,
@@ -543,7 +547,7 @@ public class DefaultPaymentDao implements PaymentDao {
                             break;
                         } else if (existingPaymentMethod.equalsButActive(finalPaymentMethod)) {
                             // We already have it but its status has changed - update it accordingly
-                            undeletedPaymentMethodInTransaction(entitySqlDaoWrapperFactory, existingPaymentMethod.getId(), context);
+                            undeletedPaymentMethodInTransaction(entitySqlDaoWrapperFactory, existingPaymentMethod.getId(), contextWithUpdatedDate);
                             foundExistingPaymentMethod = existingPaymentMethod;
                             break;
                         }
@@ -551,7 +555,7 @@ public class DefaultPaymentDao implements PaymentDao {
                     }
 
                     if (foundExistingPaymentMethod == null) {
-                        insertPaymentMethodInTransaction(entitySqlDaoWrapperFactory, finalPaymentMethod, context);
+                        insertPaymentMethodInTransaction(entitySqlDaoWrapperFactory, finalPaymentMethod, contextWithUpdatedDate);
                     } else {
                         existingPaymentMethods.remove(foundExistingPaymentMethod);
                     }
@@ -563,10 +567,10 @@ public class DefaultPaymentDao implements PaymentDao {
                     // the logic around audit/history will use getById to retrieve the entity and that method would not return
                     // a marked as deleted object
                     if (existingPaymentMethod.isActive()) {
-                        deletedPaymentMethodInTransaction(entitySqlDaoWrapperFactory, existingPaymentMethod.getId(), context);
+                        deletedPaymentMethodInTransaction(entitySqlDaoWrapperFactory, existingPaymentMethod.getId(), contextWithUpdatedDate);
                     }
                 }
-                return transactional.getForAccount(context);
+                return transactional.getForAccount(contextWithUpdatedDate);
             }
         });
     }
@@ -637,4 +641,8 @@ public class DefaultPaymentDao implements PaymentDao {
             log.error("Failed to post Payment event event for account {} ", accountId, e);
         }
     }
+
+    private InternalCallContext contextWithUpdatedDate(final InternalCallContext input) {
+        return new InternalCallContext(input, clock.getUTCNow());
+    }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
index fec3c2e..a84a036 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PaymentPluginDispatcher.java
@@ -45,15 +45,15 @@ public class PaymentPluginDispatcher {
         try {
             log.debug("Calling plugin(s) {}", pluginNames);
             final ReturnType result = pluginDispatcher.dispatchWithTimeout(callable);
-            log.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, accountId, result);
+            log.debug("Successful plugin(s) call of {} for account {} with result {}", pluginNames, accountExternalKey, result);
             return result;
         } catch (final TimeoutException e) {
-            final String errorMessage = String.format("TimeoutException while executing plugin='%s'", pluginNames);
+            final String errorMessage = String.format("Call TIMEOUT for accountId='%s' accountExternalKey='%s' plugin='%s'", accountId, accountExternalKey, pluginNames);
             log.warn(errorMessage);
             throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, errorMessage);
         } catch (final InterruptedException e) {
             Thread.currentThread().interrupt();
-            final String errorMessage = String.format("InterruptedException while executing plugin='%s'", pluginNames);
+            final String errorMessage = String.format("Call was interrupted for accountId='%s' accountExternalKey='%s' plugin='%s'", accountId, accountExternalKey, pluginNames);
             log.warn(errorMessage, e);
             throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), errorMessage));
         } catch (final ExecutionException e) {
@@ -64,7 +64,8 @@ public class PaymentPluginDispatcher {
                 log.warn(format);
                 throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, format);
             } else {
-                throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
+                // Unwraps the ExecutionException (e.getCause()), since it's a dispatch implementation detail
+                throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
             }
         }
     }
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
index 614e2fc..d8f6437 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
@@ -93,7 +93,7 @@ update <tableName()>
 set state_name = :stateName
 , transaction_id = :transactionId
 , updated_by = :updatedBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
@@ -105,7 +105,7 @@ set state_name = :stateName
 , transaction_id = :transactionId
 , plugin_properties = :pluginProperties
 , updated_by = :updatedBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
index 7d2d59c..bece059 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
@@ -34,7 +34,7 @@ markPaymentMethodAsDeleted(id) ::= <<
 update <tableName()>
 set is_active = false
 , updated_by = :updatedBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where  id = :id
 <AND_CHECK_TENANT()>
 ;
@@ -44,7 +44,7 @@ unmarkPaymentMethodAsDeleted(id) ::= <<
 update <tableName()>
 set is_active = true
 , updated_by = :updatedBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where  id = :id
 <AND_CHECK_TENANT()>
 ;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
index 5b4ff43..6b110e9 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -39,7 +39,7 @@ tableValues() ::= <<
 updatePaymentForNewTransaction() ::= <<
 update <tableName()>
 set updated_by = :updatedBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
@@ -49,7 +49,7 @@ updatePaymentStateName() ::= <<
 update <tableName()>
 set state_name = :stateName
 , updated_by = :updatedBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
@@ -60,7 +60,7 @@ update <tableName()>
 set state_name = :stateName
 , last_success_state_name = :lastSuccessStateName
 , updated_by = :updatedBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/RefundSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/RefundSqlDao.sql.stg
index 9f618e0..e5adc47 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/RefundSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/RefundSqlDao.sql.stg
@@ -40,7 +40,7 @@ set refund_status = :refundStatus
 , processed_amount = :processedAmount
 , processed_currency = :processedCurrency
 , updated_by = :updatedBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
index b538166..3a91dc9 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
@@ -65,7 +65,7 @@ set transaction_status = :transactionStatus
 , gateway_error_code = :gatewayErrorCode
 , gateway_error_msg = :gatewayErrorMsg
 , updated_by = :updatedBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
index 7453caf..da851a2 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
+++ b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
@@ -379,7 +379,6 @@
         <stateMachine name="CHARGEBACK">
             <states>
                 <state name="CHARGEBACK_INIT"/>
-                <state name="CHARGEBACK_PENDING"/>
                 <state name="CHARGEBACK_SUCCESS"/>
                 <state name="CHARGEBACK_FAILED"/>
                 <state name="CHARGEBACK_ERRORED"/>
@@ -400,30 +399,6 @@
                 <transition>
                     <initialState>CHARGEBACK_INIT</initialState>
                     <operation>OP_CHARGEBACK</operation>
-                    <operationResult>PENDING</operationResult>
-                    <finalState>CHARGEBACK_PENDING</finalState>
-                </transition>
-                <transition>
-                    <initialState>CHARGEBACK_PENDING</initialState>
-                    <operation>OP_CHARGEBACK</operation>
-                    <operationResult>SUCCESS</operationResult>
-                    <finalState>CHARGEBACK_SUCCESS</finalState>
-                </transition>
-                <transition>
-                    <initialState>CHARGEBACK_PENDING</initialState>
-                    <operation>OP_CHARGEBACK</operation>
-                    <operationResult>FAILURE</operationResult>
-                    <finalState>CHARGEBACK_FAILED</finalState>
-                </transition>
-                <transition>
-                    <initialState>CHARGEBACK_PENDING</initialState>
-                    <operation>OP_CHARGEBACK</operation>
-                    <operationResult>EXCEPTION</operationResult>
-                    <finalState>CHARGEBACK_ERRORED</finalState>
-                </transition>
-                <transition>
-                    <initialState>CHARGEBACK_INIT</initialState>
-                    <operation>OP_CHARGEBACK</operation>
                     <operationResult>EXCEPTION</operationResult>
                     <finalState>CHARGEBACK_ERRORED</finalState>
                 </transition>
@@ -555,5 +530,11 @@
             <finalStateMachine>CHARGEBACK</finalStateMachine>
             <finalState>CHARGEBACK_INIT</finalState>
         </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>CHARGEBACK</initialStateMachine>
+            <initialState>CHARGEBACK_FAILED</initialState>
+            <finalStateMachine>REFUND</finalStateMachine>
+            <finalState>REFUND_INIT</finalState>
+        </linkStateMachine>
     </linkStateMachines>
 </stateMachineConfig>
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultPayment.java b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultPayment.java
new file mode 100644
index 0000000..7cf7830
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultPayment.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2016 Groupon, Inc
+ * Copyright 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
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.api;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestDefaultPayment extends PaymentTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testAmountsCaptureVoided() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.AUTHORIZE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.CAPTURE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.VOID, TransactionStatus.SUCCESS, null));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testAmountsCaptureVoidedAuthReversed() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.AUTHORIZE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.CAPTURE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.VOID, TransactionStatus.SUCCESS, null),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.VOID, TransactionStatus.SUCCESS, null));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testAmountsCaptureChargeback() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.AUTHORIZE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.CAPTURE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.TEN));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testAmountsCaptureChargebackReversed() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.AUTHORIZE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.CAPTURE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.PAYMENT_FAILURE, BigDecimal.TEN));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testAmountsCaptureChargebackReversedAndRefund() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.AUTHORIZE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.CAPTURE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.REFUND, TransactionStatus.SUCCESS, BigDecimal.ONE),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.PAYMENT_FAILURE, BigDecimal.TEN));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ONE), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testAmountsPurchaseChargeback() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.PURCHASE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.TEN));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testAmountsPurchaseChargebackReversed() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.PURCHASE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.PAYMENT_FAILURE, BigDecimal.TEN));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testAmountsPurchaseChargebackReversedAndRefund() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.PURCHASE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.REFUND, TransactionStatus.SUCCESS, BigDecimal.ONE),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.PAYMENT_FAILURE, BigDecimal.TEN));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ONE), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testAmountsPurchaseMultipleChargebacks() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.PURCHASE, TransactionStatus.SUCCESS, BigDecimal.TEN),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.REFUND, TransactionStatus.SUCCESS, BigDecimal.ONE),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.ONE),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.ONE),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.PAYMENT_FAILURE, BigDecimal.ONE));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(new BigDecimal("9")), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ONE), 0);
+    }
+
+    private PaymentTransaction buildPaymentTransaction(final UUID paymentId, final String externalKey, final TransactionType transactionType, final TransactionStatus transactionStatus, final BigDecimal amount) {
+        return new DefaultPaymentTransaction(UUID.randomUUID(),
+                                             UUID.randomUUID(),
+                                             externalKey,
+                                             clock.getUTCNow(),
+                                             clock.getUTCNow(),
+                                             paymentId,
+                                             transactionType,
+                                             clock.getUTCNow(),
+                                             transactionStatus,
+                                             amount,
+                                             Currency.USD,
+                                             amount,
+                                             Currency.USD,
+                                             null,
+                                             null,
+                                             null);
+    }
+
+    private Payment buildPayment(final UUID paymentId, final List<PaymentTransaction> transactions) {
+        return new DefaultPayment(paymentId,
+                                  clock.getUTCNow(),
+                                  clock.getUTCNow(),
+                                  UUID.randomUUID(),
+                                  UUID.randomUUID(),
+                                  1,
+                                  UUID.randomUUID().toString(),
+                                  transactions);
+    }
+}
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 bb8c247..fa221bc 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
@@ -20,16 +20,14 @@ package org.killbill.billing.payment.api;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.UUID;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeoutException;
 
 import javax.annotation.Nullable;
 
 import org.joda.time.LocalDate;
-import org.killbill.automaton.OperationException;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.api.Currency;
@@ -37,15 +35,16 @@ import org.killbill.billing.control.plugin.api.PaymentControlApiException;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
 import org.killbill.billing.payment.MockRecurringInvoiceItem;
 import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
-import org.killbill.billing.payment.core.sm.OperationCallbackBase;
 import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.dao.PaymentSqlDao;
 import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
-import org.killbill.billing.payment.logging.SpyLogger;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
 import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
+import org.killbill.billing.payment.provider.MockPaymentControlProviderPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.bus.api.PersistentBus.EventBusException;
@@ -54,20 +53,21 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 
-import static org.killbill.billing.payment.logging.TestLoggingHelper.withSpyLogger;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
 public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
     private MockPaymentProviderPlugin mockPaymentProviderPlugin;
 
+    private MockPaymentControlProviderPlugin mockPaymentControlProviderPlugin;
+
     final PaymentOptions INVOICE_PAYMENT = new PaymentOptions() {
         @Override
         public boolean isExternalPayment() {
@@ -80,6 +80,18 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         }
     };
 
+    final PaymentOptions CONTROL_PLUGIN_OPTIONS = new PaymentOptions() {
+        @Override
+        public boolean isExternalPayment() {
+            return true;
+        }
+
+        @Override
+        public List<String> getPaymentControlPluginNames() {
+            return Arrays.asList(MockPaymentControlProviderPlugin.PLUGIN_NAME);
+        }
+    };
+
     private Account account;
 
     @BeforeClass(groups = "slow")
@@ -93,6 +105,24 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         super.beforeMethod();
         mockPaymentProviderPlugin.clear();
         account = testHelper.createTestAccount("bobo@gmail.com", true);
+
+        mockPaymentControlProviderPlugin = new MockPaymentControlProviderPlugin();
+        controlPluginRegistry.registerService(new OSGIServiceDescriptor() {
+            @Override
+            public String getPluginSymbolicName() {
+                return null;
+            }
+
+            @Override
+            public String getPluginName() {
+                return MockPaymentControlProviderPlugin.PLUGIN_NAME;
+            }
+
+            @Override
+            public String getRegistrationName() {
+                return MockPaymentControlProviderPlugin.PLUGIN_NAME;
+            }
+        }, mockPaymentControlProviderPlugin);
     }
 
     @Test(groups = "slow")
@@ -255,6 +285,72 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
     }
 
+    @Test(groups = "slow")
+    public void testCreatePurchasePaymentPluginException() {
+        mockPaymentProviderPlugin.makeNextPaymentFailWithException();
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final String paymentExternalKey = "pay external key";
+        final String transactionExternalKey = "txn external key";
+        try {
+            paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+                                      paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
+            fail();
+        } catch (PaymentApiException e) {
+            assertTrue(e.getCause() instanceof PaymentPluginApiException);
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testCreatePurchaseWithControlPaymentPluginException() throws Exception {
+        mockPaymentProviderPlugin.makeNextPaymentFailWithException();
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final String paymentExternalKey = "pay controle external key";;
+        final String transactionExternalKey = "txn control external key";
+        try {
+            paymentApi.createPurchaseWithPaymentControl(
+                    account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+                    paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
+            fail();
+        } catch (PaymentApiException e) {
+            assertTrue(e.getCause() instanceof PaymentPluginApiException);
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testCreatePurchaseWithControlPluginException() throws Exception {
+        mockPaymentControlProviderPlugin.throwsException(new PaymentControlApiException());
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final String paymentExternalKey = "pay controle external key";;
+        final String transactionExternalKey = "txn control external key";
+        try {
+            paymentApi.createPurchaseWithPaymentControl(
+                    account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+                    paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
+            fail();
+        } catch (PaymentApiException e) {
+            assertTrue(e.getCause() instanceof PaymentControlApiException);
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testCreatePurchaseWithControlPluginRuntimeException() throws Exception {
+        mockPaymentControlProviderPlugin.throwsException(new IllegalStateException());
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final String paymentExternalKey = "pay controle external key";;
+        final String transactionExternalKey = "txn control external key";
+        try {
+            paymentApi.createPurchaseWithPaymentControl(
+                    account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+                    paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
+            fail();
+        } catch (PaymentApiException e) {
+            assertTrue(e.getCause() instanceof IllegalStateException);
+        }
+    }
 
     @Test(groups = "slow")
     public void testCreateSuccessAuthVoid() throws PaymentApiException {
@@ -950,51 +1046,242 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(payment2.getCurrency(), Currency.USD);
     }
 
-    @Test(groups = "slow")
+    @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/477")
     public void testCreateChargeback() throws PaymentApiException {
+        // API change in 0.17
+        final DefaultPaymentApi paymentApi = (DefaultPaymentApi) this.paymentApi;
+
         final BigDecimal requestedAmount = BigDecimal.TEN;
+        final Currency currency = Currency.AED;
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String purchaseTransactionExternalKey = UUID.randomUUID().toString();
+        final String chargebackTransactionExternalKey = UUID.randomUUID().toString();
+        final ImmutableList<PluginProperty> properties = ImmutableList.<PluginProperty>of();
+
+        final Payment payment = paymentApi.createPurchase(account,
+                                                          account.getPaymentMethodId(),
+                                                          null,
+                                                          requestedAmount,
+                                                          currency,
+                                                          paymentExternalKey,
+                                                          purchaseTransactionExternalKey,
+                                                          properties,
+                                                          callContext);
 
-        final String paymentExternalKey = "couic";
-        final String transactionExternalKey = "couac";
-        final String transactionExternalKey2 = "couyc";
+        assertEquals(payment.getExternalKey(), paymentExternalKey);
+        assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment.getAccountId(), account.getId());
+        assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getPurchasedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCurrency(), currency);
 
-        final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
-                                                          ImmutableList.<PluginProperty>of(), callContext);
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getExternalKey(), purchaseTransactionExternalKey);
+        assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
+        assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), currency);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), currency);
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+        assertEquals(payment.getTransactions().get(0).getGatewayErrorMsg(), "");
+        assertEquals(payment.getTransactions().get(0).getGatewayErrorCode(), "");
 
-        paymentApi.createChargeback(account, payment.getId(), requestedAmount, Currency.AED, transactionExternalKey2, callContext);
-        final Payment payment2 = paymentApi.getPayment(payment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getStateName(), "PURCHASE_SUCCESS");
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getLastSuccessStateName(), "PURCHASE_SUCCESS");
+
+        // First chargeback
+        final Payment payment2 = paymentApi.createChargeback(account,
+                                                             payment.getId(),
+                                                             requestedAmount,
+                                                             currency,
+                                                             chargebackTransactionExternalKey,
+                                                             callContext);
 
         assertEquals(payment2.getExternalKey(), paymentExternalKey);
         assertEquals(payment2.getPaymentMethodId(), account.getPaymentMethodId());
         assertEquals(payment2.getAccountId(), account.getId());
         assertEquals(payment2.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment2.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
-        assertEquals(payment2.getPurchasedAmount().compareTo(requestedAmount), 0);
+        // Purchase amount zero-ed out
+        assertEquals(payment2.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment2.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
-        assertEquals(payment2.getCurrency(), Currency.AED);
+        assertEquals(payment2.getCurrency(), currency);
 
         assertEquals(payment2.getTransactions().size(), 2);
-        assertEquals(payment2.getTransactions().get(1).getExternalKey(), transactionExternalKey2);
+        assertEquals(payment2.getTransactions().get(1).getExternalKey(), chargebackTransactionExternalKey);
         assertEquals(payment2.getTransactions().get(1).getPaymentId(), payment.getId());
         assertEquals(payment2.getTransactions().get(1).getAmount().compareTo(requestedAmount), 0);
-        assertEquals(payment2.getTransactions().get(1).getCurrency(), Currency.AED);
-
+        assertEquals(payment2.getTransactions().get(1).getCurrency(), currency);
         assertEquals(payment2.getTransactions().get(1).getProcessedAmount().compareTo(requestedAmount), 0);
-        assertEquals(payment2.getTransactions().get(1).getProcessedCurrency(), Currency.AED);
-
+        assertEquals(payment2.getTransactions().get(1).getProcessedCurrency(), currency);
         assertEquals(payment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
         assertEquals(payment2.getTransactions().get(1).getTransactionType(), TransactionType.CHARGEBACK);
         assertNull(payment2.getTransactions().get(1).getGatewayErrorMsg());
         assertNull(payment2.getTransactions().get(1).getGatewayErrorCode());
 
-        // Attempt to any other operation afterwards, that should fail
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getStateName(), "CHARGEBACK_SUCCESS");
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getLastSuccessStateName(), "CHARGEBACK_SUCCESS");
+
         try {
-            paymentApi.createPurchase(account, account.getPaymentMethodId(), payment.getId(), requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
-                                      ImmutableList.<PluginProperty>of(), callContext);
-            Assert.fail("Purchase not succeed after a chargeback");
+            paymentApi.createRefund(account,
+                                    payment.getId(),
+                                    requestedAmount,
+                                    currency,
+                                    UUID.randomUUID().toString(),
+                                    properties,
+                                    callContext);
+            Assert.fail("Refunds are no longer permitted after a chargeback");
         } catch (final PaymentApiException e) {
-            Assert.assertTrue(true);
+            assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
         }
+
+        // First reversal
+        final Payment payment3 = paymentApi.createChargebackReversal(account,
+                                                                     payment.getId(),
+                                                                     chargebackTransactionExternalKey,
+                                                                     callContext);
+
+        assertEquals(payment3.getExternalKey(), paymentExternalKey);
+        assertEquals(payment3.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment3.getAccountId(), account.getId());
+        assertEquals(payment3.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        // Actual purchase amount
+        assertEquals(payment3.getPurchasedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment3.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getCurrency(), currency);
+
+        assertEquals(payment3.getTransactions().size(), 3);
+        assertEquals(payment3.getTransactions().get(2).getExternalKey(), chargebackTransactionExternalKey);
+        assertEquals(payment3.getTransactions().get(2).getPaymentId(), payment.getId());
+        assertNull(payment3.getTransactions().get(2).getAmount());
+        assertNull(payment3.getTransactions().get(2).getCurrency());
+        assertEquals(payment3.getTransactions().get(2).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertNull(payment3.getTransactions().get(2).getProcessedCurrency());
+        assertEquals(payment3.getTransactions().get(2).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        assertEquals(payment3.getTransactions().get(2).getTransactionType(), TransactionType.CHARGEBACK);
+        assertNull(payment3.getTransactions().get(2).getGatewayErrorMsg());
+        assertNull(payment3.getTransactions().get(2).getGatewayErrorCode());
+
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getStateName(), "CHARGEBACK_FAILED");
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getLastSuccessStateName(), "CHARGEBACK_FAILED");
+
+        // Attempt a refund
+        final BigDecimal refundAmount = BigDecimal.ONE;
+        final String refundTransactionExternalKey = UUID.randomUUID().toString();
+        final Payment payment4 = paymentApi.createRefund(account,
+                                                         payment.getId(),
+                                                         refundAmount,
+                                                         currency,
+                                                         refundTransactionExternalKey,
+                                                         properties,
+                                                         callContext);
+
+        assertEquals(payment4.getExternalKey(), paymentExternalKey);
+        assertEquals(payment4.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment4.getAccountId(), account.getId());
+        assertEquals(payment4.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment4.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        // Actual purchase amount
+        assertEquals(payment4.getPurchasedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment4.getRefundedAmount().compareTo(refundAmount), 0);
+        assertEquals(payment4.getCurrency(), currency);
+
+        assertEquals(payment4.getTransactions().size(), 4);
+        assertEquals(payment4.getTransactions().get(3).getExternalKey(), refundTransactionExternalKey);
+        assertEquals(payment4.getTransactions().get(3).getPaymentId(), payment.getId());
+        assertEquals(payment4.getTransactions().get(3).getAmount().compareTo(refundAmount), 0);
+        assertEquals(payment4.getTransactions().get(3).getCurrency(), currency);
+        assertEquals(payment4.getTransactions().get(3).getProcessedAmount().compareTo(refundAmount), 0);
+        assertEquals(payment4.getTransactions().get(3).getProcessedCurrency(), currency);
+        assertEquals(payment4.getTransactions().get(3).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment4.getTransactions().get(3).getTransactionType(), TransactionType.REFUND);
+        assertEquals(payment4.getTransactions().get(3).getGatewayErrorMsg(), "");
+        assertEquals(payment4.getTransactions().get(3).getGatewayErrorCode(), "");
+
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getStateName(), "REFUND_SUCCESS");
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getLastSuccessStateName(), "REFUND_SUCCESS");
+
+        // Second chargeback
+        final BigDecimal secondChargebackAmount = requestedAmount.add(refundAmount.negate());
+        final Payment payment5 = paymentApi.createChargeback(account,
+                                                             payment.getId(),
+                                                             secondChargebackAmount,
+                                                             currency,
+                                                             chargebackTransactionExternalKey,
+                                                             callContext);
+
+        assertEquals(payment5.getExternalKey(), paymentExternalKey);
+        assertEquals(payment5.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment5.getAccountId(), account.getId());
+        assertEquals(payment5.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment5.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        // Purchase amount zero-ed out
+        assertEquals(payment5.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment5.getRefundedAmount().compareTo(refundAmount), 0);
+        assertEquals(payment5.getCurrency(), currency);
+
+        assertEquals(payment5.getTransactions().size(), 5);
+        assertEquals(payment5.getTransactions().get(4).getExternalKey(), chargebackTransactionExternalKey);
+        assertEquals(payment5.getTransactions().get(4).getPaymentId(), payment.getId());
+        assertEquals(payment5.getTransactions().get(4).getAmount().compareTo(secondChargebackAmount), 0);
+        assertEquals(payment5.getTransactions().get(4).getCurrency(), currency);
+        assertEquals(payment5.getTransactions().get(4).getProcessedAmount().compareTo(secondChargebackAmount), 0);
+        assertEquals(payment5.getTransactions().get(4).getProcessedCurrency(), currency);
+        assertEquals(payment5.getTransactions().get(4).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment5.getTransactions().get(4).getTransactionType(), TransactionType.CHARGEBACK);
+        assertNull(payment5.getTransactions().get(4).getGatewayErrorMsg());
+        assertNull(payment5.getTransactions().get(4).getGatewayErrorCode());
+
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getStateName(), "CHARGEBACK_SUCCESS");
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getLastSuccessStateName(), "CHARGEBACK_SUCCESS");
+
+        try {
+            paymentApi.createRefund(account,
+                                    payment.getId(),
+                                    refundAmount,
+                                    currency,
+                                    UUID.randomUUID().toString(),
+                                    properties,
+                                    callContext);
+            Assert.fail("Refunds are no longer permitted after a chargeback");
+        } catch (final PaymentApiException e) {
+            assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+        }
+
+        // Second reversal
+        final Payment payment6 = paymentApi.createChargebackReversal(account,
+                                                                     payment.getId(),
+                                                                     chargebackTransactionExternalKey,
+                                                                     callContext);
+
+        assertEquals(payment6.getExternalKey(), paymentExternalKey);
+        assertEquals(payment6.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment6.getAccountId(), account.getId());
+        assertEquals(payment6.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment6.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        // Actual purchase amount
+        assertEquals(payment6.getPurchasedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment6.getRefundedAmount().compareTo(refundAmount), 0);
+        assertEquals(payment6.getCurrency(), currency);
+
+        assertEquals(payment6.getTransactions().size(), 6);
+        assertEquals(payment6.getTransactions().get(5).getExternalKey(), chargebackTransactionExternalKey);
+        assertEquals(payment6.getTransactions().get(5).getPaymentId(), payment.getId());
+        assertNull(payment6.getTransactions().get(5).getAmount());
+        assertNull(payment6.getTransactions().get(5).getCurrency());
+        assertEquals(payment6.getTransactions().get(5).getProcessedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertNull(payment6.getTransactions().get(5).getProcessedCurrency());
+        assertEquals(payment6.getTransactions().get(5).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        assertEquals(payment6.getTransactions().get(5).getTransactionType(), TransactionType.CHARGEBACK);
+        assertNull(payment6.getTransactions().get(5).getGatewayErrorMsg());
+        assertNull(payment6.getTransactions().get(5).getGatewayErrorCode());
+
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getStateName(), "CHARGEBACK_FAILED");
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getLastSuccessStateName(), "CHARGEBACK_FAILED");
     }
 
     @Test(groups = "slow")
@@ -1183,38 +1470,33 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         final String paymentExternalKey = "ohhhh";
         final String transactionExternalKey = "naaahhh";
 
-        final String pluginName = mockPaymentProviderPlugin.PLUGIN_NAME;
-
         mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds((int) (paymentConfig.getPaymentPluginTimeout().getMillis() + 100));
+        try {
+            paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED,
+                                      paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
+            fail();
+        } catch (PaymentApiException e) {
+            assertEquals(e.getCode(), ErrorCode.PAYMENT_PLUGIN_TIMEOUT.getCode());
+        }
+    }
 
-        SpyLogger spyLogger = withSpyLogger(OperationCallbackBase.class, new Callable<Void>() {
-
-            @Override
-            public Void call() throws Exception {
-                PaymentApiException thrownException = null;
-
-                try {
-                    final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
-                                                                      ImmutableList.<PluginProperty>of(), callContext);
-                } catch (PaymentApiException e) {
-                    thrownException = e;
-                }
-
-                assertNotNull(thrownException);
-
-                Throwable timeoutException = thrownException.getCause();
-                assertNotNull(timeoutException);
-                assertTrue(timeoutException instanceof TimeoutException);
-
-                return null;
-            }
-        });
+    @Test(groups = "slow")
+    public void testCreatePurchaseWithControlTimeout() throws Exception {
+        final BigDecimal requestedAmount = BigDecimal.ONE;
+        final String paymentExternalKey = "111111";
+        final String transactionExternalKey = "11111";
 
-        assertTrue(spyLogger.contains("Calling plugin.*" + pluginName, Optional.of(SpyLogger.LOG_LEVEL_DEBUG)));
-        assertTrue(spyLogger.contains("TimeoutException.*" + pluginName, Optional.of(SpyLogger.LOG_LEVEL_WARN)));
+        mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds((int) (paymentConfig.getPaymentPluginTimeout().getMillis() + 100));
+        try {
+            paymentApi.createPurchaseWithPaymentControl(
+                    account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey,
+                    transactionExternalKey, ImmutableList.<PluginProperty>of(), CONTROL_PLUGIN_OPTIONS, callContext);
+            fail();
+        } catch (PaymentApiException e) {
+            assertEquals(e.getCode(), ErrorCode.PAYMENT_PLUGIN_TIMEOUT.getCode());
+        }
     }
 
-
     @Test(groups = "slow")
     public void testSanityAcrossTransactionTypes() throws PaymentApiException {
 
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java
index 680221b..3aa3554 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
 import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult;
@@ -152,6 +153,20 @@ public class TestPaymentGatewayApiWithPaymentControl extends PaymentTestSuiteNoD
 
     }
 
+    @Test(groups = "fast")
+    public void testBuildFormDescriptorWithPaymentControlAbortedPayment() throws PaymentApiException {
+        plugin.setAborted(true);
+
+        // Set a random UUID to verify the plugin will successfully override it
+        try {
+            paymentGatewayApi.buildFormDescriptorWithPaymentControl(account, UUID.randomUUID(), ImmutableList.<PluginProperty>of(), ImmutableList.<PluginProperty>of(), paymentOptions, callContext);
+            Assert.fail();
+        } catch (PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode());
+        }
+
+    }
+
     public static class TestPaymentGatewayApiValidationPlugin implements PaymentControlPluginApi {
 
         public static final String VALIDATION_PLUGIN_NAME = "TestPaymentGatewayApiValidationPlugin";
@@ -216,13 +231,20 @@ public class TestPaymentGatewayApiWithPaymentControl extends PaymentTestSuiteNoD
         private Iterable<PluginProperty> newOnResultProperties;
         private Iterable<PluginProperty> removedOnResultProperties;
 
+        private boolean aborted;
+
         public TestPaymentGatewayApiControlPlugin() {
+            this.aborted = false;
             this.newPriorCallProperties = ImmutableList.of();
             this.removedPriorCallProperties = ImmutableList.of();
             this.newOnResultProperties = ImmutableList.of();
             this.removedOnResultProperties = ImmutableList.of();
         }
 
+        public void setAborted(final boolean aborted) {
+            this.aborted = aborted;
+        }
+
         public void setPriorCallProperties(final Iterable<PluginProperty> newPriorCallProperties, final Iterable<PluginProperty> removedPriorCallProperties) {
             this.newPriorCallProperties = newPriorCallProperties;
             this.removedPriorCallProperties = removedPriorCallProperties;
@@ -235,7 +257,7 @@ public class TestPaymentGatewayApiWithPaymentControl extends PaymentTestSuiteNoD
 
         @Override
         public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
-            return new DefaultPriorPaymentControlResult(false, account.getPaymentMethodId(), null, null, getAdjustedProperties(properties, newPriorCallProperties, removedPriorCallProperties));
+            return new DefaultPriorPaymentControlResult(aborted, account.getPaymentMethodId(), null, null, getAdjustedProperties(properties, newPriorCallProperties, removedPriorCallProperties));
         }
 
         @Override
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
index 9b1c710..a09ddfa 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
@@ -63,6 +63,8 @@ public class MockRetryAuthorizeOperationCallback extends AuthorizeControlOperati
         if (exception != null) {
             if (exception instanceof PaymentApiException) {
                 throw (PaymentApiException) exception;
+            } else if (exception instanceof RuntimeException) {
+                throw (RuntimeException) exception;
             } else {
                 throw new RuntimeException(exception);
             }
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 94463d3..2e642c9 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
@@ -57,7 +57,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
 
     private static final String PLUGIN_NAME_PLACEHOLDER = "pluginName";
 
-    private static final int TIMEOUT = 5;
+    private static final int TIMEOUT = 10;
 
     private final GlobalLocker locker = new MemoryGlobalLocker();
     private final Account account = Mockito.mock(Account.class);
@@ -97,7 +97,8 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
             Assert.fail();
         } catch (final OperationException e) {
             Assert.assertEquals(e.getOperationResult(), OperationResult.EXCEPTION);
-            Assert.assertTrue(e.getCause() instanceof NullPointerException);
+            Assert.assertTrue(e.getCause() instanceof PaymentApiException);
+            Assert.assertTrue(e.getCause().getCause() instanceof NullPointerException);
         }
     }
 
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
index 5a50314..9f6f8d8 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
@@ -73,7 +73,9 @@ import com.google.inject.Inject;
 
 import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
 
 public class TestRetryablePayment extends PaymentTestSuiteNoDB {
 
@@ -227,19 +229,26 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
         runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
               .setContext(paymentStateContext);
 
-        runner.run(true,
-                   TransactionType.AUTHORIZE,
-                   account,
-                   paymentMethodId,
-                   null,
-                   paymentExternalKey,
-                   paymentTransactionExternalKey,
-                   amount,
-                   currency,
-                   emptyProperties,
-                   null,
-                   callContext,
-                   internalCallContext);
+        try {
+            runner.run(true,
+                       TransactionType.AUTHORIZE,
+                       account,
+                       paymentMethodId,
+                       null,
+                       paymentExternalKey,
+                       paymentTransactionExternalKey,
+                       amount,
+                       currency,
+                       emptyProperties,
+                       null,
+                       callContext,
+                       internalCallContext);
+            fail();
+        } catch (PaymentApiException e) {
+            assertEquals(e.getCode(), ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode());
+        }
+        assertFalse(mockRetryProviderPlugin.isOnSuccessCallExecuted(), "OnSuccessCall method should not be called when payment is aborted");
+        assertFalse(mockRetryProviderPlugin.isOnFailureCallExecuted(), "onFailureCall method should not be called when payment is aborted");
 
         final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
         assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
@@ -342,7 +351,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                        null,
                        callContext, internalCallContext);
 
-            Assert.fail("Expected PaymentApiException...");
+            fail("Expected PaymentApiException...");
 
         } catch (final PaymentApiException e) {
             final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
@@ -380,7 +389,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                        null,
                        callContext, internalCallContext);
 
-            Assert.fail("Expected PaymentApiException...");
+            fail("Expected PaymentApiException...");
         } catch (final PaymentApiException e) {
             final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
             assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
@@ -417,7 +426,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                        null,
                        callContext, internalCallContext);
 
-            Assert.fail("Expected Exception...");
+            fail("Expected Exception...");
         } catch (final PaymentApiException e) {
             final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
             assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
@@ -454,7 +463,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                        null,
                        callContext, internalCallContext);
 
-            Assert.fail("Expected Exception...");
+            fail("Expected Exception...");
         } catch (final PaymentApiException e) {
             final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
             assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
@@ -549,7 +558,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                        callContext,
                        internalCallContext);
 
-            Assert.fail("Expecting paymentApiException...");
+            fail("Expecting paymentApiException...");
         } catch (final PaymentApiException e) {
             final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
             assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
@@ -596,7 +605,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
                        callContext,
                        internalCallContext);
 
-            Assert.fail("Expecting paymentApiException...");
+            fail("Expecting paymentApiException...");
         } catch (final PaymentApiException e) {
 
             final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext);
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
index 4d3a3d0..655b60a 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
@@ -20,6 +20,7 @@ package org.killbill.billing.payment;
 
 import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
 import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApi;
@@ -59,6 +60,8 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @Inject
     protected OSGIServiceRegistration<PaymentPluginApi> registry;
     @Inject
+    protected OSGIServiceRegistration<PaymentControlPluginApi> controlPluginRegistry;
+    @Inject
     protected PersistentBus eventBus;
     @Inject
     protected PaymentApi paymentApi;
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
index 0327309..3719daf 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
@@ -35,6 +35,11 @@ public class MockPaymentControlProviderPlugin implements PaymentControlPluginApi
 
     private boolean isAborted;
     private DateTime nextRetryDate;
+    private Exception exception;
+
+    private boolean priorCallExecuted;
+    private boolean onSuccessCallExecuted;
+    private boolean onFailureCallExecuted;
 
     public MockPaymentControlProviderPlugin setAborted(final boolean isAborted) {
         this.isAborted = isAborted;
@@ -46,18 +51,48 @@ public class MockPaymentControlProviderPlugin implements PaymentControlPluginApi
         return this;
     }
 
+    public MockPaymentControlProviderPlugin throwsException(PaymentControlApiException exception) {
+        this.exception = exception;
+        return this;
+    }
+
+    public MockPaymentControlProviderPlugin throwsException(RuntimeException exception) {
+        this.exception = exception;
+        return this;
+    }
+
     @Override
     public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+        priorCallExecuted = true;
+        if (exception instanceof PaymentControlApiException) {
+            throw (PaymentControlApiException) exception;
+        } else if (exception instanceof RuntimeException) {
+            throw (RuntimeException) exception;
+        }
         return new DefaultPriorPaymentControlResult(isAborted);
     }
 
     @Override
     public OnSuccessPaymentControlResult onSuccessCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+        onSuccessCallExecuted = true;
         return new DefaultOnSuccessPaymentControlResult();
     }
 
     @Override
     public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+        onFailureCallExecuted = true;
         return new DefaultFailureCallResult(nextRetryDate);
     }
+
+    public boolean isPriorCallExecuted() {
+        return priorCallExecuted;
+    }
+
+    public boolean isOnSuccessCallExecuted() {
+        return onSuccessCallExecuted;
+    }
+
+    public boolean isOnFailureCallExecuted() {
+        return onFailureCallExecuted;
+    }
 }
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 b46915f..69a716d 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
@@ -18,6 +18,7 @@
 package org.killbill.billing.jaxrs;
 
 import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.UUID;
 
@@ -32,11 +33,14 @@ import org.killbill.billing.client.model.PaymentMethodPluginDetail;
 import org.killbill.billing.client.model.PaymentTransaction;
 import org.killbill.billing.client.model.Payments;
 import org.killbill.billing.client.model.PluginProperty;
+import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.provider.MockPaymentControlProviderPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
@@ -48,20 +52,42 @@ import com.google.common.collect.ImmutableMap;
 import com.google.inject.Inject;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.fail;
 
 public class TestPayment extends TestJaxrsBase {
 
     @Inject
     protected OSGIServiceRegistration<PaymentPluginApi> registry;
+    @Inject
+    private OSGIServiceRegistration<PaymentControlPluginApi> controlPluginRegistry;
 
     private MockPaymentProviderPlugin mockPaymentProviderPlugin;
+    private MockPaymentControlProviderPlugin mockPaymentControlProviderPlugin;
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
         mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(PLUGIN_NAME);
         mockPaymentProviderPlugin.clear();
+
+        mockPaymentControlProviderPlugin = new MockPaymentControlProviderPlugin();
+        controlPluginRegistry.registerService(new OSGIServiceDescriptor() {
+            @Override
+            public String getPluginSymbolicName() {
+                return null;
+            }
+
+            @Override
+            public String getPluginName() {
+                return MockPaymentControlProviderPlugin.PLUGIN_NAME;
+            }
+
+            @Override
+            public String getRegistrationName() {
+                return MockPaymentControlProviderPlugin.PLUGIN_NAME;
+            }
+        }, mockPaymentControlProviderPlugin);
     }
 
     @Test(groups = "slow")
@@ -74,9 +100,48 @@ public class TestPayment extends TestJaxrsBase {
         authTransaction.setAmount(BigDecimal.ONE);
         authTransaction.setCurrency(account.getCurrency());
         authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
-        final Payment payment = killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
-        assertEquals(payment.getTransactions().get(0).getGatewayErrorCode(), MockPaymentProviderPlugin.GATEWAY_ERROR_CODE);
-        assertEquals(payment.getTransactions().get(0).getGatewayErrorMsg(), MockPaymentProviderPlugin.GATEWAY_ERROR);
+        try {
+            killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+            fail();
+        } catch (KillBillClientException e) {
+            assertEquals(402, e.getResponse().getStatusCode());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testWithCanceledPayment() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+
+        mockPaymentProviderPlugin.makeNextPaymentFailWithCancellation();
+
+        final PaymentTransaction authTransaction = new PaymentTransaction();
+        authTransaction.setAmount(BigDecimal.ONE);
+        authTransaction.setCurrency(account.getCurrency());
+        authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
+        try {
+            killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+            fail();
+        } catch (KillBillClientException e) {
+            assertEquals(502, e.getResponse().getStatusCode());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testWithTimeoutPayment() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+
+        mockPaymentProviderPlugin.makePluginWaitSomeMilliseconds(10000);
+
+        final PaymentTransaction authTransaction = new PaymentTransaction();
+        authTransaction.setAmount(BigDecimal.ONE);
+        authTransaction.setCurrency(account.getCurrency());
+        authTransaction.setTransactionType(TransactionType.AUTHORIZE.name());
+        try {
+            killBillClient.createPayment(account.getAccountId(), account.getPaymentMethodId(), authTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+            fail();
+        } catch (KillBillClientException e) {
+            assertEquals(504, e.getResponse().getStatusCode());
+        }
     }
 
     @Test(groups = "slow")
@@ -363,14 +428,61 @@ public class TestPayment extends TestJaxrsBase {
     public void testComboAuthorization() throws Exception {
         final Account accountJson = getAccount();
         accountJson.setAccountId(null);
+        final String paymentExternalKey = UUID.randomUUID().toString();
+
+        final ComboPaymentTransaction comboPaymentTransaction = createComboPaymentTransaction(accountJson, paymentExternalKey);
+
+        final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
+        verifyComboPayment(payment, paymentExternalKey, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
+
+        // Void payment using externalKey
+        final String voidTransactionExternalKey = UUID.randomUUID().toString();
+        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");
+    }
+
+    @Test(groups = "slow")
+    public void testComboAuthorizationAbortedPayment() throws Exception {
+        final Account accountJson = getAccount();
+        accountJson.setAccountId(null);
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final ComboPaymentTransaction comboPaymentTransaction = createComboPaymentTransaction(accountJson, paymentExternalKey);
+
+        mockPaymentControlProviderPlugin.setAborted(true);
+        try {
+            killBillClient.createPayment(comboPaymentTransaction, Arrays.asList(MockPaymentControlProviderPlugin.PLUGIN_NAME), ImmutableMap.<String, String>of(), createdBy, reason, comment);
+            fail();
+        } catch (KillBillClientException e) {
+            assertEquals(e.getResponse().getStatusCode(), 422);
+        }
+        assertFalse(mockPaymentControlProviderPlugin.isOnFailureCallExecuted());
+        assertFalse(mockPaymentControlProviderPlugin.isOnSuccessCallExecuted());
+    }
+
+    @Test(groups = "slow")
+    public void testComboAuthorizationControlPluginException() throws Exception {
+        final Account accountJson = getAccount();
+        accountJson.setAccountId(null);
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final ComboPaymentTransaction comboPaymentTransaction = createComboPaymentTransaction(accountJson, paymentExternalKey);
 
+        mockPaymentControlProviderPlugin.throwsException(new IllegalStateException());
+        try {
+            killBillClient.createPayment(comboPaymentTransaction, Arrays.asList(MockPaymentControlProviderPlugin.PLUGIN_NAME), ImmutableMap.<String, String>of(), createdBy, reason, comment);
+            fail();
+        } catch (KillBillClientException e) {
+            assertEquals(e.getResponse().getStatusCode(), 500);
+        }
+    }
+
+    private ComboPaymentTransaction createComboPaymentTransaction(final Account accountJson, final String paymentExternalKey) {
         final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
         info.setProperties(null);
 
         final String paymentMethodExternalKey = UUID.randomUUID().toString();
         final PaymentMethod paymentMethodJson = new PaymentMethod(null, paymentMethodExternalKey, null, true, PLUGIN_NAME, info);
 
-        final String paymentExternalKey = UUID.randomUUID().toString();
         final String authTransactionExternalKey = UUID.randomUUID().toString();
         final PaymentTransaction authTransactionJson = new PaymentTransaction();
         authTransactionJson.setAmount(BigDecimal.TEN);
@@ -379,16 +491,7 @@ public class TestPayment extends TestJaxrsBase {
         authTransactionJson.setTransactionExternalKey(authTransactionExternalKey);
         authTransactionJson.setTransactionType("AUTHORIZE");
 
-        final ComboPaymentTransaction comboPaymentTransaction = new ComboPaymentTransaction(accountJson, paymentMethodJson, authTransactionJson, ImmutableList.<PluginProperty>of(), ImmutableList.<PluginProperty>of());
-
-        final Payment payment = killBillClient.createPayment(comboPaymentTransaction, ImmutableMap.<String, String>of(), createdBy, reason, comment);
-        verifyComboPayment(payment, paymentExternalKey, BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, 1);
-
-        // Void payment using externalKey
-        final String voidTransactionExternalKey = UUID.randomUUID().toString();
-        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");
+        return new ComboPaymentTransaction(accountJson, paymentMethodJson, authTransactionJson, ImmutableList.<PluginProperty>of(), ImmutableList.<PluginProperty>of());
     }
 
     @Test(groups = "slow")
diff --git a/profiles/killbill/src/test/resources/killbill.properties b/profiles/killbill/src/test/resources/killbill.properties
index 71e091f..c6def31 100644
--- a/profiles/killbill/src/test/resources/killbill.properties
+++ b/profiles/killbill/src/test/resources/killbill.properties
@@ -21,6 +21,8 @@ org.killbill.overdue.uri=overdue.xml
 
 org.killbill.payment.retry.days=8,8,8
 
+org.killbill.payment.plugin.timeout=8s
+
 # Local DB
 #org.killbill.billing.dbi.test.useLocalDb=true
 
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
index f1eef82..879f9f9 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/alignment/PlanAligner.java
@@ -44,6 +44,10 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseTransitionData
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
 import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
 /**
  * PlanAligner offers specific APIs to return the correct {@code TimedPhase} when creating, changing Plan or to compute
  * next Phase on current Plan.
@@ -171,6 +175,7 @@ public class PlanAligner extends BaseAligner {
                                                  lastPlanTransition.getNextPriceList().getName(),
                                                  effectiveDate,
                                                  lastPlanTransition.getEffectiveTransitionTime(),
+                                                 subscription.getAllTransitions().get(0).getNextPhase().getPhaseType(),
                                                  WhichPhase.NEXT,
                                                  context);
                 default:
@@ -229,6 +234,7 @@ public class PlanAligner extends BaseAligner {
                                      effectiveDate,
                                      // This method is only called while doing the change, hence we want to pass the change effective date
                                      effectiveDate,
+                                     subscription.getAllTransitions().get(0).getNextPhase().getPhaseType(),
                                      which,
                                      context);
     }
@@ -242,6 +248,7 @@ public class PlanAligner extends BaseAligner {
                                              final String priceList,
                                              final DateTime effectiveDate,
                                              final DateTime lastOrCurrentChangeEffectiveDate,
+                                             final PhaseType originalInitialPhase,
                                              final WhichPhase which,
                                              final InternalTenantContext context) throws CatalogApiException, SubscriptionBaseApiException {
         final Catalog catalog = catalogService.getFullCatalog(context);
@@ -257,17 +264,21 @@ public class PlanAligner extends BaseAligner {
                                                                 nextPlan.getRecurringBillingPeriod(),
                                                                 priceList);
 
+        final PhaseType initialPhase;
         final DateTime planStartDate;
         final PlanAlignmentChange alignment = catalog.planChangeAlignment(fromPlanPhaseSpecifier, toPlanSpecifier, effectiveDate);
         switch (alignment) {
             case START_OF_SUBSCRIPTION:
                 planStartDate = subscriptionStartDate;
+                initialPhase = isPlanContainPhaseType(nextPlan, originalInitialPhase) ? originalInitialPhase : null;
                 break;
             case START_OF_BUNDLE:
                 planStartDate = bundleStartDate;
+                initialPhase = isPlanContainPhaseType(nextPlan, originalInitialPhase) ? originalInitialPhase : null;
                 break;
             case CHANGE_OF_PLAN:
                 planStartDate = lastOrCurrentChangeEffectiveDate;
+                initialPhase = null;
                 break;
             case CHANGE_OF_PRICELIST:
                 throw new SubscriptionBaseError(String.format("Not implemented yet %s", alignment));
@@ -275,10 +286,11 @@ public class PlanAligner extends BaseAligner {
                 throw new SubscriptionBaseError(String.format("Unknown PlanAlignmentChange %s", alignment));
         }
 
-        final List<TimedPhase> timedPhases = getPhaseAlignments(nextPlan, null, planStartDate);
+        final List<TimedPhase> timedPhases = getPhaseAlignments(nextPlan, initialPhase, planStartDate);
         return getTimedPhase(timedPhases, effectiveDate, which);
     }
 
+
     private List<TimedPhase> getPhaseAlignments(final Plan plan, @Nullable final PhaseType initialPhase, final DateTime initialPhaseStartDate) throws SubscriptionBaseApiException {
         if (plan == null) {
             return Collections.emptyList();
@@ -339,6 +351,12 @@ public class PlanAligner extends BaseAligner {
         }
     }
 
-
-
+    private boolean isPlanContainPhaseType(final Plan plan, @Nullable final PhaseType phaseType) {
+        return Iterables.any(ImmutableList.copyOf(plan.getAllPhases()), new Predicate<PlanPhase>() {
+            @Override
+            public boolean apply(final PlanPhase input) {
+                return input.getPhaseType() == phaseType;
+            }
+        });
+    }
 }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index d7cc375..cead31c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -386,17 +386,19 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
     @Override
     public void updateChargedThroughDate(final DefaultSubscriptionBase subscription, final InternalCallContext context) {
+
         final Date ctd = (subscription.getChargedThroughDate() != null) ? subscription.getChargedThroughDate().toDate() : null;
+        final InternalCallContext contextWithUpdatedDate = contextWithUpdatedDate(context);
 
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
                 final SubscriptionSqlDao transactionalDao = entitySqlDaoWrapperFactory.become(SubscriptionSqlDao.class);
-                transactionalDao.updateChargedThroughDate(subscription.getId().toString(), ctd, context);
+                transactionalDao.updateChargedThroughDate(subscription.getId().toString(), ctd, contextWithUpdatedDate);
 
                 final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
                 final String bundleId = subscription.getBundleId().toString();
-                bundleSqlDao.updateBundleLastSysTime(bundleId, clock.getUTCNow().toDate(), context);
+                bundleSqlDao.updateBundleLastSysTime(bundleId, clock.getUTCNow().toDate(), contextWithUpdatedDate);
                 return null;
             }
         });
@@ -571,6 +573,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
     @Override
     public void uncancelSubscription(final DefaultSubscriptionBase subscription, final List<SubscriptionBaseEvent> uncancelEvents, final InternalCallContext context) {
+
+        final InternalCallContext contextWithUpdatedDate = contextWithUpdatedDate(context);
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
@@ -579,7 +583,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
                 final UUID subscriptionId = subscription.getId();
                 SubscriptionEventModelDao cancelledEvent = null;
                 final Date now = clock.getUTCNow().toDate();
-                final List<SubscriptionEventModelDao> eventModels = transactional.getFutureActiveEventForSubscription(subscriptionId.toString(), now, context);
+                final List<SubscriptionEventModelDao> eventModels = transactional.getFutureActiveEventForSubscription(subscriptionId.toString(), now, contextWithUpdatedDate);
 
                 for (final SubscriptionEventModelDao cur : eventModels) {
                     if (cur.getUserType() == ApiEventType.CANCEL) {
@@ -592,17 +596,17 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
                 if (cancelledEvent != null) {
                     final String cancelledEventId = cancelledEvent.getId().toString();
-                    transactional.unactiveEvent(cancelledEventId, context);
+                    transactional.unactiveEvent(cancelledEventId, contextWithUpdatedDate);
                     for (final SubscriptionBaseEvent cur : uncancelEvents) {
-                        transactional.create(new SubscriptionEventModelDao(cur), context);
+                        transactional.create(new SubscriptionEventModelDao(cur), contextWithUpdatedDate);
                         recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory,
                                                                 cur.getEffectiveDate(),
                                                                 new SubscriptionNotificationKey(cur.getId()),
-                                                                context);
+                                                                contextWithUpdatedDate);
                     }
 
                     // Notify the Bus of the latest requested change
-                    notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, uncancelEvents.get(uncancelEvents.size() - 1), SubscriptionBaseTransitionType.UNCANCEL, context);
+                    notifyBusOfRequestedChange(entitySqlDaoWrapperFactory, subscription, uncancelEvents.get(uncancelEvents.size() - 1), SubscriptionBaseTransitionType.UNCANCEL, contextWithUpdatedDate);
                 }
 
                 return null;
@@ -905,7 +909,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
             public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
 
                 final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
-                bundleSqlDao.updateBundleExternalKey(bundleId.toString(), externalKey, context);
+                bundleSqlDao.updateBundleExternalKey(bundleId.toString(), externalKey, contextWithUpdatedDate(context));
                 return null;
             }
         });
@@ -1042,4 +1046,8 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         return subscriptionWithNewEvent;
     }
 
+    private InternalCallContext contextWithUpdatedDate(final InternalCallContext input) {
+        return new InternalCallContext(input, clock.getUTCNow());
+    }
+
 }
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
index c85a7f0..4495409 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
@@ -30,7 +30,7 @@ update <tableName()>
 set
 last_sys_update_date = :lastSysUpdateDate
 , updated_by = :createdBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
@@ -41,7 +41,7 @@ update <tableName()>
 set
 external_key = :externalKey
 , updated_by = :createdBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
index 296083c..a1f8af9 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
@@ -43,22 +43,18 @@ tableValues() ::= <<
 , :updatedDate
 >>
 
-
 unactiveEvent() ::= <<
 update <tableName()>
 set
 is_active = false
 , updated_by = :createdBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where
 id = :id
 <AND_CHECK_TENANT()>
 ;
 >>
 
-
-
-
 getFutureActiveEventForSubscription() ::= <<
 select <allTableFields()>
 , record_id as total_ordering
@@ -70,7 +66,7 @@ and effective_date > :now
 <AND_CHECK_TENANT()>
 <defaultOrderBy()>
 ;
->> 
+>>
 
 getEventsForSubscription() ::= <<
 select <allTableFields()>
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg
index f644d2d..47ae310 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionSqlDao.sql.stg
@@ -44,9 +44,8 @@ update <tableName()>
 set
 charged_through_date = :chargedThroughDate
 , updated_by = :createdBy
-, updated_date = :createdDate
+, updated_date = :updatedDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
 >>
-
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
index 0b0188b..ac29706 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.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
@@ -25,8 +25,6 @@ import javax.inject.Inject;
 
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
@@ -46,10 +44,7 @@ public class JDBCSessionDao extends CachingSessionDAO {
 
     @Override
     protected void doUpdate(final Session session) {
-        // Assume only the last access time attribute was updated (see https://github.com/killbill/killbill/issues/326)
-        final DateTime lastAccessTime = new DateTime(session.getLastAccessTime(), DateTimeZone.UTC);
-        final Long sessionId = Long.valueOf(session.getId().toString());
-        jdbcSessionSqlDao.updateLastAccessTime(lastAccessTime, sessionId);
+        jdbcSessionSqlDao.update(new SessionModelDao(session));
     }
 
     @Override
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
index 68000d9..f8d9322 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.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
@@ -18,7 +18,6 @@
 
 package org.killbill.billing.util.security.shiro.dao;
 
-import org.joda.time.DateTime;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 import org.killbill.commons.jdbi.binder.SmartBindBean;
 import org.skife.jdbi.v2.sqlobject.Bind;
@@ -39,9 +38,6 @@ public interface JDBCSessionSqlDao extends Transactional<JDBCSessionSqlDao> {
     public void update(@SmartBindBean final SessionModelDao sessionModelDao);
 
     @SqlUpdate
-    public void updateLastAccessTime(@Bind("lastAccessTime") final DateTime lastAccessTime, @Bind("recordId") final Long sessionId);
-
-    @SqlUpdate
     public void delete(@SmartBindBean final SessionModelDao sessionModelDao);
 
     @SqlQuery
diff --git a/util/src/main/resources/org/killbill/billing/util/ddl.sql b/util/src/main/resources/org/killbill/billing/util/ddl.sql
index 3b67044..7cd9a43 100644
--- a/util/src/main/resources/org/killbill/billing/util/ddl.sql
+++ b/util/src/main/resources/org/killbill/billing/util/ddl.sql
@@ -7,7 +7,7 @@ CREATE TABLE custom_fields (
     object_id varchar(36) NOT NULL,
     object_type varchar(30) NOT NULL,
     is_active boolean default true,
-    field_name varchar(30) NOT NULL,
+    field_name varchar(64) NOT NULL,
     field_value varchar(255),
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
@@ -29,7 +29,7 @@ CREATE TABLE custom_field_history (
     object_id varchar(36) NOT NULL,
     object_type varchar(30) NOT NULL,
     is_active boolean default true,
-    field_name varchar(30),
+    field_name varchar(64),
     field_value varchar(255),
     change_type varchar(6) NOT NULL,
     created_by varchar(50) NOT NULL,
diff --git a/util/src/main/resources/org/killbill/billing/util/migration/V20160517224709__increase_field_name_length.sql b/util/src/main/resources/org/killbill/billing/util/migration/V20160517224709__increase_field_name_length.sql
new file mode 100644
index 0000000..f0fbcec
--- /dev/null
+++ b/util/src/main/resources/org/killbill/billing/util/migration/V20160517224709__increase_field_name_length.sql
@@ -0,0 +1,2 @@
+alter table custom_fields modify field_name varchar(64) NOT NULL;
+alter table custom_field_history modify field_name varchar(64);
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg
index 2599c29..fe228d2 100644
--- a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg
@@ -40,13 +40,6 @@ where record_id = :recordId
 ;
 >>
 
-updateLastAccessTime() ::= <<
-update sessions set
-  last_access_time = :lastAccessTime
-where record_id = :recordId
-;
->>
-
 delete() ::= <<
 delete from sessions
 where record_id = :recordId
diff --git a/util/src/test/java/org/killbill/billing/util/security/shiro/dao/TestJDBCSessionDao.java b/util/src/test/java/org/killbill/billing/util/security/shiro/dao/TestJDBCSessionDao.java
index df97ca8..c8da341 100644
--- a/util/src/test/java/org/killbill/billing/util/security/shiro/dao/TestJDBCSessionDao.java
+++ b/util/src/test/java/org/killbill/billing/util/security/shiro/dao/TestJDBCSessionDao.java
@@ -22,7 +22,6 @@ import java.util.UUID;
 
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.mgt.SimpleSession;
-import org.joda.time.DateTime;
 import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -57,11 +56,11 @@ public class TestJDBCSessionDao extends UtilTestSuiteWithEmbeddedDB {
         Assert.assertEquals(retrievedSession, session);
 
         // Update
-        final Date lastAccessTime = DateTime.now().withTimeAtStartOfDay().toDate(); // Milliseconds will be truncated
-        Assert.assertNotEquals(retrievedSession.getLastAccessTime(), lastAccessTime);
-        session.setLastAccessTime(lastAccessTime);
+        final String newHost = UUID.randomUUID().toString();
+        Assert.assertNotEquals(retrievedSession.getHost(), newHost);
+        session.setHost(newHost);
         jdbcSessionDao.doUpdate(session);
-        Assert.assertEquals(jdbcSessionDao.doReadSession(sessionId).getLastAccessTime().compareTo(lastAccessTime), 0);
+        Assert.assertEquals(jdbcSessionDao.doReadSession(sessionId).getHost(), newHost);
 
         // Delete
         jdbcSessionDao.doDelete(session);