killbill-aplcache

payment: bail when attempting to complete a transaction in

6/23/2016 9:04:33 PM

Details

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 f9b3dde..f05b8d5 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
@@ -49,7 +49,9 @@ import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.janitor.IncompletePaymentTransactionTask;
+import org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper;
 import org.killbill.billing.payment.core.sm.PaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
 import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
@@ -70,6 +72,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -327,33 +330,117 @@ public class PaymentProcessor extends ProcessorBase {
                                   );
     }
 
-    private Payment performOperation(final boolean isApiPayment, @Nullable final UUID attemptId,
-                                     final TransactionType 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,
-                                     final boolean shouldLockAccountAndDispatch, @Nullable final OperationResult overridePluginOperationResult,
+    private Payment performOperation(final boolean isApiPayment,
+                                     @Nullable final UUID attemptId,
+                                     final TransactionType 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,
+                                     final boolean shouldLockAccountAndDispatch,
+                                     @Nullable final OperationResult overridePluginOperationResult,
                                      final Iterable<PluginProperty> properties,
-                                     final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
-        final UUID nonNullPaymentId = paymentAutomatonRunner.run(isApiPayment,
-                                                                 transactionType,
-                                                                 account,
-                                                                 attemptId,
-                                                                 paymentMethodId,
-                                                                 paymentId,
-                                                                 transactionId,
-                                                                 paymentExternalKey,
-                                                                 paymentTransactionExternalKey,
-                                                                 amount,
-                                                                 currency,
-                                                                 shouldLockAccountAndDispatch,
-                                                                 overridePluginOperationResult,
-                                                                 properties,
-                                                                 callContext,
-                                                                 internalCallContext);
+                                     final CallContext callContext,
+                                     final InternalCallContext internalCallContext) throws PaymentApiException {
+        final PaymentStateContext paymentStateContext = paymentAutomatonRunner.buildPaymentStateContext(isApiPayment,
+                                                                                                        transactionType,
+                                                                                                        account,
+                                                                                                        attemptId,
+                                                                                                        paymentMethodId != null ? paymentMethodId : account.getPaymentMethodId(),
+                                                                                                        paymentId,
+                                                                                                        transactionId,
+                                                                                                        paymentExternalKey,
+                                                                                                        paymentTransactionExternalKey,
+                                                                                                        amount,
+                                                                                                        currency,
+                                                                                                        shouldLockAccountAndDispatch,
+                                                                                                        overridePluginOperationResult,
+                                                                                                        properties,
+                                                                                                        callContext,
+                                                                                                        internalCallContext);
+        final PaymentAutomatonDAOHelper daoHelper = paymentAutomatonRunner.buildDaoHelper(paymentStateContext, internalCallContext);
+
+        String currentStateName = null;
+        if (paymentStateContext.getPaymentId() != null) {
+            if (paymentStateContext.getTransactionId() != null || paymentStateContext.getPaymentTransactionExternalKey() != null) {
+                // If a transaction id or key is passed, we are maybe completing an existing transaction (unless a new key was provided)
+                PaymentTransactionModelDao transactionToComplete = findTransactionToComplete(paymentStateContext, daoHelper);
+
+                if (transactionToComplete != null) {
+                    // For completion calls, always invoke the Janitor first to get the latest state. The state machine will then
+                    // prevent disallowed transitions in case the state couldn't be fixed (or if it's already in a final state).
+                    getPayment(paymentStateContext.getPaymentId(), true, properties, callContext, internalCallContext);
+
+                    // Get the latest state (TODO refactor Janitor code below to avoid extra DAO query)
+                    transactionToComplete = daoHelper.getPaymentDao().getPaymentTransaction(transactionToComplete.getId(), paymentStateContext.getInternalCallContext());
+
+                    // We can't tell where we should be in the state machine - bail (cannot be enforced by the state machine unfortunately because UNKNOWN and PLUGIN_FAILURE are both treated as EXCEPTION)
+                    if (transactionToComplete.getTransactionStatus() == TransactionStatus.UNKNOWN) {
+                        throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_OPERATION, paymentStateContext.getTransactionType(), transactionToComplete.getTransactionStatus());
+                    }
+
+                    paymentStateContext.setPaymentTransactionModelDao(transactionToComplete);
+                }
+            }
+
+            // Get the latest state of the payment (this will also throw a proper exception is a bogus paymentId was passed)
+            final PaymentModelDao paymentModelDao = daoHelper.getPayment();
+            // Use the original payment method id of the payment being completed
+            paymentStateContext.setPaymentMethodId(paymentModelDao.getPaymentMethodId());
+            // We always take the last successful state name to permit retries on failures
+            currentStateName = paymentModelDao.getLastSuccessStateName();
+        }
+
+        // Sanity: no paymentMethodId was passed through API and account does not have a default paymentMethodId
+        if (paymentStateContext.getPaymentMethodId() == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, paymentStateContext.getAccount().getId());
+        }
+
+        final UUID nonNullPaymentId = paymentAutomatonRunner.run(paymentStateContext, daoHelper, currentStateName, transactionType);
+
         return getPayment(nonNullPaymentId, true, properties, callContext, internalCallContext);
     }
 
