Search in sources :

Example 1 with PluginDispatcherReturnType

use of org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType in project killbill by killbill.

the class TestPluginDispatcher method testDispatchWithRequestData.

@Test(groups = "fast")
public void testDispatchWithRequestData() throws TimeoutException, PaymentApiException, ExecutionException, InterruptedException {
    final String requestId = "vive la vie et les coquillettes";
    final Callable<PluginDispatcherReturnType<String>> delegate = new Callable<PluginDispatcherReturnType<String>>() {

        @Override
        public PluginDispatcherReturnType<String> call() throws Exception {
            return PluginDispatcher.<String>createPluginDispatcherReturnType(Request.getPerThreadRequestData().getRequestId());
        }
    };
    final CallableWithRequestData<PluginDispatcherReturnType<String>> callable = new CallableWithRequestData<PluginDispatcherReturnType<String>>(new RequestData(requestId), UUIDs.getRandom(), null, null, MDC.getCopyOfContextMap(), delegate);
    final String actualRequestId = stringPluginDispatcher.dispatchWithTimeout(callable, 100, TimeUnit.MILLISECONDS);
    Assert.assertEquals(actualRequestId, requestId);
}
Also used : PluginDispatcherReturnType(org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType) RequestData(org.killbill.commons.request.RequestData) Callable(java.util.concurrent.Callable) Test(org.testng.annotations.Test)

Example 2 with PluginDispatcherReturnType

use of org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType in project killbill by killbill.

the class PaymentMethodProcessor method refreshPaymentMethods.

/**
 * This refreshed the payment methods from the plugin for cases when adding payment method does not flow through KB because of PCI compliance
 * issues. The logic below is not optimal because there is no atomicity in the step but the good news is that this is idempotent so can always be
 * replayed if necessary-- partial failure scenario.
 *
 * @param pluginName
 * @param account
 * @param context
 * @return the list of payment methods -- should be identical between KB, the plugin view-- if it keeps a state-- and the gateway.
 * @throws PaymentApiException
 */
