killbill-memoizeit

Create config param for payment timeout and default to 90 sec

5/4/2013 11:46:31 PM

Details

diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index 427e35b..1c5d83b 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -52,6 +52,17 @@ public interface PaymentApi {
 
 
     /**
+     *
+     * @param account   the account
+     * @param paymentId the payment id
+     * @param context
+     * @return
+     * @throws PaymentApiException
+     */
+    public Payment retryPayment(Account account, UUID paymentId, CallContext context)
+            throws PaymentApiException;
+
+    /**
      * Create a refund for a given payment. The associated invoice is not adjusted.
      *
      * @param account      account to refund
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
index 4d41e06..e63a21b 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/PaymentResource.java
@@ -30,6 +30,7 @@ import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
@@ -136,6 +137,27 @@ public class PaymentResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(paymentJsonSimple).build();
     }
 
+    @PUT
+    @Path("/{paymentId:" + UUID_PATTERN + "}")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response retryFailedPayment(@PathParam(ID_PARAM_NAME) final String paymentIdString,
+                                   @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                   @HeaderParam(HDR_REASON) final String reason,
+                                   @HeaderParam(HDR_COMMENT) final String comment,
+                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
+
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+
+        final UUID paymentId = UUID.fromString(paymentIdString);
+        final Payment payment = paymentApi.getPayment(paymentId, false, callContext);
+        final Account account = accountApi.getAccountById(payment.getAccountId(), callContext);
+        final Payment newPayment = paymentApi.retryPayment(account, paymentId, callContext);
+
+        return Response.status(Status.OK).entity(new PaymentJsonSimple(newPayment)).build();
+    }
+
+
     @GET
     @Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
     @Produces(APPLICATION_JSON)
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index 92e5d45..346061b 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -29,6 +29,7 @@ import com.ning.billing.payment.core.PaymentMethodProcessor;
 import com.ning.billing.payment.core.PaymentProcessor;
 import com.ning.billing.payment.core.RefundProcessor;
 import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalCallContextFactory;
 import com.ning.billing.util.callcontext.TenantContext;
 
@@ -67,6 +68,13 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
+    public Payment retryPayment(final Account account, final UUID paymentId, final CallContext context) throws PaymentApiException {
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), context);
+        paymentProcessor.retryPaymentFromApi(paymentId, internalCallContext);
+        return getPayment(paymentId, false, context);
+    }
+
+    @Override
     public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final TenantContext context) throws PaymentApiException {
         final Payment payment = paymentProcessor.getPayment(paymentId, withPluginInfo, internalCallContextFactory.createInternalTenantContext(context));
         if (payment == null) {
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
index dc45727..232bfb5 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
@@ -113,8 +113,8 @@ public class PaymentProcessor extends ProcessorBase {
         this.autoPayoffRetryService = autoPayoffRetryService;
         this.clock = clock;
         this.paymentConfig = paymentConfig;
-        this.paymentPluginDispatcher = new PluginDispatcher<Payment>(executor);
-        this.voidPluginDispatcher = new PluginDispatcher<Void>(executor);
+        this.paymentPluginDispatcher = new PluginDispatcher<Payment>(paymentConfig.getPaymentTimeoutSeconds(), executor);
+        this.voidPluginDispatcher = new PluginDispatcher<Void>(paymentConfig.getPaymentTimeoutSeconds(), executor);
     }
 
     public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final InternalTenantContext context) throws PaymentApiException {
@@ -328,6 +328,13 @@ public class PaymentProcessor extends ProcessorBase {
         retryFailedPaymentInternal(paymentId, context, PaymentStatus.PAYMENT_FAILURE);
     }
 
+    public void retryPaymentFromApi(final UUID paymentId, final InternalCallContext context) {
+        log.info("Retrying payment " + paymentId + " time = " + clock.getUTCNow());
+        retryFailedPaymentInternal(paymentId, context, PaymentStatus.UNKNOWN,
+                                   PaymentStatus.AUTO_PAY_OFF,
+                                   PaymentStatus.PAYMENT_FAILURE,
+                                   PaymentStatus.PLUGIN_FAILURE);
+    }
 
     private void retryFailedPaymentInternal(final UUID paymentId, final InternalCallContext context, final PaymentStatus... expectedPaymentStates) {
 
@@ -386,7 +393,7 @@ public class PaymentProcessor extends ProcessorBase {
         } catch (AccountApiException e) {
             log.error(String.format("Failed to retry payment for paymentId %s", paymentId), e);
         } catch (PaymentApiException e) {
-            log.info(String.format("Failed to retry payment for paymentId %s", paymentId));
+            log.info(String.format("Failed to retry payment for paymentId %s", paymentId), e);
         } catch (TimeoutException e) {
             log.warn(String.format("Retry for payment %s timedout", paymentId));
             // STEPH we should throw some exception so NotificationQ does not clear status and retries us
diff --git a/payment/src/main/java/com/ning/billing/payment/dispatcher/PluginDispatcher.java b/payment/src/main/java/com/ning/billing/payment/dispatcher/PluginDispatcher.java
index ba0e3fc..d62750c 100644
--- a/payment/src/main/java/com/ning/billing/payment/dispatcher/PluginDispatcher.java
+++ b/payment/src/main/java/com/ning/billing/payment/dispatcher/PluginDispatcher.java
@@ -34,16 +34,18 @@ public class PluginDispatcher<T> {
 
     private final TimeUnit DEEFAULT_PLUGIN_TIMEOUT_UNIT = TimeUnit.SECONDS;
 
+    private final long timeoutSeconds;
     private final ExecutorService executor;
 
-    public PluginDispatcher(final ExecutorService executor) {
+    public PluginDispatcher(final long tiemoutSeconds, final ExecutorService executor) {
+        this.timeoutSeconds = tiemoutSeconds;
         this.executor = executor;
     }
 
+
     public T dispatchWithAccountLock(final Callable<T> task)
             throws PaymentApiException, TimeoutException {
-        final long DEFAULT_PLUGIN_TIMEOUT_SEC = 30;
-        return dispatchWithAccountLockAndTimeout(task, DEFAULT_PLUGIN_TIMEOUT_SEC, DEEFAULT_PLUGIN_TIMEOUT_UNIT);
+        return dispatchWithAccountLockAndTimeout(task, timeoutSeconds, DEEFAULT_PLUGIN_TIMEOUT_UNIT);
     }
 
     public T dispatchWithAccountLockAndTimeout(final Callable<T> task, final long timeout, final TimeUnit unit)
diff --git a/payment/src/test/java/com/ning/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/com/ning/billing/payment/dispatcher/TestPluginDispatcher.java
new file mode 100644
index 0000000..9617ee9
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dispatcher/TestPluginDispatcher.java
@@ -0,0 +1,77 @@
+package com.ning.billing.payment.dispatcher;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.ErrorCode;
+import com.ning.billing.payment.PaymentTestSuiteNoDB;
+import com.ning.billing.payment.api.PaymentApiException;
+
+public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
+
+    private final PluginDispatcher<Void> voidPluginDispatcher = new PluginDispatcher<Void>(10, Executors.newSingleThreadExecutor());
+
+    @Test(groups = "fast")
+    public void testDispatchWithTimeout() throws TimeoutException, PaymentApiException {
+        boolean gotIt = false;
+        try {
+            voidPluginDispatcher.dispatchWithAccountLockAndTimeout(new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    Thread.sleep(1000);
+                    return null;
+                }
+            }, 100, TimeUnit.MILLISECONDS);
+            Assert.fail("Failed : should have had Timeout exception");
+        } catch (TimeoutException e) {
+            gotIt = true;
+        } catch (PaymentApiException e) {
+            Assert.fail("Failed : should have had Timeout exception");
+        }
+        Assert.assertTrue(gotIt);
+    }
+
+    @Test(groups = "fast")
+    public void testDispatchWithPaymentApiException() throws TimeoutException, PaymentApiException {
+        boolean gotIt = false;
+        try {
+            voidPluginDispatcher.dispatchWithAccountLockAndTimeout(new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    throw new PaymentApiException(ErrorCode.PAYMENT_ADD_PAYMENT_METHOD, "foo", "foo");
+                }
+            }, 100, TimeUnit.MILLISECONDS);
+            Assert.fail("Failed : should have had Timeout exception");
+        } catch (TimeoutException e) {
+            Assert.fail("Failed : should have had PaymentApiException exception");
+        } catch (PaymentApiException e) {
+            gotIt = true;
+        }
+        Assert.assertTrue(gotIt);
+    }
+
+    @Test(groups = "fast")
+    public void testDispatchWithRuntimeExceptionWrappedInPaymentApiException() throws TimeoutException, PaymentApiException {
+        boolean gotIt = false;
+        try {
+            voidPluginDispatcher.dispatchWithAccountLockAndTimeout(new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    throw new RuntimeException("whatever");
+                }
+            }, 100, TimeUnit.MILLISECONDS);
+            Assert.fail("Failed : should have had Timeout exception");
+        } catch (TimeoutException e) {
+            Assert.fail("Failed : should have had RuntimeException exception");
+        } catch (PaymentApiException e) {
+            gotIt = true;
+        } catch (RuntimeException e) {
+        }
+        Assert.assertTrue(gotIt);
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/config/PaymentConfig.java b/util/src/main/java/com/ning/billing/util/config/PaymentConfig.java
index d4d7ad0..47549a1 100644
--- a/util/src/main/java/com/ning/billing/util/config/PaymentConfig.java
+++ b/util/src/main/java/com/ning/billing/util/config/PaymentConfig.java
@@ -48,6 +48,11 @@ public interface PaymentConfig extends KillbillConfig {
     @Description("Maximum number of retries for failed payments")
     public int getPluginFailureRetryMaxAttempts();
 
+    @Config("killbill.payment.timeout.seconds")
+    @Default("90")
+    @Description("Timeout for each payment attempt")
+    public int getPaymentTimeoutSeconds();
+
     @Config("killbill.payment.off")
     @Default("false")
     @Description("Whether the payment subsystem is off")