killbill-aplcache
Changes
payment/src/main/java/org/killbill/billing/payment/core/sm/payments/ChargebackInitiated.java 54(+32 -22)
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());