public List<PaymentMethod> refreshPaymentMethods(final String pluginName, final Account account, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context) throws PaymentApiException {
    // Don't hold the account lock while fetching the payment methods from the gateway as those could change anyway
    final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
    final List<PaymentMethodInfoPlugin> pluginPms;
    try {
        pluginPms = pluginApi.getPaymentMethods(account.getId(), true, properties, callContext);
        // The method should never return null by convention, but let's not trust the plugin...
        if (pluginPms == null) {
            log.debug("No payment methods defined on the account {} for plugin {}", account.getId(), pluginName);
            return ImmutableList.<PaymentMethod>of();
        }
    } catch (final PaymentPluginApiException e) {
        throw new PaymentApiException(e, ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
    }
    try {
        final PluginDispatcherReturnType<List<PaymentMethod>> result = new WithAccountLock<List<PaymentMethod>, PaymentApiException>(paymentConfig).processAccountWithLock(locker, account.getId(), new DispatcherCallback<PluginDispatcherReturnType<List<PaymentMethod>>, PaymentApiException>() {

            @Override
            public PluginDispatcherReturnType<List<PaymentMethod>> doOperation() throws PaymentApiException {
                UUID defaultPaymentMethodId = null;
                final List<PaymentMethodInfoPlugin> pluginPmsWithId = new ArrayList<PaymentMethodInfoPlugin>();
                final List<PaymentMethodModelDao> finalPaymentMethods = new ArrayList<PaymentMethodModelDao>();
                for (final PaymentMethodInfoPlugin cur : pluginPms) {
                    // If the kbPaymentId is NULL, the plugin does not know about it, so we create a new UUID
                    final UUID paymentMethodId = cur.getPaymentMethodId() != null ? cur.getPaymentMethodId() : UUIDs.randomUUID();
                    final String externalKey = cur.getExternalPaymentMethodId() != null ? cur.getExternalPaymentMethodId() : paymentMethodId.toString();
                    final PaymentMethod input = new DefaultPaymentMethod(paymentMethodId, externalKey, account.getId(), pluginName);
                    final PaymentMethodModelDao pmModel = new PaymentMethodModelDao(input.getId(), input.getExternalKey(), input.getCreatedDate(), input.getUpdatedDate(), input.getAccountId(), input.getPluginName(), input.isActive());
                    finalPaymentMethods.add(pmModel);
                    pluginPmsWithId.add(new DefaultPaymentMethodInfoPlugin(cur, paymentMethodId));
                    // will always return false - it's Kill Bill in that case which is responsible to manage default payment methods
                    if (cur.isDefault()) {
                        defaultPaymentMethodId = paymentMethodId;
                    }
                }
                final List<PaymentMethodModelDao> refreshedPaymentMethods = paymentDao.refreshPaymentMethods(pluginName, finalPaymentMethods, context);
                try {
                    pluginApi.resetPaymentMethods(account.getId(), pluginPmsWithId, properties, callContext);
                } catch (final PaymentPluginApiException e) {
                    throw new PaymentApiException(e, ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
                }
                try {
                    updateDefaultPaymentMethodIfNeeded(pluginName, account, defaultPaymentMethodId, context);
                } catch (final AccountApiException e) {
                    throw new PaymentApiException(e);
                }
                final List<PaymentMethod> result = ImmutableList.<PaymentMethod>copyOf(Collections2.transform(refreshedPaymentMethods, new Function<PaymentMethodModelDao, PaymentMethod>() {

                    @Override
                    public PaymentMethod apply(final PaymentMethodModelDao input) {
                        return new DefaultPaymentMethod(input, null);
                    }
                }));
                return PluginDispatcher.createPluginDispatcherReturnType(result);
            }
        });
        return result.getReturnType();
    } catch (final Exception e) {
        throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, MoreObjects.firstNonNull(e.getMessage(), ""));
    }
}
Also used : PluginDispatcherReturnType(org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType) DefaultPaymentMethodInfoPlugin(org.killbill.billing.payment.provider.DefaultPaymentMethodInfoPlugin) PaymentPluginApiException(org.killbill.billing.payment.plugin.api.PaymentPluginApiException) PaymentMethodModelDao(org.killbill.billing.payment.dao.PaymentMethodModelDao) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) DefaultPaymentMethod(org.killbill.billing.payment.api.DefaultPaymentMethod) PaymentControlApiAbortException(org.killbill.billing.payment.core.sm.control.PaymentControlApiAbortException) PaymentControlApiException(org.killbill.billing.control.plugin.api.PaymentControlApiException) PaymentPluginApiException(org.killbill.billing.payment.plugin.api.PaymentPluginApiException) AccountApiException(org.killbill.billing.account.api.AccountApiException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) PaymentPluginApi(org.killbill.billing.payment.plugin.api.PaymentPluginApi) PaymentMethodInfoPlugin(org.killbill.billing.payment.plugin.api.PaymentMethodInfoPlugin) DefaultPaymentMethodInfoPlugin(org.killbill.billing.payment.provider.DefaultPaymentMethodInfoPlugin) AccountApiException(org.killbill.billing.account.api.AccountApiException) PaymentMethod(org.killbill.billing.payment.api.PaymentMethod) DefaultPaymentMethod(org.killbill.billing.payment.api.DefaultPaymentMethod) List(java.util.List) ArrayList(java.util.ArrayList) ImmutableList(com.google.common.collect.ImmutableList) UUID(java.util.UUID)

Example 3 with PluginDispatcherReturnType

use of org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType in project killbill by killbill.

the class OperationControlCallback method doOperationCallback.