+    private PaymentTransactionModelDao findTransactionToComplete(final PaymentStateContext paymentStateContext, final PaymentAutomatonDAOHelper daoHelper) throws PaymentApiException {
+        final List<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment = daoHelper.getPaymentDao().getTransactionsForPayment(paymentStateContext.getPaymentId(), paymentStateContext.getInternalCallContext());
+
+        final Collection<PaymentTransactionModelDao> completionCandidates = new LinkedList<PaymentTransactionModelDao>();
+        for (final PaymentTransactionModelDao paymentTransactionModelDao : paymentTransactionsForCurrentPayment) {
+            // Check if we already have a transaction for that id or key
+            if (!(paymentStateContext.getTransactionId() != null && paymentTransactionModelDao.getId().equals(paymentStateContext.getTransactionId())) &&
+                !(paymentStateContext.getPaymentTransactionExternalKey() != null && paymentTransactionModelDao.getTransactionExternalKey().equals(paymentStateContext.getPaymentTransactionExternalKey()))) {
+                // Sanity: if not, prevent multiple PENDING transactions for initial calls (cannot be enforced by the state machine unfortunately)
+                if ((paymentTransactionModelDao.getTransactionType() == TransactionType.AUTHORIZE ||
+                     paymentTransactionModelDao.getTransactionType() == TransactionType.PURCHASE ||
+                     paymentTransactionModelDao.getTransactionType() == TransactionType.CREDIT) &&
+                    paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PENDING) {
+                    final PaymentModelDao paymentModelDao = daoHelper.getPayment();
+                    throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_OPERATION, paymentTransactionModelDao.getTransactionType(), paymentModelDao.getStateName());
+                } else {
+                    continue;
+                }
+            }
+
+            // Sanity: if we already have a transaction for that id or key, the transaction type must match
+            if (paymentTransactionModelDao.getTransactionType() != paymentStateContext.getTransactionType()) {
+                final PaymentModelDao paymentModelDao = daoHelper.getPayment();
+                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_OPERATION, paymentStateContext.getTransactionType(), paymentModelDao.getStateName());
+            }
+
+            // UNKNOWN transactions are potential candidates, we'll invoke the Janitor first though
+            if (paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PENDING || paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.UNKNOWN) {
+                completionCandidates.add(paymentTransactionModelDao);
+            }
+        }
+
+        Preconditions.checkState(Iterables.<PaymentTransactionModelDao>size(completionCandidates) <= 1, "There should be at most one completion candidate");
+        return Iterables.<PaymentTransactionModelDao>getLast(completionCandidates, null);
+    }
+
     // Used in bulk get API (getAccountPayments / getPayments)
     private List<PaymentTransactionInfoPlugin> getPaymentTransactionInfoPluginsIfNeeded(@Nullable final PaymentPluginApi pluginApi, final PaymentModelDao paymentModelDao, final TenantContext context) {
         if (pluginApi == null) {
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 83bf790..957ecd1 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
@@ -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
@@ -67,7 +67,6 @@ import org.killbill.billing.payment.core.sm.payments.VoidOperation;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.config.PaymentConfig;
@@ -75,10 +74,7 @@ import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 
-import com.google.common.base.Objects;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
+import com.google.common.base.MoreObjects;
 
 public class PaymentAutomatonRunner {
 
@@ -88,6 +84,7 @@ public class PaymentAutomatonRunner {
     protected final PluginDispatcher<OperationResult> paymentPluginDispatcher;
     protected final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
     protected final Clock clock;
+
     private final PersistentBus eventBus;
     private final PaymentConfig paymentConfig;
 
@@ -109,42 +106,57 @@ public class PaymentAutomatonRunner {
         this.paymentConfig = paymentConfig;
         final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
         this.paymentPluginDispatcher = new PluginDispatcher<OperationResult>(paymentPluginTimeoutSec, executors);
-
     }
 
-    public UUID run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID attemptId, @Nullable final UUID paymentMethodId,
-                    @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
-                    @Nullable final BigDecimal amount, @Nullable final Currency currency,
-                    final boolean shouldLockAccount, final OperationResult overridePluginOperationResult, final Iterable<PluginProperty> properties,
-                    final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
-        final DateTime utcNow = clock.getUTCNow();
-
+    public PaymentStateContext buildPaymentStateContext(final boolean isApiPayment,
+                                                        final TransactionType transactionType,
+                                                        final Account account,
+                                                        @Nullable final UUID attemptId,
+                                                        @Nullable final UUID paymentMethodId,
+                                                        @Nullable final UUID paymentId,
+                                                        @Nullable final UUID transactionId,
+                                                        @Nullable final String paymentExternalKey,
+                                                        final String paymentTransactionExternalKey,
+                                                        @Nullable final BigDecimal amount,
+                                                        @Nullable final Currency currency,
+                                                        final boolean shouldLockAccount,
+                                                        final OperationResult overridePluginOperationResult,
+                                                        final Iterable<PluginProperty> properties,
+                                                        final CallContext callContext,
+                                                        final InternalCallContext internalCallContext) throws PaymentApiException {
         // Retrieve the payment id from the payment external key if needed
         final UUID effectivePaymentId = paymentId != null ? paymentId : retrievePaymentId(paymentExternalKey, internalCallContext);
 
-        final PaymentStateContext paymentStateContext = new PaymentStateContext(isApiPayment, effectivePaymentId, transactionId, attemptId, paymentExternalKey, paymentTransactionExternalKey, transactionType,
-                                                                                account, paymentMethodId, amount, currency, shouldLockAccount, overridePluginOperationResult, properties, internalCallContext, callContext);
-
-        final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, utcNow, paymentDao, pluginRegistry, internalCallContext, eventBus, paymentSMHelper);
-
-        final UUID effectivePaymentMethodId;
-        final String currentStateName;
-        if (effectivePaymentId != null) {
-            final PaymentModelDao paymentModelDao = daoHelper.getPayment();
-            effectivePaymentMethodId = paymentModelDao.getPaymentMethodId();
-            currentStateName = paymentModelDao.getLastSuccessStateName() != null ? paymentModelDao.getLastSuccessStateName() : paymentSMHelper.getInitStateNameForTransaction();
-
-            // Check for illegal states (should never happen)
-            Preconditions.checkState(currentStateName != null, "State name cannot be null for payment " + effectivePaymentId);
-            Preconditions.checkState(paymentMethodId == null || effectivePaymentMethodId.equals(paymentMethodId), "Specified payment method id " + paymentMethodId + " doesn't match the one on the payment " + effectivePaymentMethodId);
-        } else {
-            // If the payment method is not specified, retrieve the default one on the account; it could still be null, in which case
-            //
-            effectivePaymentMethodId = paymentMethodId != null ? paymentMethodId : account.getPaymentMethodId();
-            currentStateName = paymentSMHelper.getInitStateNameForTransaction();
-        }
+        return new PaymentStateContext(isApiPayment,
+                                       effectivePaymentId,
+                                       transactionId,
+                                       attemptId,
+                                       paymentExternalKey,
+                                       paymentTransactionExternalKey,
+                                       transactionType,
+                                       account,
+                                       paymentMethodId,
+                                       amount,
+                                       currency,
+                                       shouldLockAccount,
+                                       overridePluginOperationResult,
+                                       properties,
+                                       internalCallContext,
+                                       callContext);
+    }
+
+    public PaymentAutomatonDAOHelper buildDaoHelper(final PaymentStateContext paymentStateContext,
+                                                    final InternalCallContext internalCallContext) throws PaymentApiException {
+        final DateTime utcNow = clock.getUTCNow();
+
+        return new PaymentAutomatonDAOHelper(paymentStateContext, utcNow, paymentDao, pluginRegistry, internalCallContext, eventBus, paymentSMHelper);
+    }
 
-        paymentStateContext.setPaymentMethodId(effectivePaymentMethodId);
+    public UUID run(final PaymentStateContext paymentStateContext,
+                    final PaymentAutomatonDAOHelper daoHelper,
+                    @Nullable final String currentStateNameOrNull,
+                    final TransactionType transactionType) throws PaymentApiException {
+        final String currentStateName = MoreObjects.firstNonNull(currentStateNameOrNull, paymentSMHelper.getInitStateNameForTransaction());
 
         final OperationCallback operationCallback;
         final LeavingStateCallback leavingStateCallback;
@@ -205,8 +217,11 @@ public class PaymentAutomatonRunner {
         return clock;
     }
 
-    protected void runStateMachineOperation(final String initialStateName, final TransactionType transactionType,
-                                            final LeavingStateCallback leavingStateCallback, final OperationCallback operationCallback, final EnteringStateCallback enteringStateCallback) throws PaymentApiException {
+    private void runStateMachineOperation(final String initialStateName,
+                                          final TransactionType transactionType,
+                                          final LeavingStateCallback leavingStateCallback,
+                                          final OperationCallback operationCallback,
+                                          final EnteringStateCallback enteringStateCallback) throws PaymentApiException {
         try {
             final StateMachine initialStateMachine = paymentSMHelper.getStateMachineForStateName(initialStateName);
             final State initialState = initialStateMachine.getState(initialStateName);
@@ -217,11 +232,11 @@ public class PaymentAutomatonRunner {
             throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INVALID_OPERATION, transactionType, initialStateName);
         } catch (final OperationException e) {
             if (e.getCause() == null) {
-                throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+                throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
             } else if (e.getCause() instanceof PaymentApiException) {
                 throw (PaymentApiException) e.getCause();
             } else {
-                throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+                throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.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 0b08255..5e07f04 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
@@ -17,7 +17,14 @@
 
 package org.killbill.billing.payment.core.sm.payments;
 
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.automaton.OperationException;
 import org.killbill.automaton.OperationResult;
+import org.killbill.automaton.State;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper;
@@ -25,6 +32,7 @@ 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.ImmutableList;
 import com.google.common.collect.Iterables;
 
 public class ChargebackInitiated extends PaymentLeavingStateCallback {
@@ -34,33 +42,35 @@ public class ChargebackInitiated extends PaymentLeavingStateCallback {
     }
 
     @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);
-    }
+    public void leavingState(final State oldState) throws OperationException {
+        // Sanity: chargeback reversals can only happen after a successful chargeback
+        if (OperationResult.FAILURE.equals(paymentStateContext.getOverridePluginOperationResult())) {
+            final List<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment = paymentStateContext.getPaymentId() != null ?
+                                                                                          daoHelper.getPaymentDao().getTransactionsForPayment(paymentStateContext.getPaymentId(), paymentStateContext.getInternalCallContext()) :
+                                                                                          ImmutableList.<PaymentTransactionModelDao>of();
+            final Iterable<PaymentTransactionModelDao> existingPaymentTransactionsForTransactionIdOrKey = filterExistingPaymentTransactionsForTransactionIdOrKey(paymentTransactionsForCurrentPayment, paymentStateContext.getTransactionId(), paymentStateContext.getPaymentTransactionExternalKey());
 
-    @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;
+            if (Iterables.<PaymentTransactionModelDao>isEmpty(existingPaymentTransactionsForTransactionIdOrKey)) {
+                // Chargeback reversals can only happen after a successful chargeback
+                throw new OperationException(new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_SUCCESS_PAYMENT, paymentStateContext.getPaymentId()));
+            }
         }
 
-        // 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>() {
+        super.leavingState(oldState);
+    }
+
+    private Iterable<PaymentTransactionModelDao> filterExistingPaymentTransactionsForTransactionIdOrKey(final Iterable<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment, @Nullable final UUID paymentTransactionId, @Nullable final String paymentTransactionExternalKey) {
+        return Iterables.filter(paymentTransactionsForCurrentPayment, 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()));
-
+                if (paymentTransactionId != null && input.getId().equals(paymentTransactionId)) {
+                    return true;
+                }
+                if (paymentTransactionExternalKey != null && input.getTransactionExternalKey().equals(paymentTransactionExternalKey)) {
+                    return true;
+                }
+                return false;
             }
-        })) {
-            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/PaymentLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java
index 831b349..90f8599 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentLeavingStateCallback.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
  *
  * Groupon 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,31 +17,19 @@
 
 package org.killbill.billing.payment.core.sm.payments;
 
-import java.util.List;
-import java.util.UUID;
-
-import javax.annotation.Nullable;
-
 import org.killbill.automaton.OperationException;
 import org.killbill.automaton.State;
 import org.killbill.automaton.State.LeavingStateCallback;
-import org.killbill.billing.ErrorCode;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.TransactionStatus;
-import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper;
 import org.killbill.billing.payment.core.sm.PaymentStateContext;
-import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-
 public abstract class PaymentLeavingStateCallback implements LeavingStateCallback {
 
-    private final Logger logger = LoggerFactory.getLogger(PaymentLeavingStateCallback.class);
+    private static final Logger logger = LoggerFactory.getLogger(PaymentLeavingStateCallback.class);
 
     protected final PaymentAutomatonDAOHelper daoHelper;
     protected final PaymentStateContext paymentStateContext;
@@ -55,143 +43,13 @@ public abstract class PaymentLeavingStateCallback implements LeavingStateCallbac
     public void leavingState(final State oldState) throws OperationException {
         logger.debug("Leaving state {}", oldState.getName());
 
-        // Create or update the payment and transaction
         try {
-            // No paymentMethodId was passed through API and account does not have a default paymentMethodId
-            if (paymentStateContext.getPaymentMethodId() == null) {
-                throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, paymentStateContext.getAccount().getId());
+            // We always create a new transaction (even in case of PAYMENT_FAILURE or PLUGIN_FAILURE) except for PENDING payments (completion)
+            if (paymentStateContext.getPaymentTransactionModelDao() == null || paymentStateContext.getPaymentTransactionModelDao().getTransactionStatus() != TransactionStatus.PENDING) {
+                daoHelper.createNewPaymentTransaction();
             }
-
-            // If we were given a paymentId (or existing paymentExternalId -> effectivePaymentId) we first fetch existing transactions (required for sanity and handling PENDING transactions)
-            final List<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment = paymentStateContext.getPaymentId() != null ?
-                                                                                          daoHelper.getPaymentDao().getTransactionsForPayment(paymentStateContext.getPaymentId(), paymentStateContext.getInternalCallContext()) :
-                                                                                          ImmutableList.<PaymentTransactionModelDao>of();
-
-            //
-            // Extract existing transaction matching the transactionId if specified (for e.g notifyPendingTransactionOfStateChanged), or based on transactionExternalKey
-            //
-            final Iterable<PaymentTransactionModelDao> existingPaymentTransactionsForTransactionIdOrKey = filterExistingPaymentTransactionsForTransactionIdOrKey(paymentTransactionsForCurrentPayment, paymentStateContext.getTransactionId(), paymentStateContext.getPaymentTransactionExternalKey());
-
-            // Validate the payment transactions belong to the right payment
-            validatePaymentIdAndTransactionType(existingPaymentTransactionsForTransactionIdOrKey);
-
-            // Validate some constraints on the unicity of that paymentTransactionExternalKey
-            validateUniqueTransactionExternalKey(existingPaymentTransactionsForTransactionIdOrKey);
-
-            //
-            // Handle PENDING case:
-            // a) If we have a PENDING transaction for the same (payment transaction) key, this is a completion and we want to re-use the same transaction
-            // b) If we have a PENDING transaction for a different (payment transaction) key, and for an initial request (AUTH, PURCHASE, CREDIT), we FAIL the request
-            //   (unfortunately this cannot be caught by the state machine because the transition XXX_PENDING -> _SUCCESS needs to be allowed and this is irrespective of the keys)
-            // c) If we have a PENDING transaction for a different (payment transaction) key, and for other follow-up request  (CAPTURE, REFUND, ..), we ignore it and create a new transaction
-            //
-            final Iterable<PaymentTransactionModelDao> pendingTransactionsForPaymentAndTransactionType = filterPendingTransactionsForPaymentAndTransactionType(paymentTransactionsForCurrentPayment, paymentStateContext.getTransactionType());
-
-            // Case b)
-            validateUniqueInitialPendingTransaction(pendingTransactionsForPaymentAndTransactionType, paymentStateContext.getTransactionType(), paymentStateContext.getPaymentTransactionExternalKey());
-
-
-            final PaymentTransactionModelDao pendingPaymentTransaction = filterPendingTransactionsForTransactionKey(pendingTransactionsForPaymentAndTransactionType, paymentStateContext.getPaymentTransactionExternalKey());
-            if (pendingPaymentTransaction != null) {
-                // Case a) Set the current paymentTransaction in the context (needed for the state machine logic)
-                paymentStateContext.setPaymentTransactionModelDao(pendingPaymentTransaction);
-                return;
-            }
-
-            // At this point we are left with PAYMENT_FAILURE, PLUGIN_FAILURE or nothing, and we validated the uniqueness of the paymentTransactionExternalKey so we will create a new row
-            daoHelper.createNewPaymentTransaction();
-
-        } catch (PaymentApiException e) {
+        } catch (final PaymentApiException e) {
             throw new OperationException(e);
         }
     }
-
-    private void validateUniqueInitialPendingTransaction(final Iterable<PaymentTransactionModelDao> pendingTransactionsForPaymentAndTransactionType, final TransactionType transactionType, final String paymentTransactionExternalKey) {
-        if (transactionType != TransactionType.AUTHORIZE &&
-            transactionType != TransactionType.PURCHASE &&
-            transactionType != TransactionType.CREDIT) {
-            return;
-        }
-
-        final PaymentTransactionModelDao existingPendingTransactionForDifferentKey = Iterables.tryFind(pendingTransactionsForPaymentAndTransactionType, new Predicate<PaymentTransactionModelDao>() {
-            @Override
-            public boolean apply(final PaymentTransactionModelDao input) {
-                return !input.getTransactionExternalKey().equals(paymentTransactionExternalKey);
-            }
-        }).orNull();
-        if (existingPendingTransactionForDifferentKey !=  null) {
-            // We are missing ErrorCode PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS (should be fixed in 0.17.0. See #525)
-            throw new RuntimeException(String.format("Failed to create another initial transaction for paymentId='%s' : Existing PENDING transactionId='%s'",
-                                                          existingPendingTransactionForDifferentKey.getPaymentId(), existingPendingTransactionForDifferentKey.getId()));
-        }
-    }
-
-    protected Iterable<PaymentTransactionModelDao> filterExistingPaymentTransactionsForTransactionIdOrKey(final List<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment, @Nullable final UUID paymentTransactionId, @Nullable final String paymentTransactionExternalKey) throws PaymentApiException {
-        return Iterables.filter(paymentTransactionsForCurrentPayment, new Predicate<PaymentTransactionModelDao>() {
-            @Override
-            public boolean apply(final PaymentTransactionModelDao input) {
-                if (paymentTransactionId != null && input.getId().equals(paymentTransactionId)) {
-                    return true;
-                }
-                if (paymentTransactionExternalKey != null && input.getTransactionExternalKey().equals(paymentTransactionExternalKey)) {
-                    return true;
-                }
-                return false;
-            }
-        });
-    }
-
-    protected Iterable<PaymentTransactionModelDao> filterPendingTransactionsForPaymentAndTransactionType(final Iterable<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment, final TransactionType transactionType) throws PaymentApiException {
-        return Iterables.filter(paymentTransactionsForCurrentPayment, new Predicate<PaymentTransactionModelDao>() {
-            @Override
-            public boolean apply(final PaymentTransactionModelDao input) {
-                return input.getTransactionStatus() == TransactionStatus.PENDING &&
-                       input.getTransactionType() == transactionType;
-            }
-        });
-    }
-
-    protected PaymentTransactionModelDao filterPendingTransactionsForTransactionKey(final Iterable<PaymentTransactionModelDao> existingPendingPaymentTransactions, final String paymentTransactionExternalKey) throws PaymentApiException {
-        return Iterables.tryFind(existingPendingPaymentTransactions, new Predicate<PaymentTransactionModelDao>() {
-            @Override
-            public boolean apply(final PaymentTransactionModelDao input) {
-                return input.getTransactionExternalKey().equals(paymentTransactionExternalKey);
-            }
-        }).orNull();
-    }
-
-    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;
-        }
-
-        if (Iterables.any(existingPaymentTransactions, new Predicate<PaymentTransactionModelDao>() {
-            @Override
-            public boolean apply(final PaymentTransactionModelDao input) {
-                // An existing transaction in a SUCCESS state
-                return input.getTransactionStatus() == TransactionStatus.SUCCESS ||
-                       // Or, an existing transaction for a different payment (to do really well, we should also check on paymentExternalKey which is not available here)
-                       (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());
-        }
-    }
-
-    // At this point, the payment id should have been populated for follow-up transactions (see PaymentAutomationRunner#run)
-    protected void validatePaymentIdAndTransactionType(final Iterable<PaymentTransactionModelDao> existingPaymentTransactions) throws PaymentApiException {
-        for (final PaymentTransactionModelDao paymentTransactionModelDao : existingPaymentTransactions) {
-            if (!paymentTransactionModelDao.getPaymentId().equals(paymentStateContext.getPaymentId())) {
-                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, paymentTransactionModelDao.getId(), "does not belong to payment " + paymentStateContext.getPaymentId());
-            }
-            if (paymentStateContext.getTransactionType() != null && paymentTransactionModelDao.getTransactionType() != paymentStateContext.getTransactionType()) {
-                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, paymentTransactionModelDao.getId(), "has a transaction type of " + paymentTransactionModelDao.getTransactionType() +
-                                                                                                                       " instead of requested " + paymentStateContext.getTransactionType());
-            }
-        }
-    }
 }
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 c9aadbe..a90154f 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
@@ -1354,16 +1354,9 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         final String paymentExternalKey = "rouge";
         final String transactionExternalKey = "vert";
 
-        final Payment initialPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
-                                                                      ImmutableList.<PluginProperty>of(), callContext);
+        final Payment initialPayment = createPayment(TransactionType.AUTHORIZE, null, paymentExternalKey, transactionExternalKey, authAmount, PaymentPluginStatus.PENDING);
 
-        // Update the payment/transaction by hand to simulate a PENDING state.
-        final PaymentTransaction paymentTransaction = initialPayment.getTransactions().get(0);
-        paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), null, initialPayment.getId(), TransactionType.AUTHORIZE, "AUTH_PENDING", "AUTH_PENDING",
-                                                           paymentTransaction.getId(), TransactionStatus.PENDING, paymentTransaction.getProcessedAmount(), paymentTransaction.getProcessedCurrency(),
-                                                           null, null, internalCallContext);
-
-        final Payment payment = paymentApi.notifyPendingTransactionOfStateChanged(account, paymentTransaction.getId(), true, callContext);
+        final Payment payment = paymentApi.notifyPendingTransactionOfStateChanged(account, initialPayment.getTransactions().get(0).getId(), true, callContext);
 
         assertEquals(payment.getExternalKey(), paymentExternalKey);
         assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
