killbill-memoizeit

See #351. Fixes #354

8/6/2015 7:10:50 PM

Details

diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
index 9e12621..8a93a68 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentAttemptTask.java
@@ -98,49 +98,67 @@ public class IncompletePaymentAttemptTask extends CompletionTaskBase<PaymentAtte
         final PaymentTransactionModelDao transaction = Iterables.tryFind(transactions, new Predicate<PaymentTransactionModelDao>() {
             @Override
             public boolean apply(final PaymentTransactionModelDao input) {
-                return input.getAttemptId().equals(attempt.getId()) &&
-                       input.getTransactionStatus() == TransactionStatus.SUCCESS;
+                return input.getAttemptId().equals(attempt.getId());
             }
         }).orNull();
 
-        if (transaction == null) {
+        // UNKNOWN transaction are handled by the Janitor IncompletePaymentTransactionTask  and should eventually transition to something else,
+        // at which point the attempt can also be transition to a different state.
+        if (transaction.getTransactionStatus() == TransactionStatus.UNKNOWN) {
+            return;
+        }
+
+        // In those 3 case null transaction, PLUGIN_FAILURE and PAYMENT_FAILURE, we are taking a *shortcut* but this is incorrect; ideally we should call back the priorCall
+        // control plugins to decide what to do:
+        // * For null transaction and PLUGIN_FAILURE something went wrong before we could even make the payment, so possibly we should inform the control plugin
+        //   and retry
+        // * For PAYMENT_FAILURE, the payment went through but was denied byt the gateway, and so this is a different case where a control plugin may want to retry
+        //
+        if (transaction == null ||
+            transaction.getTransactionStatus() == TransactionStatus.PLUGIN_FAILURE ||
+            transaction.getTransactionStatus() == TransactionStatus.PAYMENT_FAILURE) {
             log.info("Janitor AttemptCompletionTask moving attempt " + attempt.getId() + " -> ABORTED");
             paymentDao.updatePaymentAttempt(attempt.getId(), attempt.getTransactionId(), "ABORTED", internalCallContext);
             return;
         }
 
-        try {
-            log.info("Janitor AttemptCompletionTask completing attempt " + attempt.getId() + " -> SUCCESS");
-
-            final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
-            final boolean isApiPayment = true; // unclear
-            final PaymentStateControlContext paymentStateContext = new PaymentStateControlContext(attempt.toPaymentControlPluginNames(),
-                                                                                                  isApiPayment,
-                                                                                                  transaction.getPaymentId(),
-                                                                                                  attempt.getPaymentExternalKey(),
-                                                                                                  transaction.getTransactionExternalKey(),
-                                                                                                  transaction.getTransactionType(),
-                                                                                                  account,
-                                                                                                  attempt.getPaymentMethodId(),
-                                                                                                  transaction.getAmount(),
-                                                                                                  transaction.getCurrency(),
-                                                                                                  PluginPropertySerializer.deserialize(attempt.getPluginProperties()),
-                                                                                                  internalCallContext,
-                                                                                                  callContext);
-
-            paymentStateContext.setAttemptId(attempt.getId()); // Normally set by leavingState Callback
-            paymentStateContext.setPaymentTransactionModelDao(transaction); // Normally set by raw state machine
-            //
-            // Will rerun the state machine with special callbacks to only make the executePluginOnSuccessCalls call
-            // to the PaymentControlPluginApi plugin and transition the state.
-            //
-            pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
-        } catch (final AccountApiException e) {
-            log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
-        } catch (final PluginPropertySerializerException e) {
-            log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
-        } catch (final PaymentApiException e) {
-            log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+        // On SUCCESS, PENDING state we complete the payment control state machine, allowing to call the control plugin onSuccessCall API.
+        if (transaction.getTransactionStatus() == TransactionStatus.SUCCESS ||
+            transaction.getTransactionStatus() == TransactionStatus.PENDING) {
+
+            try {
+                log.info("Janitor AttemptCompletionTask completing attempt " + attempt.getId() + " -> SUCCESS");
+
+                final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
+                final boolean isApiPayment = true; // unclear
+                final PaymentStateControlContext paymentStateContext = new PaymentStateControlContext(attempt.toPaymentControlPluginNames(),
+                                                                                                      isApiPayment,
+                                                                                                      transaction.getPaymentId(),
+                                                                                                      attempt.getPaymentExternalKey(),
+                                                                                                      transaction.getTransactionExternalKey(),
+                                                                                                      transaction.getTransactionType(),
+                                                                                                      account,
+                                                                                                      attempt.getPaymentMethodId(),
+                                                                                                      transaction.getAmount(),
+                                                                                                      transaction.getCurrency(),
+                                                                                                      PluginPropertySerializer.deserialize(attempt.getPluginProperties()),
+                                                                                                      internalCallContext,
+                                                                                                      callContext);
+
+                paymentStateContext.setAttemptId(attempt.getId()); // Normally set by leavingState Callback
+                paymentStateContext.setPaymentTransactionModelDao(transaction); // Normally set by raw state machine
+                //
+                // Will rerun the state machine with special callbacks to only make the executePluginOnSuccessCalls call
+                // to the PaymentControlPluginApi plugin and transition the state.
+                //
+                pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
+            } catch (final AccountApiException e) {
+                log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+            } catch (final PluginPropertySerializerException e) {
+                log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+            } catch (final PaymentApiException e) {
+                log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+            }
         }
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
index 1c2f87c..de7730a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java
@@ -32,7 +32,12 @@ import org.killbill.automaton.OperationResult;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.DefaultCallContext;
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
 import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult;
+import org.killbill.billing.control.plugin.api.PaymentControlApiException;
+import org.killbill.billing.control.plugin.api.PaymentControlContext;
+import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
@@ -46,11 +51,6 @@ import org.killbill.billing.payment.core.sm.OperationCallbackBase;
 import org.killbill.billing.payment.core.sm.PaymentStateContext;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
-import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
-import org.killbill.billing.control.plugin.api.PaymentControlApiException;
-import org.killbill.billing.control.plugin.api.PaymentControlContext;
-import org.killbill.billing.control.plugin.api.PaymentControlPluginApi;
-import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
 import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.commons.locker.GlobalLocker;
@@ -229,8 +229,11 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
                     if (result.getAdjustedPluginProperties() != null) {
                         inputPluginProperties = result.getAdjustedPluginProperties();
                     }
+                    // Exceptions form the control plugins are ignored (and logged) because the semantics on what to do are undefined.
                 } catch (final PaymentControlApiException e) {
                     logger.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + paymentControlContext.getPaymentExternalKey(), e);
+                } catch (final RuntimeException e) {
+                    logger.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + paymentControlContext.getPaymentExternalKey(), e);
                 }
             }
         }