killbill-memoizeit

Details

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 2f714dd..e27495f 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/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 385f2e2..9f2d5b6 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
@@ -45,6 +45,7 @@ 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 {
 
@@ -663,6 +664,46 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         }
     }
 
+    //@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");
+
+        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
     public List<Payment> getAccountPayments(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentApiException {
         return paymentProcessor.getAccountPayments(accountId, withPluginInfo, tenantContext, internalCallContextFactory.createInternalTenantContext(accountId, tenantContext));
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 1a92573..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) {
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..0b08255 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,50 @@
 
 package org.killbill.billing.payment.core.sm.payments;
 
+import org.killbill.automaton.OperationResult;
+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 validatePaymentIdAndTransactionType(final Iterable<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
+        if (OperationResult.FAILURE.equals(paymentStateContext.getOverridePluginOperationResult()) && !existingPaymentTransactions.iterator().hasNext()) {
+            // Chargeback reversals can only happen after a successful chargeback
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_SUCCESS_PAYMENT, paymentStateContext.getPaymentId());
+        }
+        super.validatePaymentIdAndTransactionType(existingPaymentTransactions);
+    }
+
+    @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/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/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 ac5ec07..a00a2c2 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
@@ -1046,51 +1046,304 @@ 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")
+    public void testCreateChargebackReversalBeforeChargeback() 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);
+
+        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);
+
+        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(), "");
+
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getStateName(), "PURCHASE_SUCCESS");
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getLastSuccessStateName(), "PURCHASE_SUCCESS");
+
+        try {
+            paymentApi.createChargebackReversal(account,
+                                                payment.getId(),
+                                                chargebackTransactionExternalKey,
+                                                callContext);
+            Assert.fail("Chargeback reversals are not permitted before a chargeback");
+        } catch (final PaymentApiException e) {
+            assertEquals(e.getCode(), ErrorCode.PAYMENT_NO_SUCH_SUCCESS_PAYMENT.getCode());
+        }
+
+        assertEquals(paymentApi.getPayment(payment.getId(), false, ImmutableList.<PluginProperty>of(), callContext).getTransactions().size(), 1);
+
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getStateName(), "PURCHASE_SUCCESS");
+        assertEquals(paymentDao.getPayment(payment.getId(), internalCallContext).getLastSuccessStateName(), "PURCHASE_SUCCESS");
     }
 
     @Test(groups = "slow")