@@ -1372,15 +1365,15 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
         assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
-        assertEquals(payment.getCurrency(), Currency.AED);
+        assertEquals(payment.getCurrency(), Currency.USD);
 
         assertEquals(payment.getTransactions().size(), 1);
         assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
         assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
         assertEquals(payment.getTransactions().get(0).getAmount().compareTo(authAmount), 0);
-        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.AED);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.USD);
         assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(authAmount), 0);
-        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.AED);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.USD);
 
         assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
         assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.AUTHORIZE);
@@ -1581,7 +1574,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
             createPayment(TransactionType.PURCHASE, null, paymentExternalKey, transactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
             Assert.fail("PURCHASE transaction with same key should have failed");
         } catch (final PaymentApiException expected) {
-            Assert.assertEquals(expected.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
+            Assert.assertEquals(expected.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
         }
     }
 
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java
index ec77da2..4c8688e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiWithControl.java
@@ -21,6 +21,7 @@ import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
+import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
@@ -147,18 +148,19 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
         Assert.assertEquals(payment.getTransactions().size(), 1);
         Assert.assertNotNull(((DefaultPaymentTransaction) payment.getTransactions().get(0)).getAttemptId());
 
-        payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
-                                                                   payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
-        Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
+        try {
+            payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
+                                                                       payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+        }
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
         Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
-        // TODO A second transaction is created - seems like a bug
-        Assert.assertEquals(payment.getTransactions().size(), 2);
+        Assert.assertEquals(payment.getTransactions().size(), 1);
         Assert.assertNotNull(((DefaultPaymentTransaction) payment.getTransactions().get(0)).getAttemptId());
         Assert.assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.UNKNOWN);
         Assert.assertEquals(payment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey);
