killbill-memoizeit

payment: allow updatePaymentAndTransactionOnCompletion

3/2/2018 7:22:40 AM

Details

diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultAdminPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultAdminPaymentApi.java
index 9ebad50..c5bd576 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultAdminPaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultAdminPaymentApi.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
index 6887cc5..1e101e1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/janitor/IncompletePaymentTransactionTask.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -229,9 +229,6 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
             return false;
         }
 
-        // Recompute new lastSuccessPaymentState. This is important to be able to allow new operations on the state machine (for e.g an AUTH_SUCCESS would now allow a CAPTURE operation)
-        final String lastSuccessPaymentState = paymentStateMachineHelper.isSuccessState(newPaymentState) ? newPaymentState : null;
-
         // Update processedAmount and processedCurrency
         final BigDecimal processedAmount;
         if (TransactionStatus.SUCCESS.equals(transactionStatus) || TransactionStatus.PENDING.equals(transactionStatus)) {
@@ -258,11 +255,18 @@ public class IncompletePaymentTransactionTask extends CompletionTaskBase<Payment
                  payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(payment.getAccountId(), callContext);
-        paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(), paymentTransaction.getAttemptId(), payment.getId(), paymentTransaction.getTransactionType(), newPaymentState, lastSuccessPaymentState,
-                                                           paymentTransaction.getId(), transactionStatus, processedAmount, processedCurrency, gatewayErrorCode, gatewayError, internalCallContext);
 
-        return true;
+        // Recompute new lastSuccessPaymentState. This is important to be able to allow new operations on the state machine (for e.g an AUTH_SUCCESS would now allow a CAPTURE operation)
+        if (paymentStateMachineHelper.isSuccessState(newPaymentState)) {
+            final String lastSuccessPaymentState = newPaymentState;
+            paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(), paymentTransaction.getAttemptId(), payment.getId(), paymentTransaction.getTransactionType(), newPaymentState, lastSuccessPaymentState,
+                                                               paymentTransaction.getId(), transactionStatus, processedAmount, processedCurrency, gatewayErrorCode, gatewayError, internalCallContext);
+        } else {
+            paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(), paymentTransaction.getAttemptId(), payment.getId(), paymentTransaction.getTransactionType(), newPaymentState,
+                                                               paymentTransaction.getId(), transactionStatus, processedAmount, processedCurrency, gatewayErrorCode, gatewayError, internalCallContext);
+        }
 
