use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.
the class PaymentAutomatonRunner method retrievePaymentId.
// TODO Could we cache these to avoid extra queries in PaymentAutomatonDAOHelper?
private UUID retrievePaymentId(@Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey, final InternalCallContext internalCallContext) throws PaymentApiException {
if (paymentExternalKey != null) {
final PaymentModelDao payment = paymentDao.getPaymentByExternalKey(paymentExternalKey, internalCallContext);
if (payment != null) {
return payment.getId();
}
}
if (paymentTransactionExternalKey == null) {
return null;
}
final List<PaymentTransactionModelDao> paymentTransactionModelDaos = paymentDao.getPaymentTransactionsByExternalKey(paymentTransactionExternalKey, internalCallContext);
for (final PaymentTransactionModelDao paymentTransactionModelDao : paymentTransactionModelDaos) {
if (paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.SUCCESS || paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PENDING || paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.UNKNOWN) {
return paymentTransactionModelDao.getPaymentId();
}
}
UUID paymentIdCandidate = null;
for (final PaymentTransactionModelDao paymentTransactionModelDao : paymentTransactionModelDaos) {
if (paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PAYMENT_FAILURE || paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PLUGIN_FAILURE) {
if (paymentIdCandidate == null) {
paymentIdCandidate = paymentTransactionModelDao.getPaymentId();
} else if (!paymentIdCandidate.equals(paymentTransactionModelDao.getPaymentId())) {
throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, "Multiple failed payments sharing the same transaction external key - this should never happen");
}
}
}
return paymentIdCandidate;
}
use of org.killbill.billing.payment.api.PaymentApiException 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.api.PaymentApiException in project killbill by killbill.
the class DefaultInvoicePaymentInternalApi method createPurchaseForInvoicePayment.
@Override
public InvoicePayment createPurchaseForInvoicePayment(final boolean isApiPayment, final Account account, final UUID invoiceId, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate, final String paymentExternalKey, final String paymentTransactionExternalKey, final Iterable<PluginProperty> originalProperties, final PaymentOptions paymentOptions, final InternalCallContext internalCallContext) throws PaymentApiException {
checkExternalKeyLength(paymentTransactionExternalKey);
final Collection<PluginProperty> pluginProperties = new LinkedList<PluginProperty>();
if (originalProperties != null) {
for (final PluginProperty pluginProperty : originalProperties) {
pluginProperties.add(pluginProperty);
}
}
pluginProperties.add(new PluginProperty("IPCD_INVOICE_ID", invoiceId.toString(), false));
final CallContext callContext = internalCallContextFactory.createCallContext(internalCallContext);
final List<String> defaultOrUserSpecifiedPaymentControlPluginNames = toPaymentControlPluginNames(paymentOptions, callContext);
final List<String> paymentControlPluginNames = InvoicePaymentPaymentOptions.addInvoicePaymentControlPlugin(defaultOrUserSpecifiedPaymentControlPluginNames);
final UUID resolvedPaymentMethodId = (paymentMethodId == null && paymentOptions.isExternalPayment()) ? paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, pluginProperties, callContext, internalCallContext) : paymentMethodId;
final String transactionType = TransactionType.PURCHASE.name();
Payment payment = null;
PaymentTransaction paymentTransaction = null;
PaymentApiException exception = null;
try {
logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
payment = pluginControlPaymentProcessor.createPurchase(isApiPayment, account, resolvedPaymentMethodId, paymentId, amount, currency, effectiveDate, paymentExternalKey, paymentTransactionExternalKey, pluginProperties, paymentControlPluginNames, callContext, internalCallContext);
paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
} catch (final PaymentApiException e) {
exception = e;
throw e;
} finally {
logExitAPICall(log, transactionType, account, payment != null ? payment.getPaymentMethodId() : null, payment != null ? payment.getId() : null, paymentTransaction != null ? paymentTransaction.getId() : null, paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null, paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null, payment != null ? payment.getExternalKey() : null, paymentTransaction != null ? paymentTransaction.getExternalKey() : null, paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null, paymentControlPluginNames, exception);
}
return paymentTransaction != null ? invoiceInternalApi.getInvoicePaymentByCookieId(paymentTransaction.getExternalKey(), callContext) : null;
}
use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.
the class PaymentAutomatonDAOHelper method getPayment.
public PaymentModelDao getPayment() throws PaymentApiException {
final PaymentModelDao paymentModelDao;
paymentModelDao = paymentDao.getPayment(paymentStateContext.getPaymentId(), internalCallContext);
if (paymentModelDao == null) {
throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentStateContext.getPaymentId());
}
return paymentModelDao;
}
use of org.killbill.billing.payment.api.PaymentApiException in project killbill by killbill.
the class IncompletePaymentAttemptTask method doIteration.
@Override
public void doIteration(final PaymentAttemptModelDao attempt) {
// We don't grab account lock here as the lock will be taken when calling the completeRun API.
final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(attempt.getTenantRecordId(), attempt.getAccountRecordId());
final CallContext callContext = createCallContext("AttemptCompletionJanitorTask", tenantContext);
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(attempt.getAccountId(), callContext);
final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(attempt.getTransactionExternalKey(), tenantContext);
final List<PaymentTransactionModelDao> filteredTransactions = ImmutableList.copyOf(Iterables.filter(transactions, new Predicate<PaymentTransactionModelDao>() {
@Override
public boolean apply(final PaymentTransactionModelDao input) {
return input.getAttemptId().equals(attempt.getId());
}
}));
// We only expect at most one transaction for a given attempt, but as a precaution we check for more; if this is the case we log a warn and continue processing the first one.
if (filteredTransactions.size() > 1) {
log.warn("Found {} transactions for paymentAttempt {}", filteredTransactions.size(), attempt.getId());
}
final PaymentTransactionModelDao transaction = filteredTransactions.isEmpty() ? null : filteredTransactions.get(0);
if (transaction == null) {
log.info("Moving attemptId='{}' to ABORTED", attempt.getId());
paymentDao.updatePaymentAttempt(attempt.getId(), attempt.getTransactionId(), "ABORTED", internalCallContext);
return;
}
// at which point the attempt can also be transition to a different state.
if (transaction.getTransactionStatus() == TransactionStatus.UNKNOWN) {
return;
}
try {
log.info("Completing attemptId='{}'", attempt.getId());
final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
// unclear
final boolean isApiPayment = true;
final PaymentStateControlContext paymentStateContext = new PaymentStateControlContext(attempt.toPaymentControlPluginNames(), isApiPayment, null, transaction.getPaymentId(), attempt.getPaymentExternalKey(), transaction.getId(), transaction.getTransactionExternalKey(), transaction.getTransactionType(), account, attempt.getPaymentMethodId(), transaction.getAmount(), transaction.getCurrency(), PluginPropertySerializer.deserialize(attempt.getPluginProperties()), internalCallContext, callContext);
// Normally set by leavingState Callback
paymentStateContext.setAttemptId(attempt.getId());
// Normally set by raw state machine
paymentStateContext.setPaymentTransactionModelDao(transaction);
//
// Will rerun the state machine with special callbacks to only make the executePluginOnSuccessCalls / executePluginOnFailureCalls calls
// to the PaymentControlPluginApi plugin and transition the state.
//
pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
} catch (final AccountApiException e) {
log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
} catch (final PluginPropertySerializerException e) {
log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
} catch (final PaymentApiException e) {
log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
}
}
Aggregations