-        Assert.assertNotNull(((DefaultPaymentTransaction) payment.getTransactions().get(1)).getAttemptId());
-        Assert.assertEquals(payment.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
-        Assert.assertEquals(payment.getTransactions().get(1).getExternalKey(), paymentTransactionExternalKey);
     }
 
     @Test(groups = "slow")
@@ -196,18 +198,19 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
         Assert.assertEquals(payment.getTransactions().size(), 1);
         Assert.assertNotNull(((DefaultPaymentTransaction) payment.getTransactions().get(0)).getAttemptId());
 
-        payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
-                                                 payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), callContext);
-        Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
+        try {
+            payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
+                                                     payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), callContext);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+        }
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
         Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
-        // TODO A second transaction is created - seems like a bug
-        Assert.assertEquals(payment.getTransactions().size(), 2);
+        Assert.assertEquals(payment.getTransactions().size(), 1);
         Assert.assertNotNull(((DefaultPaymentTransaction) payment.getTransactions().get(0)).getAttemptId());
         Assert.assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.UNKNOWN);
         Assert.assertEquals(payment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey);
-        Assert.assertNull(((DefaultPaymentTransaction) payment.getTransactions().get(1)).getAttemptId());
-        Assert.assertEquals(payment.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
-        Assert.assertEquals(payment.getTransactions().get(1).getExternalKey(), paymentTransactionExternalKey);
     }
 
     @Test(groups = "slow")
