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);
}
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(), ""));
}
}
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));
}
}
});
}
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);
}
}
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));
}
}
});
}
Aggregations