@Override
public OperationResult doOperationCallback() throws OperationException {
    final List<String> pluginNameList = paymentStateControlContext.getPaymentControlPluginNames();
    final String pluginNames = JOINER.join(pluginNameList);
    return dispatchWithAccountLockAndTimeout(pluginNames, new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {

        @Override
        public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
            final Account account = paymentStateContext.getAccount();
            final UUID accountId = account != null ? account.getId() : null;
            final PaymentControlContext paymentControlContext = new DefaultPaymentControlContext(accountId, paymentStateContext.getPaymentMethodId(), paymentStateControlContext.getOriginalPaymentPluginName(), paymentStateControlContext.getAttemptId(), paymentStateContext.getPaymentId(), paymentStateContext.getPaymentExternalKey(), paymentStateContext.getTransactionId(), paymentStateContext.getPaymentTransactionExternalKey(), PaymentApiType.PAYMENT_TRANSACTION, paymentStateContext.getTransactionType(), null, paymentStateContext.getAmount(), paymentStateContext.getCurrency(), paymentStateControlContext.getProcessedAmount(), paymentStateControlContext.getProcessedCurrency(), paymentStateControlContext.isApiPayment(), paymentStateContext.getCallContext());
            try {
                executePluginPriorCalls(paymentStateControlContext.getPaymentControlPluginNames(), paymentControlContext);
            } catch (final PaymentControlApiAbortException e) {
                // Transition to ABORTED
                final PaymentApiException paymentAbortedException = new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_API_ABORTED, e.getPluginName());
                throw new OperationException(paymentAbortedException, OperationResult.EXCEPTION);
            } catch (final PaymentControlApiException e) {
                // Transition to ABORTED and throw PaymentControlApiException to caller.
                throw new OperationException(e, OperationResult.EXCEPTION);
            }
            final boolean success;
            try {
                final Payment result = doCallSpecificOperationCallback();
                ((PaymentStateControlContext) paymentStateContext).setResult(result);
                final PaymentTransaction transaction = ((PaymentStateControlContext) paymentStateContext).getCurrentTransaction();
                success = transaction.getTransactionStatus() == TransactionStatus.SUCCESS || transaction.getTransactionStatus() == TransactionStatus.PENDING;
                final PaymentControlContext updatedPaymentControlContext = new DefaultPaymentControlContext(accountId, paymentStateContext.getPaymentMethodId(), null, paymentStateControlContext.getAttemptId(), result.getId(), result.getExternalKey(), transaction.getId(), paymentStateContext.getPaymentTransactionExternalKey(), PaymentApiType.PAYMENT_TRANSACTION, paymentStateContext.getTransactionType(), null, transaction.getAmount(), transaction.getCurrency(), transaction.getProcessedAmount(), transaction.getProcessedCurrency(), paymentStateControlContext.isApiPayment(), paymentStateContext.getCallContext());
                if (success) {
                    executePluginOnSuccessCalls(paymentStateControlContext.getPaymentControlPluginNames(), updatedPaymentControlContext);
                    return PluginDispatcher.createPluginDispatcherReturnType(OperationResult.SUCCESS);
                } else {
                    throw new OperationException(null, executePluginOnFailureCallsAndSetRetryDate(updatedPaymentControlContext));
                }
            } catch (final PaymentApiException e) {
                // Wrap PaymentApiException, and throw a new OperationException with an ABORTED/FAILURE state based on the retry result.
                throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(paymentControlContext));
            } catch (final RuntimeException e) {
                // Attempts to set the retry date in context if needed.
                throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(paymentControlContext));
            }
        }
    });
}
Also used : PluginDispatcherReturnType(org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType) Account(org.killbill.billing.account.api.Account) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) PaymentControlApiException(org.killbill.billing.control.plugin.api.PaymentControlApiException) PaymentTransaction(org.killbill.billing.payment.api.PaymentTransaction) Payment(org.killbill.billing.payment.api.Payment) DefaultPaymentControlContext(org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext) PaymentControlContext(org.killbill.billing.control.plugin.api.PaymentControlContext) DefaultPaymentControlContext(org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext) UUID(java.util.UUID) OperationException(org.killbill.automaton.OperationException)

Example 4 with PluginDispatcherReturnType

use of org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType in project killbill by killbill.

the class OperationCallbackBase method dispatchWithAccountLockAndTimeout.

// 
// Dispatch the Callable to the executor by first wrapping it into a CallableWithAccountLock
// The dispatcher may throw a TimeoutException, ExecutionException, or InterruptedException; those will be handled in specific
// callback to eventually throw a OperationException, that will be used to drive the state machine in the right direction.
// 
protected <ExceptionType extends Exception> OperationResult dispatchWithAccountLockAndTimeout(final String pluginNames, final DispatcherCallback<PluginDispatcherReturnType<OperationResult>, ExceptionType> callback) throws OperationException {
    final Account account = paymentStateContext.getAccount();
    logger.debug("Dispatching plugin call for account {}", account.getExternalKey());
    try {
        final Callable<PluginDispatcherReturnType<OperationResult>> task = new CallableWithAccountLock<OperationResult, ExceptionType>(locker, account.getId(), paymentConfig, callback);
        final OperationResult operationResult = PaymentPluginDispatcher.dispatchWithExceptionHandling(account, pluginNames, task, paymentPluginDispatcher);
        return operationResult;
    } catch (final PaymentApiException e) {
        throw unwrapExceptionFromDispatchedTask(e);
    }
}
Also used : Account(org.killbill.billing.account.api.Account) PluginDispatcherReturnType(org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType) CallableWithAccountLock(org.killbill.billing.payment.core.ProcessorBase.CallableWithAccountLock) OperationResult(org.killbill.automaton.OperationResult) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException)

Example 5 with PluginDispatcherReturnType

use of org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType in project killbill by killbill.