@@ -245,18 +248,19 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
         Assert.assertEquals(payment.getTransactions().size(), 1);
         Assert.assertNull(((DefaultPaymentTransaction) payment.getTransactions().get(0)).getAttemptId());
 
-        payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
-                                                                   payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
-        Assert.assertEquals(payment.getAuthAmount().compareTo(requestedAmount), 0);
+        try {
+            payment = paymentApi.createAuthorizationWithPaymentControl(account, payment.getPaymentMethodId(), payment.getId(), requestedAmount, payment.getCurrency(), payment.getExternalKey(),
+                                                                       payment.getTransactions().get(0).getExternalKey(), ImmutableList.<PluginProperty>of(), PAYMENT_OPTIONS, callContext);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+        }
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
         Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
-        // TODO A second transaction is created - seems like a bug
-        Assert.assertEquals(payment.getTransactions().size(), 2);
+        Assert.assertEquals(payment.getTransactions().size(), 1);
         Assert.assertNull(((DefaultPaymentTransaction) payment.getTransactions().get(0)).getAttemptId());
         Assert.assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.UNKNOWN);
         Assert.assertEquals(payment.getTransactions().get(0).getExternalKey(), paymentTransactionExternalKey);
-        Assert.assertNotNull(((DefaultPaymentTransaction) payment.getTransactions().get(1)).getAttemptId());
-        Assert.assertEquals(payment.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
-        Assert.assertEquals(payment.getTransactions().get(1).getExternalKey(), paymentTransactionExternalKey);
     }
 
     @Test(groups = "slow")
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java
index ffb44d2..38a1e9b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java
@@ -88,61 +88,6 @@ public class TestPaymentLeavingStateCallback extends PaymentTestSuiteWithEmbedde
         Assert.assertEquals(paymentDao.getTransactionsForPayment(paymentId, internalCallContext).size(), 2);
     }
 
