killbill-memoizeit
Changes
payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java 4(+2 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java 4(+2 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java 4(+2 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java 11(+9 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java 380(+380 -0)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java 4(+2 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/OperationControlCallback.java 291(+71 -220)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java 4(+2 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java 4(+2 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java 4(+2 -2)
payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java 22(+13 -9)
payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java 3(+3 -0)
payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java 12(+11 -1)
payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java 257(+257 -0)
payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java 5(+3 -2)
payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java 5(+3 -2)
pom.xml 2(+1 -1)
Details
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
new file mode 100644
index 0000000..cd916d9
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultApiBase.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.api;
+
+import java.math.BigDecimal;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.invoice.InvoicePaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+
+public class DefaultApiBase {
+
+ private static final Logger log = LoggerFactory.getLogger(DefaultApiBase.class);
+
+ private final PaymentConfig paymentConfig;
+
+ public DefaultApiBase(final PaymentConfig paymentConfig) {
+ this.paymentConfig = paymentConfig;
+ }
+
+ protected void logAPICall(final String transactionType, final Account account, 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) {
+ if (log.isInfoEnabled()) {
+ final StringBuilder logLine = new StringBuilder();
+ logLine.append("PaymentApi : ")
+ .append(transactionType)
+ .append(", account = ")
+ .append(account.getId());
+ if (paymentMethodId != null) {
+ logLine.append(", paymentMethodId = ")
+ .append(paymentMethodId);
+ }
+ if (paymentExternalKey != null) {
+ logLine.append(", paymentExternalKey = ")
+ .append(paymentExternalKey);
+ }
+ if (paymentTransactionExternalKey != null) {
+ logLine.append(", paymentTransactionExternalKey = ")
+ .append(paymentTransactionExternalKey);
+ }
+ if (paymentId != null) {
+ logLine.append(", paymentId = ")
+ .append(paymentId);
+ }
+ if (transactionId != null) {
+ logLine.append(", transactionId = ")
+ .append(transactionId);
+ }
+ if (amount != null) {
+ logLine.append(", amount = ")
+ .append(amount);
+ }
+ if (currency != null) {
+ logLine.append(", currency = ")
+ .append(currency);
+ }
+ log.info(logLine.toString());
+ }
+ }
+
+ protected List<String> toPaymentControlPluginNames(final PaymentOptions paymentOptions) {
+ // Special path for JAX-RS InvoicePayment endpoints (see JaxRsResourceBase)
+ if (paymentConfig.getPaymentControlPluginNames() != null &&
+ paymentOptions.getPaymentControlPluginNames() != null &&
+ paymentOptions.getPaymentControlPluginNames().size() == 1 &&
+ InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentOptions.getPaymentControlPluginNames().get(0))) {
+ final List<String> paymentControlPluginNames = new LinkedList<String>(paymentOptions.getPaymentControlPluginNames());
+ paymentControlPluginNames.addAll(paymentConfig.getPaymentControlPluginNames());
+ return paymentControlPluginNames;
+ } else if (paymentOptions.getPaymentControlPluginNames() != null && !paymentOptions.getPaymentControlPluginNames().isEmpty()) {
+ return paymentOptions.getPaymentControlPluginNames();
+ } else if (paymentConfig.getPaymentControlPluginNames() != null && !paymentConfig.getPaymentControlPluginNames().isEmpty()) {
+ return paymentConfig.getPaymentControlPluginNames();
+ } else {
+ return ImmutableList.<String>of();
+ }
+ }
+
+ protected void checkNotNullParameter(final Object parameter, final String parameterName) throws PaymentApiException {
+ if (parameter == null) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null");
+ }
+ }
+
+ protected void checkPositiveAmount(final BigDecimal amount) throws PaymentApiException {
+ if (amount.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "amount", "should be greater than 0");
+ }
+ }
+
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
index 1e2d91d..3da498d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
@@ -44,7 +44,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
-public class DefaultPaymentApi implements PaymentApi {
+public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
private static final boolean SHOULD_LOCK_ACCOUNT = true;
private static final boolean IS_API_PAYMENT = true;
@@ -52,7 +52,6 @@ public class DefaultPaymentApi implements PaymentApi {
private static final Logger log = LoggerFactory.getLogger(DefaultPaymentApi.class);
- private final PaymentConfig paymentConfig;
private final PaymentProcessor paymentProcessor;
private final PaymentMethodProcessor paymentMethodProcessor;
private final PluginControlPaymentProcessor pluginControlPaymentProcessor;
@@ -60,7 +59,7 @@ public class DefaultPaymentApi implements PaymentApi {
@Inject
public DefaultPaymentApi(final PaymentConfig paymentConfig, final PaymentProcessor paymentProcessor, final PaymentMethodProcessor paymentMethodProcessor, final PluginControlPaymentProcessor pluginControlPaymentProcessor, final InternalCallContextFactory internalCallContextFactory) {
- this.paymentConfig = paymentConfig;
+ super(paymentConfig);
this.paymentProcessor = paymentProcessor;
this.paymentMethodProcessor = paymentMethodProcessor;
this.pluginControlPaymentProcessor = pluginControlPaymentProcessor;
@@ -167,7 +166,7 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
- public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey, final String paymentTransactionExternalKey,
+ public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
if (paymentControlPluginNames.isEmpty()) {
@@ -180,7 +179,6 @@ public class DefaultPaymentApi implements PaymentApi {
checkPositiveAmount(amount);
checkNotNullParameter(currency, "currency");
}
- checkNotNullParameter(paymentExternalKey, "paymentExternalKey");
checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey");
checkNotNullParameter(properties, "plugin properties");
@@ -498,73 +496,4 @@ public class DefaultPaymentApi implements PaymentApi {
return paymentMethods;
}
-
- private void logAPICall(final String transactionType, final Account account, 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) {
- if (log.isInfoEnabled()) {
- final StringBuilder logLine = new StringBuilder();
- logLine.append("PaymentApi : ")
- .append(transactionType)
- .append(", account = ")
- .append(account.getId());
- if (paymentMethodId != null) {
- logLine.append(", paymentMethodId = ")
- .append(paymentMethodId);
- }
- if (paymentExternalKey != null) {
- logLine.append(", paymentExternalKey = ")
- .append(paymentExternalKey);
- }
- if (paymentTransactionExternalKey != null) {
- logLine.append(", paymentTransactionExternalKey = ")
- .append(paymentTransactionExternalKey);
- }
- if (paymentId != null) {
- logLine.append(", paymentId = ")
- .append(paymentId);
- }
- if (transactionId != null) {
- logLine.append(", transactionId = ")
- .append(transactionId);
- }
- if (amount != null) {
- logLine.append(", amount = ")
- .append(amount);
- }
- if (currency != null) {
- logLine.append(", currency = ")
- .append(currency);
- }
- log.info(logLine.toString());
- }
- }
-
- private List<String> toPaymentControlPluginNames(final PaymentOptions paymentOptions) {
- // Special path for JAX-RS InvoicePayment endpoints (see JaxRsResourceBase)
- if (paymentConfig.getPaymentControlPluginNames() != null &&
- paymentOptions.getPaymentControlPluginNames() != null &&
- paymentOptions.getPaymentControlPluginNames().size() == 1 &&
- InvoicePaymentControlPluginApi.PLUGIN_NAME.equals(paymentOptions.getPaymentControlPluginNames().get(0))) {
- final List<String> paymentControlPluginNames = new LinkedList<String>(paymentOptions.getPaymentControlPluginNames());
- paymentControlPluginNames.addAll(paymentConfig.getPaymentControlPluginNames());
- return paymentControlPluginNames;
- } else if (paymentOptions.getPaymentControlPluginNames() != null && !paymentOptions.getPaymentControlPluginNames().isEmpty()) {
- return paymentOptions.getPaymentControlPluginNames();
- } else if (paymentConfig.getPaymentControlPluginNames() != null && !paymentConfig.getPaymentControlPluginNames().isEmpty()) {
- return paymentConfig.getPaymentControlPluginNames();
- } else {
- return ImmutableList.<String>of();
- }
- }
-
- private void checkNotNullParameter(final Object parameter, final String parameterName) throws PaymentApiException {
- if (parameter == null) {
- throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null");
- }
- }
-
- private void checkPositiveAmount(final BigDecimal amount) throws PaymentApiException {
- if (amount.compareTo(BigDecimal.ZERO) <= 0) {
- throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "amount", "should be greater than 0");
- }
- }
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
index c9adbaf..39117a5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
@@ -17,6 +17,7 @@
package org.killbill.billing.payment.api;
+import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -24,25 +25,37 @@ import javax.inject.Inject;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.account.api.Account;
+import org.killbill.billing.control.plugin.api.HPPType;
+import org.killbill.billing.control.plugin.api.PaymentApiType;
+import org.killbill.billing.control.plugin.api.PaymentControlApiException;
+import org.killbill.billing.control.plugin.api.PriorPaymentControlResult;
import org.killbill.billing.payment.core.PaymentGatewayProcessor;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
import org.killbill.billing.payment.plugin.api.GatewayNotification;
import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.PaymentConfig;
-public class DefaultPaymentGatewayApi implements PaymentGatewayApi {
+public class DefaultPaymentGatewayApi extends DefaultApiBase implements PaymentGatewayApi {
private final PaymentGatewayProcessor paymentGatewayProcessor;
+ private final ControlPluginRunner controlPluginRunner;
private final InternalCallContextFactory internalCallContextFactory;
@Inject
- public DefaultPaymentGatewayApi(final PaymentGatewayProcessor paymentGatewayProcessor, final InternalCallContextFactory internalCallContextFactory) {
+ public DefaultPaymentGatewayApi(final PaymentConfig paymentConfig,
+ final PaymentGatewayProcessor paymentGatewayProcessor,
+ final ControlPluginRunner controlPluginRunner,
+ final InternalCallContextFactory internalCallContextFactory) {
+ super(paymentConfig);
this.paymentGatewayProcessor = paymentGatewayProcessor;
+ this.controlPluginRunner = controlPluginRunner;
this.internalCallContextFactory = internalCallContextFactory;
}
@Override
- public HostedPaymentPageFormDescriptor buildFormDescriptor(final Account account, @Nullable final UUID paymentMethodId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
+ public HostedPaymentPageFormDescriptor buildFormDescriptor(final Account account, final UUID paymentMethodId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
final UUID paymentMethodIdToUse = paymentMethodId != null ? paymentMethodId : account.getPaymentMethodId();
if (paymentMethodId == null) {
@@ -53,8 +66,14 @@ public class DefaultPaymentGatewayApi implements PaymentGatewayApi {
}
@Override
- public HostedPaymentPageFormDescriptor buildFormDescriptorWithPaymentControl(final Account account, final UUID uuid, final Iterable<PluginProperty> iterable, final Iterable<PluginProperty> iterable1, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
- throw new IllegalStateException("Not implemented");
+ public HostedPaymentPageFormDescriptor buildFormDescriptorWithPaymentControl(final Account account, final UUID paymentMethodId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+
+ return executeWithPaymentControl(account, paymentMethodId, properties, paymentOptions, callContext, new WithPaymentControlCallback<HostedPaymentPageFormDescriptor>() {
+ @Override
+ public HostedPaymentPageFormDescriptor doPaymentGatewayApiOperation(final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException {
+ return buildFormDescriptor(account, paymentMethodId, customFields, adjustedPluginProperties, callContext);
+ }
+ });
}
@Override
@@ -63,7 +82,61 @@ public class DefaultPaymentGatewayApi implements PaymentGatewayApi {
}
@Override
- public GatewayNotification processNotificationWithPaymentControl(final String s, final String s1, final Iterable<PluginProperty> iterable, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
- throw new IllegalStateException("Not implemented");
+ public GatewayNotification processNotificationWithPaymentControl(final String notification, final String pluginName, final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+ return executeWithPaymentControl(null, null, properties, paymentOptions, callContext, new WithPaymentControlCallback<GatewayNotification>() {
+ @Override
+ public GatewayNotification doPaymentGatewayApiOperation(final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException {
+ return processNotification(notification, pluginName, adjustedPluginProperties, callContext);
+ }
+ });
+ }
+
+
+ private interface WithPaymentControlCallback<T> {
+ T doPaymentGatewayApiOperation(final Iterable<PluginProperty> adjustedPluginProperties) throws PaymentApiException;
+ }
+
+ private <T> T executeWithPaymentControl(@Nullable final Account account,
+ @Nullable final UUID paymentMethodId,
+ final Iterable<PluginProperty> properties,
+ final PaymentOptions paymentOptions,
+ final CallContext callContext,
+ final WithPaymentControlCallback<T> callback) throws PaymentApiException {
+
+ final List<String> paymentControlPluginNames = toPaymentControlPluginNames(paymentOptions);
+ if (paymentControlPluginNames.isEmpty()) {
+ return callback.doPaymentGatewayApiOperation(properties);
+ }
+
+ final PriorPaymentControlResult priorCallResult;
+ try {
+ priorCallResult = controlPluginRunner.executePluginPriorCalls(account,
+ paymentMethodId,
+ null, null, null, null,
+ PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
+ null, null, true, paymentControlPluginNames, properties, callContext);
+
+ } catch (final PaymentControlApiException e) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e);
+ }
+
+ try {
+ final T result = callback.doPaymentGatewayApiOperation(priorCallResult.getAdjustedPluginProperties());
+ controlPluginRunner.executePluginOnSuccessCalls(account,
+ paymentMethodId,
+ null, null, null, null, null,
+ PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
+ null, null, null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext);
+ return result;
+ } catch (final PaymentApiException e) {
+ controlPluginRunner.executePluginOnFailureCalls(account,
+ paymentMethodId,
+ null, null, null, null,
+ PaymentApiType.HPP, null, HPPType.BUILD_FORM_DESCRIPTOR,
+ null, null, true, paymentControlPluginNames, priorCallResult.getAdjustedPluginProperties(), callContext);
+
+ throw e;
+
+ }
}
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
index 9634461..0459ad2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/AuthorizeControlOperation.java
@@ -27,8 +27,8 @@ import org.killbill.commons.locker.GlobalLocker;
public class AuthorizeControlOperation extends OperationControlCallback {
- public AuthorizeControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
- super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ public AuthorizeControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
}
@Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
index 2d325c4..2b76885 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CaptureControlOperation.java
@@ -27,8 +27,8 @@ import org.killbill.commons.locker.GlobalLocker;
public class CaptureControlOperation extends OperationControlCallback {
- public CaptureControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
- super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ public CaptureControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
}
@Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
index 3f47fdc..07b5344 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ChargebackControlOperation.java
@@ -28,8 +28,8 @@ import org.killbill.commons.locker.GlobalLocker;
public class ChargebackControlOperation extends OperationControlCallback {
- public ChargebackControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
- super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ public ChargebackControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
}
@Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
index d6369e7..6f3e300 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CompletionControlOperation.java
@@ -19,11 +19,13 @@ package org.killbill.billing.payment.core.sm.control;
import org.killbill.automaton.OperationException;
import org.killbill.automaton.OperationResult;
+import org.killbill.billing.control.plugin.api.PaymentApiType;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.core.PaymentProcessor;
import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext;
import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
import org.killbill.billing.payment.dispatcher.PluginDispatcher;
import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
@@ -36,8 +38,11 @@ import org.killbill.commons.locker.GlobalLocker;
//
public class CompletionControlOperation extends OperationControlCallback {
- public CompletionControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry) {
- super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry);
+ public CompletionControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+ final PaymentStateControlContext paymentStateContext,
+ final PaymentProcessor paymentProcessor,
+ final ControlPluginRunner controlPluginRunner) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
}
@Override
@@ -54,7 +59,9 @@ public class CompletionControlOperation extends OperationControlCallback {
paymentStateContext.getPaymentExternalKey(),
transaction.getId(),
paymentStateContext.getPaymentTransactionExternalKey(),
+ PaymentApiType.PAYMENT_TRANSACTION,
paymentStateContext.getTransactionType(),
+ null,
transaction.getAmount(),
transaction.getCurrency(),
transaction.getProcessedAmount(),
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
new file mode 100644
index 0000000..41c37a2
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/ControlPluginRunner.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm.control;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+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.HPPType;
+import org.killbill.billing.control.plugin.api.OnFailurePaymentControlResult;
+import org.killbill.billing.control.plugin.api.OnSuccessPaymentControlResult;
+import org.killbill.billing.control.plugin.api.PaymentApiType;
+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.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.retry.DefaultFailureCallResult;
+import org.killbill.billing.payment.retry.DefaultOnSuccessPaymentControlResult;
+import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ControlPluginRunner {
+
+ private static final Logger log = LoggerFactory.getLogger(ControlPluginRunner.class);
+
+ private final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry;
+
+ @Inject
+ public ControlPluginRunner(final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
+ this.paymentControlPluginRegistry = paymentControlPluginRegistry;
+ }
+
+ public PriorPaymentControlResult executePluginPriorCalls(final Account account,
+ final UUID paymentMethodId,
+ final UUID paymentAttemptId,
+ final UUID paymentId,
+ final String paymentExternalKey,
+ final String paymentTransactionExternalKey,
+ final PaymentApiType paymentApiType,
+ final TransactionType transactionType,
+ final HPPType hppType,
+ final BigDecimal amount,
+ final Currency currency,
+ final boolean isApiPayment,
+ final List<String> paymentControlPluginNames,
+ final Iterable<PluginProperty> pluginProperties,
+ final CallContext callContext) throws PaymentControlApiException {
+ // Return as soon as the first plugin aborts, or the last result for the last plugin
+ PriorPaymentControlResult prevResult = null;
+
+ // Those values are adjusted prior each call with the result of what previous call to plugin returned
+ Iterable<PluginProperty> inputPluginProperties = pluginProperties;
+ PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account,
+ paymentMethodId,
+ paymentAttemptId,
+ paymentId,
+ paymentExternalKey,
+ paymentTransactionExternalKey,
+ paymentApiType,
+ transactionType,
+ hppType,
+ amount,
+ currency,
+ isApiPayment,
+ callContext);
+
+ for (final String pluginName : paymentControlPluginNames) {
+ final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+ if (plugin == null) {
+ // First call to plugin, we log warn, if plugin is not registered
+ log.warn("Skipping unknown payment control plugin {} when fetching results", pluginName);
+ continue;
+ }
+ prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties);
+ if (prevResult.getAdjustedPluginProperties() != null) {
+ inputPluginProperties = prevResult.getAdjustedPluginProperties();
+ }
+ if (prevResult.isAborted()) {
+ break;
+ }
+ inputPaymentControlContext = new DefaultPaymentControlContext(account,
+ prevResult.getAdjustedPaymentMethodId() != null ? prevResult.getAdjustedPaymentMethodId() : paymentMethodId,
+ paymentAttemptId,
+ paymentId,
+ paymentExternalKey,
+ paymentTransactionExternalKey,
+ paymentApiType,
+ transactionType,
+ hppType,
+ prevResult.getAdjustedAmount() != null ? prevResult.getAdjustedAmount() : amount,
+ prevResult.getAdjustedCurrency() != null ? prevResult.getAdjustedCurrency() : currency,
+ isApiPayment,
+ callContext);
+ }
+ // Rebuild latest result to include inputPluginProperties
+ prevResult = new DefaultPriorPaymentControlResult(prevResult, inputPluginProperties);
+ return prevResult;
+ }
+
+ public OnSuccessPaymentControlResult executePluginOnSuccessCalls(final Account account,
+ final UUID paymentMethodId,
+ final UUID paymentAttemptId,
+ final UUID paymentId,
+ final String paymentExternalKey,
+ final UUID transactionId,
+ final String paymentTransactionExternalKey,
+ final PaymentApiType paymentApiType,
+ final TransactionType transactionType,
+ final HPPType hppType,
+ final BigDecimal amount,
+ final Currency currency,
+ final BigDecimal processedAmount,
+ final Currency processedCurrency,
+ final boolean isApiPayment,
+ final List<String> paymentControlPluginNames,
+ final Iterable<PluginProperty> pluginProperties,
+ final CallContext callContext) {
+
+ final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account,
+ paymentMethodId,
+ paymentAttemptId,
+ paymentId,
+ paymentExternalKey,
+ transactionId,
+ paymentTransactionExternalKey,
+ paymentApiType,
+ transactionType,
+ hppType,
+ amount,
+ currency,
+ processedAmount,
+ processedCurrency,
+ isApiPayment,
+ callContext);
+ Iterable<PluginProperty> inputPluginProperties = pluginProperties;
+ for (final String pluginName : paymentControlPluginNames) {
+ final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+ if (plugin != null) {
+ try {
+ final OnSuccessPaymentControlResult result = plugin.onSuccessCall(inputPaymentControlContext, inputPluginProperties);
+ if (result.getAdjustedPluginProperties() != null) {
+ inputPluginProperties = result.getAdjustedPluginProperties();
+ }
+ // Exceptions from the control plugins are ignored (and logged) because the semantics on what to do are undefined.
+ } catch (final PaymentControlApiException e) {
+ log.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + inputPaymentControlContext.getPaymentExternalKey(), e);
+ } catch (final RuntimeException e) {
+ log.warn("Plugin " + pluginName + " failed to complete executePluginOnSuccessCalls call for " + inputPaymentControlContext.getPaymentExternalKey(), e);
+ }
+ }
+ }
+ return new DefaultOnSuccessPaymentControlResult(inputPluginProperties);
+ }
+
+ public OnFailurePaymentControlResult executePluginOnFailureCalls(final Account account,
+ final UUID paymentMethodId,
+ final UUID paymentAttemptId,
+ final UUID paymentId,
+ final String paymentExternalKey,
+ final String paymentTransactionExternalKey,
+ final PaymentApiType paymentApiType,
+ final TransactionType transactionType,
+ final HPPType hppType,
+ final BigDecimal amount,
+ final Currency currency,
+ final boolean isApiPayment,
+ final List<String> paymentControlPluginNames,
+ final Iterable<PluginProperty> pluginProperties,
+ final CallContext callContext) {
+
+ final PaymentControlContext inputPaymentControlContext = new DefaultPaymentControlContext(account,
+ paymentMethodId,
+ paymentAttemptId,
+ paymentId,
+ paymentExternalKey,
+ paymentTransactionExternalKey,
+ paymentApiType,
+ transactionType,
+ hppType,
+ amount,
+ currency,
+ isApiPayment,
+ callContext);
+
+ DateTime candidate = null;
+ Iterable<PluginProperty> inputPluginProperties = pluginProperties;
+
+ for (final String pluginName : paymentControlPluginNames) {
+ final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+ if (plugin != null) {
+ try {
+ final OnFailurePaymentControlResult result = plugin.onFailureCall(inputPaymentControlContext, inputPluginProperties);
+ if (candidate == null) {
+ candidate = result.getNextRetryDate();
+ } else if (result.getNextRetryDate() != null) {
+ candidate = candidate.compareTo(result.getNextRetryDate()) > 0 ? result.getNextRetryDate() : candidate;
+ }
+
+ if (result.getAdjustedPluginProperties() != null) {
+ inputPluginProperties = result.getAdjustedPluginProperties();
+ }
+
+ } catch (final PaymentControlApiException e) {
+ log.warn("Plugin " + pluginName + " failed to return next retryDate for payment " + inputPaymentControlContext.getPaymentExternalKey(), e);
+ return new DefaultFailureCallResult(candidate, inputPluginProperties);
+ }
+ }
+ }
+ return new DefaultFailureCallResult(candidate, inputPluginProperties);
+ }
+
+ public static class DefaultPaymentControlContext extends DefaultCallContext implements PaymentControlContext {
+
+ private final Account account;
+ private final UUID paymentMethodId;
+ private final UUID attemptId;
+ private final UUID paymentId;
+ private final String paymentExternalKey;
+ private final UUID transactionId;
+ private final String transactionExternalKey;
+ private final PaymentApiType paymentApiType;
+ private final HPPType hppType;
+ private final TransactionType transactionType;
+ private final BigDecimal amount;
+ private final Currency currency;
+ private final BigDecimal processedAmount;
+ private final Currency processedCurrency;
+ private final boolean isApiPayment;
+
+ public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, final String transactionExternalKey,
+ final PaymentApiType paymentApiType, final TransactionType transactionType, final HPPType hppType, final BigDecimal amount, final Currency currency,
+ final boolean isApiPayment, final CallContext callContext) {
+ this(account, paymentMethodId, attemptId, paymentId, paymentExternalKey, null, transactionExternalKey, paymentApiType, transactionType, hppType, amount, currency, null, null, isApiPayment, callContext);
+ }
+
+ public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, @Nullable final UUID transactionId, final String transactionExternalKey,
+ final PaymentApiType paymentApiType, final TransactionType transactionType, final HPPType hppType,
+ final BigDecimal amount, final Currency currency, @Nullable final BigDecimal processedAmount, @Nullable final Currency processedCurrency, final boolean isApiPayment, final CallContext callContext) {
+ super(callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(), callContext.getComments(), callContext.getUserToken(), callContext.getCreatedDate(), callContext.getUpdatedDate());
+ this.account = account;
+ this.paymentMethodId = paymentMethodId;
+ this.attemptId = attemptId;
+ this.paymentId = paymentId;
+ this.paymentExternalKey = paymentExternalKey;
+ this.transactionId = transactionId;
+ this.transactionExternalKey = transactionExternalKey;
+ this.paymentApiType = paymentApiType;
+ this.hppType = hppType;
+ this.transactionType = transactionType;
+ this.amount = amount;
+ this.currency = currency;
+ this.processedAmount = processedAmount;
+ this.processedCurrency = processedCurrency;
+ this.isApiPayment = isApiPayment;
+ }
+
+ @Override
+ public UUID getAccountId() {
+ return account.getId();
+ }
+
+ @Override
+ public String getPaymentExternalKey() {
+ return paymentExternalKey;
+ }
+
+ @Override
+ public String getTransactionExternalKey() {
+ return transactionExternalKey;
+ }
+
+ @Override
+ public PaymentApiType getPaymentApiType() {
+ return paymentApiType;
+ }
+
+ @Override
+ public TransactionType getTransactionType() {
+ return transactionType;
+ }
+
+ @Override
+ public HPPType getHPPType() {
+ return hppType;
+ }
+
+ @Override
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ @Override
+ public Currency getCurrency() {
+ return currency;
+ }
+
+ @Override
+ public UUID getPaymentMethodId() {
+ return paymentMethodId;
+ }
+
+ @Override
+ public UUID getPaymentId() {
+ return paymentId;
+ }
+
+ @Override
+ public UUID getAttemptPaymentId() {
+ return attemptId;
+ }
+
+ @Override
+ public BigDecimal getProcessedAmount() {
+ return processedAmount;
+ }
+
+ @Override
+ public Currency getProcessedCurrency() {
+ return processedCurrency;
+ }
+
+ @Override
+ public boolean isApiPayment() {
+ return isApiPayment;
+ }
+
+ public UUID getTransactionId() {
+ return transactionId;
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultPaymentControlContext{" +
+ "account=" + account +
+ ", paymentMethodId=" + paymentMethodId +
+ ", attemptId=" + attemptId +
+ ", paymentId=" + paymentId +
+ ", paymentExternalKey='" + paymentExternalKey + '\'' +
+ ", transactionId=" + transactionId +
+ ", transactionExternalKey='" + transactionExternalKey + '\'' +
+ ", paymentApiType=" + paymentApiType +
+ ", hppType=" + hppType +
+ ", transactionType=" + transactionType +
+ ", amount=" + amount +
+ ", currency=" + currency +
+ ", processedAmount=" + processedAmount +
+ ", processedCurrency=" + processedCurrency +
+ ", isApiPayment=" + isApiPayment +
+ '}';
+ }
+ }
+
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
index d0db210..1a071fa 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/CreditControlOperation.java
@@ -27,8 +27,8 @@ import org.killbill.commons.locker.GlobalLocker;
public class CreditControlOperation extends OperationControlCallback {
- public CreditControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
- super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ public CreditControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
}
@Override
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 e1c632f..a1e8928 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
@@ -17,9 +17,7 @@
package org.killbill.billing.payment.core.sm.control;
-import java.math.BigDecimal;
import java.util.List;
-import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
@@ -29,30 +27,24 @@ import org.joda.time.DateTime;
import org.killbill.automaton.Operation.OperationCallback;
import org.killbill.automaton.OperationException;
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.PaymentApiType;
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;
import org.killbill.billing.payment.api.PaymentTransaction;
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.PaymentProcessor;
import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
import org.killbill.billing.payment.core.sm.OperationCallbackBase;
import org.killbill.billing.payment.core.sm.PaymentStateContext;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext;
import org.killbill.billing.payment.dispatcher.PluginDispatcher;
import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
-import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult;
-import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.commons.locker.GlobalLocker;
import org.killbill.commons.locker.LockFailedException;
import org.slf4j.Logger;
@@ -62,15 +54,20 @@ import com.google.common.base.MoreObjects;
public abstract class OperationControlCallback extends OperationCallbackBase<Payment, PaymentApiException> implements OperationCallback {
+ private static final Logger logger = LoggerFactory.getLogger(OperationControlCallback.class);
+
protected final PaymentProcessor paymentProcessor;
protected final PaymentStateControlContext paymentStateControlContext;
- private final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry;
- private final Logger logger = LoggerFactory.getLogger(OperationControlCallback.class);
+ private final ControlPluginRunner controlPluginRunner;
- protected OperationControlCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry) {
+ protected OperationControlCallback(final GlobalLocker locker,
+ final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+ final PaymentStateControlContext paymentStateContext,
+ final PaymentProcessor paymentProcessor,
+ final ControlPluginRunner controlPluginRunner) {
super(locker, paymentPluginDispatcher, paymentStateContext);
this.paymentProcessor = paymentProcessor;
- this.paymentControlPluginRegistry = retryPluginRegistry;
+ this.controlPluginRunner = controlPluginRunner;
this.paymentStateControlContext = paymentStateContext;
}
@@ -91,7 +88,9 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
paymentStateContext.getPaymentId(),
paymentStateContext.getPaymentExternalKey(),
paymentStateContext.getPaymentTransactionExternalKey(),
+ PaymentApiType.PAYMENT_TRANSACTION,
paymentStateContext.getTransactionType(),
+ null,
paymentStateContext.getAmount(),
paymentStateContext.getCurrency(),
paymentStateControlContext.isApiPayment(),
@@ -124,7 +123,9 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
result.getExternalKey(),
transaction.getId(),
paymentStateContext.getPaymentTransactionExternalKey(),
+ PaymentApiType.PAYMENT_TRANSACTION,
paymentStateContext.getTransactionType(),
+ null,
transaction.getAmount(),
transaction.getCurrency(),
transaction.getProcessedAmount(),
@@ -177,67 +178,49 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
}
private PriorPaymentControlResult executePluginPriorCalls(final List<String> paymentControlPluginNames, final PaymentControlContext paymentControlContextArg) throws PaymentControlApiException {
- // Return as soon as the first plugin aborts, or the last result for the last plugin
- PriorPaymentControlResult prevResult = null;
-
- // Those values are adjusted prior each call with the result of what previous call to plugin returned
- PaymentControlContext inputPaymentControlContext = paymentControlContextArg;
- Iterable<PluginProperty> inputPluginProperties = paymentStateContext.getProperties();
-
- for (final String pluginName : paymentControlPluginNames) {
- final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
- if (plugin == null) {
- // First call to plugin, we log warn, if plugin is not registered
- logger.warn("Skipping unknown payment control plugin {} when fetching results", pluginName);
- continue;
- }
- prevResult = plugin.priorCall(inputPaymentControlContext, inputPluginProperties);
- if (prevResult.getAdjustedPluginProperties() != null) {
- inputPluginProperties = prevResult.getAdjustedPluginProperties();
- }
- if (prevResult.isAborted()) {
- break;
- }
- inputPaymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(),
- prevResult.getAdjustedPaymentMethodId() != null ? prevResult.getAdjustedPaymentMethodId() : inputPaymentControlContext.getPaymentMethodId(),
- paymentStateControlContext.getAttemptId(),
- paymentStateContext.getPaymentId(),
- paymentStateContext.getPaymentExternalKey(),
- paymentStateContext.getPaymentTransactionExternalKey(),
- paymentStateContext.getTransactionType(),
- prevResult.getAdjustedAmount() != null ? prevResult.getAdjustedAmount() : inputPaymentControlContext.getAmount(),
- prevResult.getAdjustedCurrency() != null ? prevResult.getAdjustedCurrency() : inputPaymentControlContext.getCurrency(),
- paymentStateControlContext.isApiPayment(),
- paymentStateContext.getCallContext());
- }
- // Rebuild latest result to include inputPluginProperties
- prevResult = new DefaultPriorPaymentControlResult(prevResult, inputPluginProperties);
- // Adjust context with all values if necessary
- adjustStateContextForPriorCall(paymentStateContext, prevResult);
- return prevResult;
+ final PriorPaymentControlResult result = controlPluginRunner.executePluginPriorCalls(paymentStateContext.getAccount(),
+ paymentControlContextArg.getPaymentMethodId(),
+ paymentStateControlContext.getAttemptId(),
+ paymentStateContext.getPaymentId(),
+ paymentStateContext.getPaymentExternalKey(),
+ paymentStateContext.getPaymentTransactionExternalKey(),
+ PaymentApiType.PAYMENT_TRANSACTION,
+ paymentStateContext.getTransactionType(),
+ null,
+ paymentControlContextArg.getAmount(),
+ paymentControlContextArg.getCurrency(),
+ paymentStateControlContext.isApiPayment(),
+ paymentControlPluginNames,
+ paymentStateContext.getProperties(),
+ paymentStateContext.getCallContext());
+
+ adjustStateContextForPriorCall(paymentStateContext, result);
+ return result;
}
protected void executePluginOnSuccessCalls(final List<String> paymentControlPluginNames, final PaymentControlContext paymentControlContext) {
-
- Iterable<PluginProperty> inputPluginProperties = paymentStateContext.getProperties();
- for (final String pluginName : paymentControlPluginNames) {
- final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
- if (plugin != null) {
- try {
- final OnSuccessPaymentControlResult result = plugin.onSuccessCall(paymentControlContext, inputPluginProperties);
- if (result.getAdjustedPluginProperties() != null) {
- inputPluginProperties = result.getAdjustedPluginProperties();
- }
- // Exceptions from 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);
- }
- }
- }
- adjustStateContextPluginProperties(paymentStateContext, inputPluginProperties);
+ // Values that were obtained/chnaged after the payment call was made (paymentId, processedAmount, processedCurrency,... needs to be extracted from the paymentControlContext)
+ // paymentId, paymentExternalKey, transactionAmount, transaction currency are extracted from paymentControlContext which was update from the operation result.
+ final OnSuccessPaymentControlResult result = controlPluginRunner.executePluginOnSuccessCalls(paymentStateContext.getAccount(),
+ paymentStateContext.getPaymentMethodId(),
+ paymentStateControlContext.getAttemptId(),
+ paymentControlContext.getPaymentId(),
+ paymentControlContext.getPaymentExternalKey(),
+ paymentControlContext.getTransactionId(),
+ paymentStateContext.getPaymentTransactionExternalKey(),
+ PaymentApiType.PAYMENT_TRANSACTION,
+ paymentStateContext.getTransactionType(),
+ null,
+ paymentControlContext.getAmount(),
+ paymentControlContext.getCurrency(),
+ paymentControlContext.getProcessedAmount(),
+ paymentControlContext.getProcessedCurrency(),
+ paymentStateControlContext.isApiPayment(),
+ paymentControlPluginNames,
+ paymentStateContext.getProperties(),
+ paymentStateContext.getCallContext());
+ adjustStateContextPluginProperties(paymentStateContext, result.getAdjustedPluginProperties());
}
private OperationResult executePluginOnFailureCallsAndSetRetryDate(final PaymentStateControlContext paymentStateControlContext, final PaymentControlContext paymentControlContext) {
@@ -250,32 +233,23 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
private DateTime executePluginOnFailureCalls(final List<String> paymentControlPluginNames, final PaymentControlContext paymentControlContext) {
- DateTime candidate = null;
- Iterable<PluginProperty> inputPluginProperties = paymentStateContext.getProperties();
-
- for (final String pluginName : paymentControlPluginNames) {
- final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
- if (plugin != null) {
- try {
- final OnFailurePaymentControlResult result = plugin.onFailureCall(paymentControlContext, inputPluginProperties);
- if (candidate == null) {
- candidate = result.getNextRetryDate();
- } else if (result.getNextRetryDate() != null) {
- candidate = candidate.compareTo(result.getNextRetryDate()) > 0 ? result.getNextRetryDate() : candidate;
- }
-
- if (result.getAdjustedPluginProperties() != null) {
- inputPluginProperties = result.getAdjustedPluginProperties();
- }
-
- } catch (final PaymentControlApiException e) {
- logger.warn("Plugin " + pluginName + " failed to return next retryDate for payment " + paymentControlContext.getPaymentExternalKey(), e);
- return candidate;
- }
- }
- }
- adjustStateContextPluginProperties(paymentStateContext, inputPluginProperties);
- return candidate;
+ final OnFailurePaymentControlResult result = controlPluginRunner.executePluginOnFailureCalls(paymentStateContext.getAccount(),
+ paymentControlContext.getPaymentMethodId(),
+ paymentStateControlContext.getAttemptId(),
+ paymentStateContext.getPaymentId(),
+ paymentStateContext.getPaymentExternalKey(),
+ paymentStateContext.getPaymentTransactionExternalKey(),
+ PaymentApiType.PAYMENT_TRANSACTION,
+ paymentStateContext.getTransactionType(),
+ null,
+ paymentControlContext.getAmount(),
+ paymentControlContext.getCurrency(),
+ paymentStateControlContext.isApiPayment(),
+ paymentControlPluginNames,
+ paymentStateContext.getProperties(),
+ paymentStateContext.getCallContext());
+ adjustStateContextPluginProperties(paymentStateContext, result.getAdjustedPluginProperties());
+ return result.getNextRetryDate();
}
private void adjustStateContextForPriorCall(final PaymentStateContext inputContext, @Nullable final PriorPaymentControlResult pluginResult) {
@@ -303,127 +277,4 @@ public abstract class OperationControlCallback extends OperationCallbackBase<Pay
final PaymentStateControlContext input = (PaymentStateControlContext) inputContext;
input.setProperties(pluginProperties);
}
-
- public static class DefaultPaymentControlContext extends DefaultCallContext implements PaymentControlContext {
-
- private final Account account;
- private final UUID paymentMethodId;
- private final UUID attemptId;
- private final UUID paymentId;
- private final String paymentExternalKey;
- private final UUID transactionId;
- private final String transactionExternalKey;
- private final TransactionType transactionType;
- private final BigDecimal amount;
- private final Currency currency;
- private final BigDecimal processedAmount;
- private final Currency processedCurrency;
- private final boolean isApiPayment;
-
- public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, final String transactionExternalKey, final TransactionType transactionType, final BigDecimal amount, final Currency currency,
- final boolean isApiPayment, final CallContext callContext) {
- this(account, paymentMethodId, attemptId, paymentId, paymentExternalKey, null, transactionExternalKey, transactionType, amount, currency, null, null, isApiPayment, callContext);
- }
-
- public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, @Nullable final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType,
- final BigDecimal amount, final Currency currency, @Nullable final BigDecimal processedAmount, @Nullable final Currency processedCurrency, final boolean isApiPayment, final CallContext callContext) {
- super(callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(), callContext.getComments(), callContext.getUserToken(), callContext.getCreatedDate(), callContext.getUpdatedDate());
- this.account = account;
- this.paymentMethodId = paymentMethodId;
- this.attemptId = attemptId;
- this.paymentId = paymentId;
- this.paymentExternalKey = paymentExternalKey;
- this.transactionId = transactionId;
- this.transactionExternalKey = transactionExternalKey;
- this.transactionType = transactionType;
- this.amount = amount;
- this.currency = currency;
- this.processedAmount = processedAmount;
- this.processedCurrency = processedCurrency;
- this.isApiPayment = isApiPayment;
- }
-
- @Override
- public UUID getAccountId() {
- return account.getId();
- }
-
- @Override
- public String getPaymentExternalKey() {
- return paymentExternalKey;
- }
-
- @Override
- public String getTransactionExternalKey() {
- return transactionExternalKey;
- }
-
- @Override
- public TransactionType getTransactionType() {
- return transactionType;
- }
-
- @Override
- public BigDecimal getAmount() {
- return amount;
- }
-
- @Override
- public Currency getCurrency() {
- return currency;
- }
-
- @Override
- public UUID getPaymentMethodId() {
- return paymentMethodId;
- }
-
- @Override
- public UUID getPaymentId() {
- return paymentId;
- }
-
- @Override
- public UUID getAttemptPaymentId() {
- return attemptId;
- }
-
- @Override
- public BigDecimal getProcessedAmount() {
- return processedAmount;
- }
-
- @Override
- public Currency getProcessedCurrency() {
- return processedCurrency;
- }
-
- @Override
- public boolean isApiPayment() {
- return isApiPayment;
- }
-
- public UUID getTransactionId() {
- return transactionId;
- }
-
- @Override
- public String toString() {
- return "DefaultPaymentControlContext{" +
- "account=" + account +
- ", paymentMethodId=" + paymentMethodId +
- ", attemptId=" + attemptId +
- ", paymentId=" + paymentId +
- ", paymentExternalKey='" + paymentExternalKey + '\'' +
- ", transactionId=" + transactionId +
- ", transactionExternalKey='" + transactionExternalKey + '\'' +
- ", transactionType=" + transactionType +
- ", amount=" + amount +
- ", currency=" + currency +
- ", processedAmount=" + processedAmount +
- ", processedCurrency=" + processedCurrency +
- ", isApiPayment=" + isApiPayment +
- '}';
- }
- }
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
index 602231e..7ad848f 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/PurchaseControlOperation.java
@@ -27,8 +27,8 @@ import org.killbill.commons.locker.GlobalLocker;
public class PurchaseControlOperation extends OperationControlCallback {
- public PurchaseControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry) {
- super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry);
+ public PurchaseControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
}
@Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
index d9152f0..5f4d7cd 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/RefundControlOperation.java
@@ -27,8 +27,8 @@ import org.killbill.commons.locker.GlobalLocker;
public class RefundControlOperation extends OperationControlCallback {
- public RefundControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
- super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ public RefundControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
}
@Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
index 67f4d9b..00200d1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/control/VoidControlOperation.java
@@ -27,8 +27,8 @@ import org.killbill.commons.locker.GlobalLocker;
public class VoidControlOperation extends OperationControlCallback {
- public VoidControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
- super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ public VoidControlOperation(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
}
@Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
index 9c1dbc7..f3ca0ca 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlPaymentAutomatonRunner.java
@@ -47,6 +47,7 @@ import org.killbill.billing.payment.core.sm.control.AuthorizeControlOperation;
import org.killbill.billing.payment.core.sm.control.CaptureControlOperation;
import org.killbill.billing.payment.core.sm.control.ChargebackControlOperation;
import org.killbill.billing.payment.core.sm.control.CompletionControlOperation;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
import org.killbill.billing.payment.core.sm.control.CreditControlOperation;
import org.killbill.billing.payment.core.sm.control.DefaultControlCompleted;
import org.killbill.billing.payment.core.sm.control.DefaultControlInitiated;
@@ -78,16 +79,19 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
private final PaymentProcessor paymentProcessor;
private final RetryServiceScheduler retryServiceScheduler;
private final PaymentControlStateMachineHelper paymentControlStateMachineHelper;
+ private final ControlPluginRunner controlPluginRunner;
@Inject
public PluginControlPaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry, final Clock clock, final PaymentProcessor paymentProcessor, @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler,
- final PaymentConfig paymentConfig, @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper, final PersistentBus eventBus) {
+ final PaymentConfig paymentConfig, @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper paymentControlStateMachineHelper,
+ final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) {
super(stateMachineConfig, paymentConfig, paymentDao, locker, pluginRegistry, clock, executor, eventBus, paymentSMHelper);
this.paymentProcessor = paymentProcessor;
this.paymentControlPluginRegistry = paymentControlPluginRegistry;
this.retryServiceScheduler = retryServiceScheduler;
this.paymentControlStateMachineHelper = paymentControlStateMachineHelper;
+ this.controlPluginRunner = controlPluginRunner;
}
public Payment run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
@@ -132,7 +136,7 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
public Payment completeRun(final PaymentStateControlContext paymentStateContext) throws PaymentApiException {
try {
- final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ final OperationCallback callback = new CompletionControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
final LeavingStateCallback leavingStateCallback = new NoopControlInitiated();
final EnteringStateCallback enteringStateCallback = new DefaultControlCompleted(this, paymentStateContext, paymentControlStateMachineHelper.getRetriedState(), retryServiceScheduler);
@@ -165,25 +169,25 @@ public class PluginControlPaymentAutomatonRunner extends PaymentAutomatonRunner
final OperationCallback callback;
switch (transactionType) {
case AUTHORIZE:
- callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ callback = new AuthorizeControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
break;
case CAPTURE:
- callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ callback = new CaptureControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
break;
case PURCHASE:
- callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ callback = new PurchaseControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
break;
case VOID:
- callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ callback = new VoidControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
break;
case CREDIT:
- callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ callback = new CreditControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
break;
case REFUND:
- callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ callback = new RefundControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
break;
case CHARGEBACK:
- callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+ callback = new ChargebackControlOperation(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
break;
default:
throw new IllegalStateException("Unsupported transaction type " + transactionType);
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
index fb3c06b..0c0b0a7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
@@ -47,6 +47,7 @@ import org.killbill.billing.payment.core.janitor.Janitor;
import org.killbill.billing.payment.core.sm.PaymentControlStateMachineHelper;
import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
import org.killbill.billing.payment.core.sm.PluginControlPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
import org.killbill.billing.payment.dao.DefaultPaymentDao;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.invoice.PaymentTagHandler;
@@ -125,6 +126,8 @@ public class PaymentModule extends KillBillModule {
bind(StateMachineProvider.class).annotatedWith(Names.named(STATE_MACHINE_PAYMENT)).toInstance(new StateMachineProvider(DEFAULT_STATE_MACHINE_PAYMENT_XML));
bind(StateMachineConfig.class).annotatedWith(Names.named(STATE_MACHINE_PAYMENT)).toProvider(Key.get(StateMachineProvider.class, Names.named(STATE_MACHINE_PAYMENT)));
bind(PaymentStateMachineHelper.class).asEagerSingleton();
+
+ bind(ControlPluginRunner.class).asEagerSingleton();
}
protected void installAutomatonRunner() {
diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
index 0484897..8510301 100644
--- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
@@ -33,6 +33,7 @@ import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.control.plugin.api.PaymentApiType;
import org.killbill.billing.invoice.api.Invoice;
import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
@@ -114,7 +115,9 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
@Override
public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> pluginProperties) throws PaymentControlApiException {
+
final TransactionType transactionType = paymentControlContext.getTransactionType();
+ Preconditions.checkArgument(paymentControlContext.getPaymentApiType() == PaymentApiType.PAYMENT_TRANSACTION);
Preconditions.checkArgument(transactionType == TransactionType.PURCHASE ||
transactionType == TransactionType.REFUND ||
transactionType == TransactionType.CHARGEBACK);
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java
index 91408b7..d1bb59d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultFailureCallResult.java
@@ -22,10 +22,20 @@ import org.killbill.billing.payment.api.PluginProperty;
public class DefaultFailureCallResult implements OnFailurePaymentControlResult {
+ private final Iterable<PluginProperty> adjustedPluginProperties;
private final DateTime nextRetryDate;
+ public DefaultFailureCallResult() {
+ this(null, null);
+ }
+
public DefaultFailureCallResult(final DateTime nextRetryDate) {
+ this(nextRetryDate, null);
+ }
+
+ public DefaultFailureCallResult(final DateTime nextRetryDate, final Iterable<PluginProperty> adjustedPluginProperties) {
this.nextRetryDate = nextRetryDate;
+ this.adjustedPluginProperties = adjustedPluginProperties;
}
@Override
@@ -35,6 +45,6 @@ public class DefaultFailureCallResult implements OnFailurePaymentControlResult {
@Override
public Iterable<PluginProperty> getAdjustedPluginProperties() {
- return null;
+ return adjustedPluginProperties;
}
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java
index 0060036..a97dc58 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultOnSuccessPaymentControlResult.java
@@ -22,8 +22,18 @@ import org.killbill.billing.payment.api.PluginProperty;
public class DefaultOnSuccessPaymentControlResult implements OnSuccessPaymentControlResult {
+ private final Iterable<PluginProperty> adjustedPluginProperties;
+
+ public DefaultOnSuccessPaymentControlResult() {
+ this(null);
+ }
+
+ public DefaultOnSuccessPaymentControlResult(final Iterable<PluginProperty> adjustedPluginProperties) {
+ this.adjustedPluginProperties = adjustedPluginProperties;
+ }
+
@Override
public Iterable<PluginProperty> getAdjustedPluginProperties() {
- return null;
+ return adjustedPluginProperties;
}
}
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 5d528a1..34f146d 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
@@ -46,7 +46,7 @@ import com.google.inject.Inject;
public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
@Inject
- private OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry;
+ private OSGIServiceRegistration<PaymentControlPluginApi> controlPluginRegistry;
private Account account;
private UUID newPaymentMethodId;
@@ -58,7 +58,7 @@ public class TestPaymentApiWithControl extends PaymentTestSuiteWithEmbeddedDB {
final PaymentMethodPlugin paymentMethodInfo = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), false, null);
newPaymentMethodId = paymentApi.addPaymentMethod(account, paymentMethodInfo.getExternalPaymentMethodId(), MockPaymentProviderPlugin.PLUGIN_NAME, false, paymentMethodInfo, ImmutableList.<PluginProperty>of(), callContext);
- retryPluginRegistry.registerService(new OSGIServiceDescriptor() {
+ controlPluginRegistry.registerService(new OSGIServiceDescriptor() {
@Override
public String getPluginSymbolicName() {
return null;
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java
new file mode 100644
index 0000000..fb29bff
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentGatewayApiWithPaymentControl.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.api;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.account.api.Account;
+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.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.killbill.billing.payment.retry.DefaultFailureCallResult;
+import org.killbill.billing.payment.retry.DefaultOnSuccessPaymentControlResult;
+import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+
+public class TestPaymentGatewayApiWithPaymentControl extends PaymentTestSuiteNoDB {
+
+ @Inject
+ private OSGIServiceRegistration<PaymentControlPluginApi> controlPluginRegistry;
+
+ private Account account;
+
+ private PaymentOptions paymentOptions;
+
+ private TestPaymentGatewayApiControlPlugin plugin;
+ private TestPaymentGatewayApiValidationPlugin validationPlugin;
+
+ @BeforeMethod(groups = "fast")
+ public void beforeMethod() throws Exception {
+
+ super.beforeMethod();
+
+ account = testHelper.createTestAccount("arthur@gmail.com", true);
+
+ paymentOptions = new PaymentOptions() {
+ @Override
+ public boolean isExternalPayment() {
+ return false;
+ }
+
+ @Override
+ public List<String> getPaymentControlPluginNames() {
+ return ImmutableList.of(TestPaymentGatewayApiControlPlugin.PLUGIN_NAME, TestPaymentGatewayApiValidationPlugin.VALIDATION_PLUGIN_NAME);
+ }
+ };
+
+ plugin = new TestPaymentGatewayApiControlPlugin();
+
+ controlPluginRegistry.registerService(new OSGIServiceDescriptor() {
+ @Override
+ public String getPluginSymbolicName() {
+ return null;
+ }
+
+ @Override
+ public String getRegistrationName() {
+ return TestPaymentGatewayApiControlPlugin.PLUGIN_NAME;
+ }
+ }, plugin);
+
+ validationPlugin = new TestPaymentGatewayApiValidationPlugin();
+ controlPluginRegistry.registerService(new OSGIServiceDescriptor() {
+ @Override
+ public String getPluginSymbolicName() {
+ return null;
+ }
+
+ @Override
+ public String getRegistrationName() {
+ return TestPaymentGatewayApiValidationPlugin.VALIDATION_PLUGIN_NAME;
+ }
+ }, validationPlugin);
+
+ }
+
+ @Test(groups = "fast")
+ public void testBuildFormDescriptorWithPaymentControl() throws PaymentApiException {
+
+ final List<PluginProperty> initialProperties = new ArrayList<PluginProperty>();
+ initialProperties.add(new PluginProperty("keyA", "valueA", true));
+ initialProperties.add(new PluginProperty("keyB", "valueB", true));
+ initialProperties.add(new PluginProperty("keyC", "valueC", true));
+
+ final List<PluginProperty> priorNewProperties = new ArrayList<PluginProperty>();
+ priorNewProperties.add(new PluginProperty("keyD", "valueD", true));
+ final List<PluginProperty> priorRemovedProperties = new ArrayList<PluginProperty>();
+ priorRemovedProperties.add(new PluginProperty("keyA", "valueA", true));
+ plugin.setPriorCallProperties(priorNewProperties, priorRemovedProperties);
+
+ final List<PluginProperty> onResultNewProperties = new ArrayList<PluginProperty>();
+ onResultNewProperties.add(new PluginProperty("keyE", "valueE", true));
+ final List<PluginProperty> onResultRemovedProperties = new ArrayList<PluginProperty>();
+ onResultRemovedProperties.add(new PluginProperty("keyB", "valueB", true));
+ plugin.setOnResultProperties(onResultNewProperties, onResultRemovedProperties);
+
+ final List<PluginProperty> expectedPriorCallProperties = new ArrayList<PluginProperty>();
+ expectedPriorCallProperties.add(new PluginProperty("keyB", "valueB", true));
+ expectedPriorCallProperties.add(new PluginProperty("keyC", "valueC", true));
+ expectedPriorCallProperties.add(new PluginProperty("keyD", "valueD", true));
+
+ validationPlugin.setExpectedPriorCallProperties(expectedPriorCallProperties);
+
+ final List<PluginProperty> expectedProperties = new ArrayList<PluginProperty>();
+ expectedProperties.add(new PluginProperty("keyC", "valueC", true));
+ expectedProperties.add(new PluginProperty("keyD", "valueD", true));
+ expectedProperties.add(new PluginProperty("keyE", "valueE", true));
+
+ validationPlugin.setExpectedProperties(expectedProperties);
+
+ paymentGatewayApi.buildFormDescriptorWithPaymentControl(account, account.getPaymentMethodId(), ImmutableList.<PluginProperty>of(), initialProperties, paymentOptions, callContext);
+
+ }
+
+ public static class TestPaymentGatewayApiValidationPlugin implements PaymentControlPluginApi {
+
+ public static final String VALIDATION_PLUGIN_NAME = "TestPaymentGatewayApiValidationPlugin";
+
+ private Iterable<PluginProperty> expectedPriorCallProperties;
+ private Iterable<PluginProperty> expectedProperties;
+
+ public TestPaymentGatewayApiValidationPlugin() {
+ this.expectedPriorCallProperties = ImmutableList.of();
+ this.expectedProperties = ImmutableList.of();
+ }
+
+ public void setExpectedProperties(final Iterable<PluginProperty> expectedProperties) {
+ this.expectedProperties = expectedProperties;
+ }
+
+ public void setExpectedPriorCallProperties(final List<PluginProperty> expectedPriorCallProperties) {
+ this.expectedPriorCallProperties = expectedPriorCallProperties;
+ }
+
+ @Override
+ public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+ validate(properties, expectedPriorCallProperties);
+ return new DefaultPriorPaymentControlResult(false);
+ }
+
+ @Override
+ public OnSuccessPaymentControlResult onSuccessCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+ validate(properties, expectedProperties);
+ return new DefaultOnSuccessPaymentControlResult();
+ }
+
+ @Override
+ public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+ validate(properties, expectedProperties);
+ return new DefaultFailureCallResult();
+ }
+
+ private static void validate(final Iterable<PluginProperty> properties, final Iterable<PluginProperty> expected) {
+ Assert.assertEquals(Iterables.size(properties), Iterables.size(expected), "Got " + Iterables.size(properties) + "properties" + ", expected " + Iterables.size(expected));
+
+ for (final PluginProperty curExpected : expected) {
+ Assert.assertTrue(Iterables.any(properties, new Predicate<PluginProperty>() {
+ @Override
+ public boolean apply(final PluginProperty input) {
+ return input.getKey().equals(curExpected.getKey()) && input.getValue().equals(curExpected.getValue());
+
+ }
+ }), "Cannot find expected property" + curExpected.getKey());
+ }
+ }
+
+ }
+
+ public static class TestPaymentGatewayApiControlPlugin implements PaymentControlPluginApi {
+
+ public static final String PLUGIN_NAME = "TestPaymentGatewayApiControlPlugin";
+
+ private Iterable<PluginProperty> newPriorCallProperties;
+ private Iterable<PluginProperty> removedPriorCallProperties;
+
+ private Iterable<PluginProperty> newOnResultProperties;
+ private Iterable<PluginProperty> removedOnResultProperties;
+
+ public TestPaymentGatewayApiControlPlugin() {
+ this.newPriorCallProperties = ImmutableList.of();
+ this.removedPriorCallProperties = ImmutableList.of();
+ this.newOnResultProperties = ImmutableList.of();
+ this.removedOnResultProperties = ImmutableList.of();
+ }
+
+ public void setPriorCallProperties(final Iterable<PluginProperty> newPriorCallProperties, final Iterable<PluginProperty> removedPriorCallProperties) {
+ this.newPriorCallProperties = newPriorCallProperties;
+ this.removedPriorCallProperties = removedPriorCallProperties;
+ }
+
+ public void setOnResultProperties(final Iterable<PluginProperty> newOnResultProperties, final Iterable<PluginProperty> removedOnResultProperties) {
+ this.newOnResultProperties = newOnResultProperties;
+ this.removedOnResultProperties = removedOnResultProperties;
+ }
+
+ @Override
+ public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+ return new DefaultPriorPaymentControlResult(false, null, null, null, getAdjustedProperties(properties, newPriorCallProperties, removedPriorCallProperties));
+ }
+
+ @Override
+ public OnSuccessPaymentControlResult onSuccessCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+ return new DefaultOnSuccessPaymentControlResult(getAdjustedProperties(properties, newOnResultProperties, removedOnResultProperties));
+ }
+
+ @Override
+ public OnFailurePaymentControlResult onFailureCall(final PaymentControlContext paymentControlContext, final Iterable<PluginProperty> properties) throws PaymentControlApiException {
+ return new DefaultFailureCallResult(null, getAdjustedProperties(properties, newOnResultProperties, removedOnResultProperties));
+ }
+
+ private static Iterable<PluginProperty> getAdjustedProperties(final Iterable<PluginProperty> input, final Iterable<PluginProperty> newProperties, final Iterable<PluginProperty> removedProperties) {
+ final Iterable<PluginProperty> filtered = Iterables.filter(input, new Predicate<PluginProperty>() {
+ @Override
+ public boolean apply(final PluginProperty p) {
+ final boolean toBeRemoved = Iterables.any(removedProperties, new Predicate<PluginProperty>() {
+ @Override
+ public boolean apply(final PluginProperty a) {
+ return a.getKey().equals(p.getKey()) && a.getValue().equals(p.getValue());
+ }
+ });
+ return !toBeRemoved;
+ }
+ });
+ return Iterables.concat(filtered, newProperties);
+ }
+ }
+
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
index 7a69891..14f6758 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
@@ -37,6 +37,7 @@ import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dispatcher.PluginDispatcher;
@@ -62,8 +63,8 @@ public class MockRetryablePaymentAutomatonRunner extends PluginControlPaymentAut
@Inject
public MockRetryablePaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, @Named(PaymentModule.STATE_MACHINE_RETRY) final StateMachineConfig retryStateMachine, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry, final Clock clock, final TagInternalApi tagApi, final PaymentProcessor paymentProcessor,
@Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, final PaymentConfig paymentConfig, @com.google.inject.name.Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
- final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper retrySMHelper, final PersistentBus eventBus) {
- super(stateMachineConfig, paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executor, paymentSMHelper, retrySMHelper, eventBus);
+ final PaymentStateMachineHelper paymentSMHelper, final PaymentControlStateMachineHelper retrySMHelper, final ControlPluginRunner controlPluginRunner, final PersistentBus eventBus) {
+ super(stateMachineConfig, paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executor, paymentSMHelper, retrySMHelper, controlPluginRunner, eventBus);
}
@Override
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
index f7007c0..3dd00a4 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
@@ -28,6 +28,7 @@ import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.core.PaymentProcessor;
import org.killbill.billing.payment.core.sm.control.AuthorizeControlOperation;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentModelDao;
@@ -45,8 +46,8 @@ public class MockRetryAuthorizeOperationCallback extends AuthorizeControlOperati
private Exception exception;
private OperationResult result;
- public MockRetryAuthorizeOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry, final PaymentDao paymentDao, final Clock clock) {
- super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry);
+ public MockRetryAuthorizeOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateControlContext paymentStateContext, final PaymentProcessor paymentProcessor, final ControlPluginRunner controlPluginRunner, final PaymentDao paymentDao, final Clock clock) {
+ super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, controlPluginRunner);
this.paymentDao = paymentDao;
this.clock = clock;
}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
index b016e7a..433806e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
@@ -41,6 +41,7 @@ import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.core.PaymentProcessor;
import org.killbill.billing.payment.core.PluginControlPaymentProcessor;
+import org.killbill.billing.payment.core.sm.control.ControlPluginRunner;
import org.killbill.billing.payment.core.sm.control.PaymentStateControlContext;
import org.killbill.billing.payment.dao.MockPaymentDao;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
@@ -109,6 +110,8 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
@Inject
private PaymentControlStateMachineHelper retrySMHelper;
@Inject
+ private ControlPluginRunner controlPluginRunner;
+ @Inject
private InternalCallContextFactory internalCallContextFactory;
private Account account;
@@ -169,6 +172,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
executor,
paymentSMHelper,
retrySMHelper,
+ controlPluginRunner,
eventBus);
paymentStateContext =
@@ -191,7 +195,7 @@ public class TestRetryablePayment extends PaymentTestSuiteNoDB {
runner.getPaymentPluginDispatcher(),
paymentStateContext,
null,
- runner.getRetryPluginRegistry(),
+ controlPluginRunner,
paymentDao,
clock);
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
index 778f279..9c19560 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
@@ -23,6 +23,7 @@ import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.payment.api.PaymentGatewayApi;
import org.killbill.billing.payment.core.PaymentMethodProcessor;
import org.killbill.billing.payment.core.PaymentProcessor;
import org.killbill.billing.payment.core.PluginControlPaymentProcessor;
@@ -62,6 +63,8 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
@Inject
protected PaymentApi paymentApi;
@Inject
+ protected PaymentGatewayApi paymentGatewayApi;
+ @Inject
protected AccountInternalApi accountInternalApi;
@Inject
protected TestPaymentHelper testHelper;
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
index 81c4813..1ab3f64 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
@@ -23,6 +23,7 @@ import org.killbill.billing.account.api.AccountInternalApi;
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.payment.api.PaymentGatewayApi;
import org.killbill.billing.payment.core.PaymentProcessor;
import org.killbill.billing.payment.core.PaymentMethodProcessor;
import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
@@ -60,6 +61,8 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
@Inject
protected PaymentApi paymentApi;
@Inject
+ protected PaymentGatewayApi paymentGatewayApi;
+ @Inject
protected AccountInternalApi accountApi;
@Inject
protected PaymentStateMachineHelper paymentSMHelper;
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index d61de93..6b6b458 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>org.kill-bill.billing</groupId>
- <version>0.33-SNAPSHOT</version>
+ <version>0.37</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.15.3-SNAPSHOT</version>
diff --git a/profiles/killbill/src/main/resources/killbill-server.properties b/profiles/killbill/src/main/resources/killbill-server.properties
index e418e11..734d17b 100644
--- a/profiles/killbill/src/main/resources/killbill-server.properties
+++ b/profiles/killbill/src/main/resources/killbill-server.properties
@@ -29,27 +29,24 @@ org.killbill.dao.logLevel=DEBUG
org.killbill.catalog.uri=SpyCarAdvanced.xml
# NotificationQ, Bus, ExtBus config
-org.killbill.notificationq.main.sleep=1000
-org.killbill.notificationq.main.claimed=10
-org.killbill.notificationq.main.sticky=true
+org.killbill.notificationq.main.sleep=100
+org.killbill.notificationq.main.claimed=1
+org.killbill.notificationq.main.queue.mode=STICKY_POLLING
+org.killbill.notificationq.main.notification.nbThreads=1
-org.killbill.persistent.bus.external.sticky=true
+org.killbill.persistent.bus.external.queue.mode=STICKY_EVENTS
org.killbill.persistent.bus.external.inMemory=true
-#org.killbill.persistent.bus.external.sticky=true
+#org.killbill.persistent.bus.external.queue.mode=STICKY_EVENTS
#org.killbill.persistent.bus.external.claimed=1
-#org.killbill.persistent.bus.external.inflight.claimed=1
#org.killbill.persistent.bus.external.nbThreads=1
#org.killbill.persistent.bus.external.sleep=0
-#org.killbill.persistent.bus.external.useInflightQ=true
#org.killbill.persistent.bus.external.queue.capacity=100
-org.killbill.persistent.bus.main.sticky=true
+org.killbill.persistent.bus.main.queue.mode=STICKY_EVENTS
org.killbill.persistent.bus.main.claimed=1
-org.killbill.persistent.bus.main.inflight.claimed=1
org.killbill.persistent.bus.main.nbThreads=1
org.killbill.persistent.bus.main.sleep=0
-org.killbill.persistent.bus.main.useInflightQ=true
org.killbill.persistent.bus.main.queue.capacity=100
# Start KB in multi-tenant
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java
new file mode 100644
index 0000000..11e19de
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestRL.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.Accounts;
+import org.killbill.billing.client.model.AuditLog;
+import org.killbill.billing.client.model.CustomField;
+import org.killbill.billing.client.model.InvoicePayments;
+import org.killbill.billing.client.model.PaymentMethod;
+import org.killbill.billing.client.model.PaymentMethodPluginDetail;
+import org.killbill.billing.client.model.Tag;
+import org.killbill.billing.util.api.AuditLevel;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class TestRL extends TestJaxrsBase {
+
+ @Test(groups = "slow", description = "Verify no PII data is required")
+ public void testEmptyAccount() throws Exception {
+ final Account emptyAccount = new Account();
+
+ final Account account = killBillClient.createAccount(emptyAccount, createdBy, reason, comment);
+ Assert.assertNotNull(account.getExternalKey());
+ Assert.assertNull(account.getName());
+ Assert.assertNull(account.getEmail());
+ }
+
+ @Test(groups = "slow", description = "Verify external key is unique")
+ public void testUniqueExternalKey() throws Exception {
+ // Verify the external key is not mandatory
+ final Account inputWithNoExternalKey = getAccount(UUID.randomUUID().toString(), null, UUID.randomUUID().toString());
+ Assert.assertNull(inputWithNoExternalKey.getExternalKey());
+
+ final Account account = killBillClient.createAccount(inputWithNoExternalKey, createdBy, reason, comment);
+ Assert.assertNotNull(account.getExternalKey());
+
+ final Account inputWithSameExternalKey = getAccount(UUID.randomUUID().toString(), account.getExternalKey(), UUID.randomUUID().toString());
+ try {
+ killBillClient.createAccount(inputWithSameExternalKey, createdBy, reason, comment);
+ Assert.fail();
+ } catch (final KillBillClientException e) {
+ Assert.assertEquals(e.getBillingException().getCode(), (Integer) ErrorCode.ACCOUNT_ALREADY_EXISTS.getCode());
+ }
+ }
+
+ @Test(groups = "slow", description = "Can create, retrieve, search and update accounts")
+ public void testAccountOk() throws Exception {
+ final Account input = createAccount();
+
+ // Retrieves by external key
+ final Account retrievedAccount = killBillClient.getAccount(input.getExternalKey());
+ Assert.assertTrue(retrievedAccount.equals(input));
+
+ // Try search endpoint
+ searchAccount(input, retrievedAccount);
+
+ // Update Account
+ final Account newInput = new Account(input.getAccountId(),
+ "zozo", 4, input.getExternalKey(), "rr@google.com", 18,
+ "USD", null, "UTC", "bl1", "bh2", "", "", "ca", "San Francisco", "usa", "en", "415-255-2991",
+ false, false, null, null);
+ final Account updatedAccount = killBillClient.updateAccount(newInput, createdBy, reason, comment);
+ Assert.assertTrue(updatedAccount.equals(newInput));
+
+ // Try search endpoint
+ searchAccount(input, null);
+ }
+
+ @Test(groups = "slow", description = "Can retrieve the account balance")
+ public void testAccountWithBalance() throws Exception {
+ final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+ final Account accountWithBalance = killBillClient.getAccount(accountJson.getAccountId(), true, false);
+ final BigDecimal accountBalance = accountWithBalance.getAccountBalance();
+ Assert.assertTrue(accountBalance.compareTo(BigDecimal.ZERO) > 0);
+ }
+
+ @Test(groups = "slow", description = "Cannot update a non-existent account")
+ public void testUpdateNonExistentAccount() throws Exception {
+ final Account input = getAccount();
+
+ Assert.assertNull(killBillClient.updateAccount(input, createdBy, reason, comment));
+ }
+
+ @Test(groups = "slow", description = "Cannot retrieve non-existent account")
+ public void testAccountNonExistent() throws Exception {
+ Assert.assertNull(killBillClient.getAccount(UUID.randomUUID()));
+ Assert.assertNull(killBillClient.getAccount(UUID.randomUUID().toString()));
+ }
+
+ @Test(groups = "slow", description = "Can CRUD payment methods")
+ public void testAccountPaymentMethods() throws Exception {
+ final Account accountJson = createAccount();
+ assertNotNull(accountJson);
+
+ final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
+ info.setProperties(getPaymentMethodCCProperties());
+ PaymentMethod paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), true, PLUGIN_NAME, info);
+ final PaymentMethod paymentMethodCC = killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment);
+ assertTrue(paymentMethodCC.getIsDefault());
+
+ //
+ // Add another payment method
+ //
+ final PaymentMethodPluginDetail info2 = new PaymentMethodPluginDetail();
+ info2.setProperties(getPaymentMethodPaypalProperties());
+ paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), false, PLUGIN_NAME, info2);
+ final PaymentMethod paymentMethodPP = killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment);
+ assertFalse(paymentMethodPP.getIsDefault());
+
+ //
+ // FETCH ALL PAYMENT METHODS
+ //
+ List<PaymentMethod> paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId());
+ assertEquals(paymentMethods.size(), 2);
+
+ //
+ // CHANGE DEFAULT
+ //
+ assertTrue(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault());
+ assertFalse(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault());
+ killBillClient.updateDefaultPaymentMethod(accountJson.getAccountId(), paymentMethodPP.getPaymentMethodId(), createdBy, reason, comment);
+ assertTrue(killBillClient.getPaymentMethod(paymentMethodPP.getPaymentMethodId()).getIsDefault());
+ assertFalse(killBillClient.getPaymentMethod(paymentMethodCC.getPaymentMethodId()).getIsDefault());
+
+ //
+ // DELETE NON DEFAULT PM
+ //
+ killBillClient.deletePaymentMethod(paymentMethodCC.getPaymentMethodId(), false, createdBy, reason, comment);
+
+ //
+ // FETCH ALL PAYMENT METHODS
+ //
+ paymentMethods = killBillClient.getPaymentMethodsForAccount(accountJson.getAccountId());
+ assertEquals(paymentMethods.size(), 1);
+
+ //
+ // DELETE DEFAULT PAYMENT METHOD (without special flag first)
+ //
+ try {
+ killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), false, createdBy, reason, comment);
+ fail();
+ } catch (final KillBillClientException e) {
+ }
+
+ //
+ // RETRY TO DELETE DEFAULT PAYMENT METHOD (with special flag this time)
+ //
+ killBillClient.deletePaymentMethod(paymentMethodPP.getPaymentMethodId(), true, createdBy, reason, comment);
+
+ // CHECK ACCOUNT IS NOW AUTO_PAY_OFF
+ final List<Tag> tagsJson = killBillClient.getAccountTags(accountJson.getAccountId());
+ Assert.assertEquals(tagsJson.size(), 1);
+ final Tag tagJson = tagsJson.get(0);
+ Assert.assertEquals(tagJson.getTagDefinitionName(), "AUTO_PAY_OFF");
+ Assert.assertEquals(tagJson.getTagDefinitionId(), new UUID(0, 1));
+
+ // FETCH ACCOUNT AGAIN AND CHECK THERE IS NO DEFAULT PAYMENT METHOD SET
+ final Account updatedAccount = killBillClient.getAccount(accountJson.getAccountId());
+ Assert.assertEquals(updatedAccount.getAccountId(), accountJson.getAccountId());
+ Assert.assertNull(updatedAccount.getPaymentMethodId());
+
+ //
+ // FINALLY TRY TO REMOVE AUTO_PAY_OFF WITH NO DEFAULT PAYMENT METHOD ON ACCOUNT
+ //
+ try {
+ killBillClient.deleteAccountTag(accountJson.getAccountId(), new UUID(0, 1), createdBy, reason, comment);
+ } catch (final KillBillClientException e) {
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testAccountPaymentsWithRefund() throws Exception {
+ final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+ // Verify payments
+ final InvoicePayments objFromJson = killBillClient.getInvoicePaymentsForAccount(accountJson.getAccountId());
+ Assert.assertEquals(objFromJson.size(), 1);
+ }
+
+ @Test(groups = "slow", description = "Add tags to account")
+ public void testTags() throws Exception {
+ final Account input = createAccount();
+ // Use tag definition for AUTO_PAY_OFF
+ final UUID autoPayOffId = new UUID(0, 1);
+
+ // Add a tag
+ killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment);
+
+ // Retrieves all tags
+ final List<Tag> tags1 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL);
+ Assert.assertEquals(tags1.size(), 1);
+ Assert.assertEquals(tags1.get(0).getTagDefinitionId(), autoPayOffId);
+
+ // Verify adding the same tag a second time doesn't do anything
+ killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment);
+
+ // Retrieves all tags again
+ killBillClient.createAccountTag(input.getAccountId(), autoPayOffId, createdBy, reason, comment);
+ final List<Tag> tags2 = killBillClient.getAccountTags(input.getAccountId(), AuditLevel.FULL);
+ Assert.assertEquals(tags2, tags1);
+
+ // Verify audit logs
+ Assert.assertEquals(tags2.get(0).getAuditLogs().size(), 1);
+ final AuditLog auditLogJson = tags2.get(0).getAuditLogs().get(0);
+ Assert.assertEquals(auditLogJson.getChangeType(), "INSERT");
+ Assert.assertEquals(auditLogJson.getChangedBy(), createdBy);
+ Assert.assertEquals(auditLogJson.getReasonCode(), reason);
+ Assert.assertEquals(auditLogJson.getComments(), comment);
+ Assert.assertNotNull(auditLogJson.getChangeDate());
+ Assert.assertNotNull(auditLogJson.getUserToken());
+ }
+
+ @Test(groups = "slow", description = "Add custom fields to account")
+ public void testCustomFields() throws Exception {
+ final Account accountJson = createAccount();
+ assertNotNull(accountJson);
+
+ final Collection<CustomField> customFields = new LinkedList<CustomField>();
+ customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "1", "value1", null));
+ customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "2", "value2", null));
+ customFields.add(new CustomField(null, accountJson.getAccountId(), ObjectType.ACCOUNT, "3", "value3", null));
+
+ killBillClient.createAccountCustomFields(accountJson.getAccountId(), customFields, createdBy, reason, comment);
+
+ final List<CustomField> accountCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId());
+ assertEquals(accountCustomFields.size(), 3);
+
+ // Delete all custom fields for account
+ killBillClient.deleteAccountCustomFields(accountJson.getAccountId(), createdBy, reason, comment);
+
+ final List<CustomField> remainingCustomFields = killBillClient.getAccountCustomFields(accountJson.getAccountId());
+ assertEquals(remainingCustomFields.size(), 0);
+ }
+
+ @Test(groups = "slow", description = "Can paginate through all accounts")
+ public void testAccountsPagination() throws Exception {
+ for (int i = 0; i < 5; i++) {
+ createAccount();
+ }
+
+ final Accounts allAccounts = killBillClient.getAccounts();
+ Assert.assertEquals(allAccounts.size(), 5);
+
+ Accounts page = killBillClient.getAccounts(0L, 1L);
+ for (int i = 0; i < 5; i++) {
+ Assert.assertNotNull(page);
+ Assert.assertEquals(page.size(), 1);
+ Assert.assertEquals(page.get(0), allAccounts.get(i));
+ page = page.getNext();
+ }
+ Assert.assertNull(page);
+ }
+
+ private void searchAccount(final Account input, @Nullable final Account output) throws Exception {
+ // Search by id
+ if (output != null) {
+ doSearchAccount(input.getAccountId().toString(), output);
+ }
+
+ // Search by name
+ doSearchAccount(input.getName(), output);
+
+ // Search by email
+ doSearchAccount(input.getEmail(), output);
+
+ // Search by company name
+ doSearchAccount(input.getCompany(), output);
+
+ // Search by external key.
+ // Note: we will always find a match since we don't update it
+ final List<Account> accountsByExternalKey = killBillClient.searchAccounts(input.getExternalKey());
+ Assert.assertEquals(accountsByExternalKey.size(), 1);
+ Assert.assertEquals(accountsByExternalKey.get(0).getAccountId(), input.getAccountId());
+ Assert.assertEquals(accountsByExternalKey.get(0).getExternalKey(), input.getExternalKey());
+ }
+
+ private void doSearchAccount(final String key, @Nullable final Account output) throws Exception {
+ final List<Account> accountsByKey = killBillClient.searchAccounts(key);
+ if (output == null) {
+ Assert.assertEquals(accountsByKey.size(), 0);
+ } else {
+ Assert.assertEquals(accountsByKey.size(), 1);
+ Assert.assertEquals(accountsByKey.get(0), output);
+ }
+ }
+}