+        return true;
     }
 
     // Keep the existing currentTransactionStatus if we can't obtain a better answer from the plugin; if not, return the newTransactionStatus
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
index c800413..b39e096 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
@@ -1,8 +1,8 @@
 /*
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
  *
- * Groupon licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -131,20 +131,37 @@ public class PaymentAutomatonDAOHelper {
         final String gatewayErrorCode = paymentInfoPlugin == null ? null : paymentInfoPlugin.getGatewayErrorCode();
         final String gatewayErrorMsg = paymentInfoPlugin == null ? null : paymentInfoPlugin.getGatewayError();
 
-        final String lastSuccessPaymentState = paymentSMHelper.isSuccessState(currentPaymentStateName) ? currentPaymentStateName : null;
-        final PaymentAndTransactionModelDao paymentAndTransactionModelDao = paymentDao.updatePaymentAndTransactionOnCompletion(paymentStateContext.getAccount().getId(),
-                                                                                                                               paymentStateContext.getAttemptId(),
-                                                                                                                               paymentStateContext.getPaymentId(),
-                                                                                                                               paymentStateContext.getTransactionType(),
-                                                                                                                               currentPaymentStateName,
-                                                                                                                               lastSuccessPaymentState,
-                                                                                                                               paymentStateContext.getPaymentTransactionModelDao().getId(),
-                                                                                                                               transactionStatus,
-                                                                                                                               processedAmount,
-                                                                                                                               processedCurrency,
-                                                                                                                               gatewayErrorCode,
-                                                                                                                               gatewayErrorMsg,
-                                                                                                                               internalCallContext);
+        final PaymentAndTransactionModelDao paymentAndTransactionModelDao;
+        if (paymentSMHelper.isSuccessState(currentPaymentStateName)) {
+            final String lastSuccessPaymentState = currentPaymentStateName;
+            paymentAndTransactionModelDao = paymentDao.updatePaymentAndTransactionOnCompletion(paymentStateContext.getAccount().getId(),
+                                                                                               paymentStateContext.getAttemptId(),
+                                                                                               paymentStateContext.getPaymentId(),
+                                                                                               paymentStateContext.getTransactionType(),
+                                                                                               currentPaymentStateName,
+                                                                                               lastSuccessPaymentState,
+                                                                                               paymentStateContext.getPaymentTransactionModelDao().getId(),
+                                                                                               transactionStatus,
+                                                                                               processedAmount,
+                                                                                               processedCurrency,
+                                                                                               gatewayErrorCode,
+                                                                                               gatewayErrorMsg,
+                                                                                               internalCallContext);
+        } else {
+            paymentAndTransactionModelDao = paymentDao.updatePaymentAndTransactionOnCompletion(paymentStateContext.getAccount().getId(),
+                                                                                               paymentStateContext.getAttemptId(),
+                                                                                               paymentStateContext.getPaymentId(),
+                                                                                               paymentStateContext.getTransactionType(),
+                                                                                               currentPaymentStateName,
+                                                                                               paymentStateContext.getPaymentTransactionModelDao().getId(),
+                                                                                               transactionStatus,
+                                                                                               processedAmount,
+                                                                                               processedCurrency,
+                                                                                               gatewayErrorCode,
+                                                                                               gatewayErrorMsg,
+                                                                                               internalCallContext);
+        }
+
         // Update the context
         paymentStateContext.setPaymentModelDao(paymentAndTransactionModelDao.getPaymentModelDao());
         paymentStateContext.setPaymentTransactionModelDao(paymentAndTransactionModelDao.getPaymentTransactionModelDao());
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
index 516057c..1256398 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -325,11 +325,40 @@ public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, P
 
     @Override
     public PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(final UUID accountId, @Nullable final UUID attemptId, final UUID paymentId, final TransactionType transactionType,
-                                                                                                    final String currentPaymentStateName, @Nullable final String lastPaymentSuccessStateName,
-                                                                                                    final UUID transactionId, final TransactionStatus transactionStatus,
-                                                                                                    final BigDecimal processedAmount, final Currency processedCurrency,
-                                                                                                    final String gatewayErrorCode, final String gatewayErrorMsg,
-                                                                                                    final InternalCallContext context) {
+                                                                                 final String currentPaymentStateName,
+                                                                                 final UUID transactionId, final TransactionStatus transactionStatus,
+                                                                                 final BigDecimal processedAmount, final Currency processedCurrency,
+                                                                                 final String gatewayErrorCode, final String gatewayErrorMsg,
+                                                                                 final InternalCallContext context) {
+        return updatePaymentAndTransactionOnCompletion(false, accountId, attemptId, paymentId, transactionType,
+                                                       currentPaymentStateName, null,
+                                                       transactionId, transactionStatus,
+                                                       processedAmount, processedCurrency,
+                                                       gatewayErrorCode, gatewayErrorMsg,
+                                                       context);
+    }
+
+    @Override
+    public PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(final UUID accountId, @Nullable final UUID attemptId, final UUID paymentId, final TransactionType transactionType,
+                                                                                 final String currentPaymentStateName, @Nullable final String lastPaymentSuccessStateName,
+                                                                                 final UUID transactionId, final TransactionStatus transactionStatus,
+                                                                                 final BigDecimal processedAmount, final Currency processedCurrency,
+                                                                                 final String gatewayErrorCode, final String gatewayErrorMsg,
+                                                                                 final InternalCallContext context) {
+        return updatePaymentAndTransactionOnCompletion(true, accountId, attemptId, paymentId, transactionType,
+                                                       currentPaymentStateName, lastPaymentSuccessStateName,
+                                                       transactionId, transactionStatus,
+                                                       processedAmount, processedCurrency,
+                                                       gatewayErrorCode, gatewayErrorMsg,
+                                                       context);
+    }
+
+    private PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(final boolean updateLastPaymentSuccessStateName, final UUID accountId, @Nullable final UUID attemptId, final UUID paymentId, final TransactionType transactionType,
+                                                                                  final String currentPaymentStateName, @Nullable final String lastPaymentSuccessStateName,
+                                                                                  final UUID transactionId, final TransactionStatus transactionStatus,
+                                                                                  final BigDecimal processedAmount, final Currency processedCurrency,
+                                                                                  final String gatewayErrorCode, final String gatewayErrorMsg,
+                                                                                  final InternalCallContext context) {
         final PaymentAndTransactionModelDao paymentAndTransactionModelDao = new PaymentAndTransactionModelDao();
 
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentAndTransactionModelDao>() {
@@ -358,7 +387,7 @@ public class DefaultPaymentDao extends EntityDaoBase<PaymentModelDao, Payment, P
 
                 final PaymentSqlDao paymentSqlDao = entitySqlDaoWrapperFactory.become(PaymentSqlDao.class);
                 final PaymentModelDao paymentModelDao;
-                if (lastPaymentSuccessStateName != null) {
+                if (updateLastPaymentSuccessStateName) {
                     paymentModelDao = (PaymentModelDao) paymentSqlDao.updateLastSuccessPaymentStateName(paymentId.toString(), currentPaymentStateName, lastPaymentSuccessStateName, contextWithUpdatedDate);
                 } else {
                     paymentModelDao = (PaymentModelDao) paymentSqlDao.updatePaymentStateName(paymentId.toString(), currentPaymentStateName, contextWithUpdatedDate);
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
index 5c67ff0..375bbdd 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
@@ -61,6 +61,10 @@ public interface PaymentDao extends EntityDao<PaymentModelDao, Payment, PaymentA
 
     public PaymentTransactionModelDao updatePaymentWithNewTransaction(UUID paymentId, PaymentTransactionModelDao paymentTransaction, InternalCallContext context);
 
+    public PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(UUID accountId, UUID attemptId, UUID paymentId, TransactionType transactionType, String currentPaymentStateName, UUID transactionId,
+                                                                                 TransactionStatus paymentStatus, BigDecimal processedAmount, Currency processedCurrency,
+                                                                                 String gatewayErrorCode, String gatewayErrorMsg, InternalCallContext context);
+
     public PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(UUID accountId, UUID attemptId, UUID paymentId, TransactionType transactionType, String currentPaymentStateName, String lastPaymentSuccessStateName, UUID transactionId,
                                                                                  TransactionStatus paymentStatus, BigDecimal processedAmount, Currency processedCurrency,
                                                                                  String gatewayErrorCode, String gatewayErrorMsg, InternalCallContext context);
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java
index 0ba11c3..3e7d30a 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultAdminPaymentApi.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2016 Groupon, Inc
- * Copyright 2016 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -18,6 +18,7 @@
 package org.killbill.billing.payment.api;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 
 import org.killbill.billing.ErrorCode;
@@ -81,16 +82,28 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testFixPaymentTransactionState() throws PaymentApiException {
-        final Payment payment = paymentApi.createAuthorization(account,
-                                                               account.getPaymentMethodId(),
-                                                               null,
-                                                               BigDecimal.TEN,
-                                                               Currency.EUR,
-                                                               null,
-                                                               UUID.randomUUID().toString(),
-                                                               UUID.randomUUID().toString(),
-                                                               ImmutableList.<PluginProperty>of(),
-                                                               callContext);
+        final PaymentOptions paymentOptions = new PaymentOptions() {
+            @Override
+            public boolean isExternalPayment() {
+                return false;
+            }
+
+            @Override
+            public List<String> getPaymentControlPluginNames() {
+                return ImmutableList.<String>of(MockPaymentControlProviderPlugin.PLUGIN_NAME);
+            }
+        };
+        final Payment payment = paymentApi.createAuthorizationWithPaymentControl(account,
+                                                                                 account.getPaymentMethodId(),
+                                                                                 null,
+                                                                                 BigDecimal.TEN,
+                                                                                 Currency.EUR,
+                                                                                 null,
+                                                                                 UUID.randomUUID().toString(),
+                                                                                 UUID.randomUUID().toString(),
+                                                                                 ImmutableList.<PluginProperty>of(),
+                                                                                 paymentOptions,
+                                                                                 callContext);
 
         final PaymentModelDao paymentModelDao = paymentDao.getPayment(payment.getId(), internalCallContext);
         final PaymentTransactionModelDao paymentTransactionModelDao = paymentDao.getPaymentTransaction(payment.getTransactions().get(0).getId(), internalCallContext);
@@ -107,13 +120,27 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         final PaymentModelDao refreshedPaymentModelDao = paymentDao.getPayment(payment.getId(), internalCallContext);
         final PaymentTransactionModelDao refreshedPaymentTransactionModelDao = paymentDao.getPaymentTransaction(payment.getTransactions().get(0).getId(), internalCallContext);
         Assert.assertEquals(refreshedPaymentModelDao.getStateName(), "AUTH_ERRORED");
-        // TODO Shouldn't we allow the user to override this too?
-        Assert.assertEquals(refreshedPaymentModelDao.getLastSuccessStateName(), "AUTH_SUCCESS");
+        Assert.assertNull(refreshedPaymentModelDao.getLastSuccessStateName());
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getProcessedAmount().compareTo(BigDecimal.TEN), 0);
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getProcessedCurrency(), Currency.EUR);
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getGatewayErrorCode(), "");
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getGatewayErrorMsg(), "");
+
+        // Verify subsequent payment retries work
+        retryService.retryPaymentTransaction(refreshedPaymentTransactionModelDao.getAttemptId(), ImmutableList.<String>of(MockPaymentControlProviderPlugin.PLUGIN_NAME), internalCallContext);
+
+        final Payment retriedPayment = paymentApi.getPayment(payment.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
+        Assert.assertEquals(retriedPayment.getTransactions().size(), 2);
+        final PaymentModelDao retriedPaymentModelDao = paymentDao.getPayment(payment.getId(), internalCallContext);
+        final PaymentTransactionModelDao retriedPaymentTransactionModelDao = paymentDao.getPaymentTransaction(retriedPayment.getTransactions().get(1).getId(), internalCallContext);
+        Assert.assertEquals(retriedPaymentModelDao.getStateName(), "AUTH_SUCCESS");
+        Assert.assertEquals(retriedPaymentModelDao.getLastSuccessStateName(), "AUTH_SUCCESS");
+        Assert.assertEquals(retriedPaymentTransactionModelDao.getTransactionStatus(), TransactionStatus.SUCCESS);
+        Assert.assertEquals(retriedPaymentTransactionModelDao.getProcessedAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(retriedPaymentTransactionModelDao.getProcessedCurrency(), Currency.EUR);
+        Assert.assertEquals(retriedPaymentTransactionModelDao.getGatewayErrorCode(), "");
+        Assert.assertEquals(retriedPaymentTransactionModelDao.getGatewayErrorMsg(), "");
     }
 
     @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/551")
@@ -201,8 +228,7 @@ public class TestDefaultAdminPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         final PaymentModelDao refreshedPaymentModelDao = paymentDao.getPayment(payment.getId(), internalCallContext);
         final PaymentTransactionModelDao refreshedPaymentTransactionModelDao = paymentDao.getPaymentTransaction(payment.getTransactions().get(0).getId(), internalCallContext);
         Assert.assertEquals(refreshedPaymentModelDao.getStateName(), "AUTH_ERRORED");
-        // TODO Shouldn't we allow the user to override this too?
-        Assert.assertEquals(refreshedPaymentModelDao.getLastSuccessStateName(), "AUTH_SUCCESS");
+        Assert.assertNull(refreshedPaymentModelDao.getLastSuccessStateName());
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getProcessedAmount().compareTo(BigDecimal.TEN), 0);
         Assert.assertEquals(refreshedPaymentTransactionModelDao.getProcessedCurrency(), Currency.EUR);
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
index 8a0df98..1db8422 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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
@@ -234,6 +234,17 @@ public class MockPaymentDao extends MockEntityDaoBase<PaymentModelDao, Payment, 
 
     @Override
     public PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(final UUID accountId, final UUID attemptId, final UUID paymentId, final TransactionType transactionType,
+                                                                                 final String currentPaymentStateName, final UUID transactionId,
+                                                                                 final TransactionStatus paymentStatus, final BigDecimal processedAmount, final Currency processedCurrency,
+                                                                                 final String gatewayErrorCode, final String gatewayErrorMsg, final InternalCallContext context) {
+        return updatePaymentAndTransactionOnCompletion(accountId, attemptId, paymentId, transactionType,
+                                                       currentPaymentStateName, null, transactionId,
+                                                       paymentStatus, processedAmount, processedCurrency,
+                                                       gatewayErrorCode, gatewayErrorMsg, context);
+    }
+
+    @Override
+    public PaymentAndTransactionModelDao updatePaymentAndTransactionOnCompletion(final UUID accountId, final UUID attemptId, final UUID paymentId, final TransactionType transactionType,
                                                                                  final String currentPaymentStateName, final String lastSuccessPaymentStateName, final UUID transactionId,
                                                                                  final TransactionStatus paymentStatus, final BigDecimal processedAmount, final Currency processedCurrency,
                                                                                  final String gatewayErrorCode, final String gatewayErrorMsg, final InternalCallContext context) {