-    @Test(groups = "slow", expectedExceptions = OperationException.class)
-    public void testLeaveStateForConflictingPaymentTransactionExternalKey() throws Exception {
-        final UUID paymentId = UUID.randomUUID();
-        setUp(paymentId);
-
-        // Verify the payment has only one transaction
-        final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(paymentId, internalCallContext);
-        Assert.assertEquals(transactions.size(), 1);
-
-        final String paymentStateName = paymentSMHelper.getErroredStateForTransaction(TransactionType.CAPTURE).toString();
-        paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), transactions.get(0).getAttemptId(), paymentId, TransactionType.AUTHORIZE, paymentStateName, paymentStateName,
-                                                           transactions.get(0).getId(), TransactionStatus.SUCCESS, BigDecimal.ONE, Currency.BRL,
-                                                           "foo", "bar", internalCallContext);
-
-        // Will validate the validateUniqueTransactionExternalKey logic for when we reuse the same payment transactionExternalKey
-        callback.leavingState(state);
-
-    }
-
-    @Test(groups = "slow", expectedExceptions = OperationException.class)
-    public void testLeaveStateForConflictingPaymentTransactionExternalKeyAcrossAccounts() throws Exception {
-        final UUID paymentId = UUID.randomUUID();
-        setUp(paymentId);
-
-        // Verify the payment has only one transaction
-        final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(paymentId, internalCallContext);
-        Assert.assertEquals(transactions.size(), 1);
-
-        final String paymentStateName = paymentSMHelper.getErroredStateForTransaction(TransactionType.CAPTURE).toString();
-        paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), transactions.get(0).getAttemptId(), paymentId, TransactionType.AUTHORIZE, paymentStateName, paymentStateName,
-                                                           transactions.get(0).getId(), TransactionStatus.SUCCESS, BigDecimal.ONE, Currency.BRL,
-                                                           "foo", "bar", internalCallContext);
-
-        internalCallContext.setAccountRecordId(123L);
-
-        paymentStateContext = new PaymentStateContext(true,
-                                                      paymentId,
-                                                      null,
-                                                      null,
-                                                      paymentStateContext.getPaymentExternalKey(),
-                                                      paymentStateContext.getPaymentTransactionExternalKey(),
-                                                      paymentStateContext.getTransactionType(),
-                                                      paymentStateContext.getAccount(),
-                                                      paymentStateContext.getPaymentMethodId(),
-                                                      paymentStateContext.getAmount(),
-                                                      paymentStateContext.getCurrency(),
-                                                      paymentStateContext.shouldLockAccountAndDispatch(),
-                                                      paymentStateContext.getOverridePluginOperationResult(),
-                                                      paymentStateContext.getProperties(),
-                                                      internalCallContext,
-                                                      callContext);
-
-        callback.leavingState(state);
-    }
-
     private void verifyPaymentTransaction() {
         Assert.assertNotNull(paymentStateContext.getPaymentTransactionModelDao().getPaymentId());
         Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getTransactionExternalKey(), paymentStateContext.getPaymentTransactionExternalKey());