the class CompletionControlOperation method doOperationCallback.

@Override
public OperationResult doOperationCallback() throws OperationException {
    final List<String> controlPluginNameList = paymentStateControlContext.getPaymentControlPluginNames();
    final String controlPluginNames = JOINER.join(controlPluginNameList);
    return dispatchWithAccountLockAndTimeout(controlPluginNames, new DispatcherCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {

        @Override
        public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
            final PaymentTransactionModelDao transaction = paymentStateContext.getPaymentTransactionModelDao();
            final Account account = paymentStateContext.getAccount();
            final UUID accountId = account != null ? account.getId() : null;
            final PaymentControlContext updatedPaymentControlContext = new DefaultPaymentControlContext(accountId, paymentStateContext.getPaymentMethodId(), null, paymentStateControlContext.getAttemptId(), transaction.getPaymentId(), paymentStateContext.getPaymentExternalKey(), transaction.getId(), paymentStateContext.getPaymentTransactionExternalKey(), PaymentApiType.PAYMENT_TRANSACTION, paymentStateContext.getTransactionType(), null, transaction.getAmount(), transaction.getCurrency(), transaction.getProcessedAmount(), transaction.getProcessedCurrency(), paymentStateControlContext.isApiPayment(), paymentStateContext.getCallContext());
            try {
                final Payment result = doCallSpecificOperationCallback();
                logger.debug("doOperationCallback payment='{}', transaction='{}'", result, transaction);
                ((PaymentStateControlContext) paymentStateContext).setResult(result);
                final boolean success = transaction.getTransactionStatus() == TransactionStatus.SUCCESS || transaction.getTransactionStatus() == TransactionStatus.PENDING;
                if (success) {
                    executePluginOnSuccessCalls(paymentStateControlContext.getPaymentControlPluginNames(), updatedPaymentControlContext);
                    // Remove scheduled retry, if any
                    paymentProcessor.cancelScheduledPaymentTransaction(paymentStateControlContext.getAttemptId(), paymentStateControlContext.getInternalCallContext());
                    return PluginDispatcher.createPluginDispatcherReturnType(OperationResult.SUCCESS);
                } else {
                    throw new OperationException(null, executePluginOnFailureCallsAndSetRetryDate(updatedPaymentControlContext));
                }
            } catch (final PaymentApiException e) {
                // Wrap PaymentApiException, and throw a new OperationException with an ABORTED/FAILURE state based on the retry result.
                throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(updatedPaymentControlContext));
            } catch (final RuntimeException e) {
                // Attempts to set the retry date in context if needed.
                throw new OperationException(e, executePluginOnFailureCallsAndSetRetryDate(updatedPaymentControlContext));
            }
        }
    });
}
Also used : PluginDispatcherReturnType(org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType) Account(org.killbill.billing.account.api.Account) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) Payment(org.killbill.billing.payment.api.Payment) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) DefaultPaymentControlContext(org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext) PaymentControlContext(org.killbill.billing.control.plugin.api.PaymentControlContext) DefaultPaymentControlContext(org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext) UUID(java.util.UUID) OperationException(org.killbill.automaton.OperationException)

Aggregations

PluginDispatcherReturnType (org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType)8 PaymentApiException (org.killbill.billing.payment.api.PaymentApiException)7 UUID (java.util.UUID)4 Account (org.killbill.billing.account.api.Account)3 ExecutionException (java.util.concurrent.ExecutionException)2 TimeoutException (java.util.concurrent.TimeoutException)2 OperationException (org.killbill.automaton.OperationException)2 PaymentControlApiException (org.killbill.billing.control.plugin.api.PaymentControlApiException)2 PaymentControlContext (org.killbill.billing.control.plugin.api.PaymentControlContext)2 Payment (org.killbill.billing.payment.api.Payment)2 DefaultPaymentControlContext (org.killbill.billing.payment.core.sm.control.ControlPluginRunner.DefaultPaymentControlContext)2 PaymentPluginApi (org.killbill.billing.payment.plugin.api.PaymentPluginApi)2 PaymentPluginApiException (org.killbill.billing.payment.plugin.api.PaymentPluginApiException)2 Test (org.testng.annotations.Test)2 ImmutableList (com.google.common.collect.ImmutableList)1 ArrayList (java.util.ArrayList)1 List (java.util.List)1 Callable (java.util.concurrent.Callable)1 OperationResult (org.killbill.automaton.OperationResult)1 AccountApiException (org.killbill.billing.account.api.AccountApiException)1