use of org.killbill.billing.payment.dao.PaymentTransactionModelDao in project killbill by killbill.
the class TestJanitor method testUnknownEntriesWithFailures.
@Test(groups = "slow")
public void testUnknownEntriesWithFailures() throws PaymentApiException, EventBusException {
final BigDecimal requestedAmount = BigDecimal.TEN;
final String paymentExternalKey = "minus";
final String transactionExternalKey = "plus";
// Make sure the state as seen by the plugin will be in PaymentPluginStatus.ERROR, which will be returned later to Janitor
mockPaymentProviderPlugin.makeNextPaymentFailWithError();
testListener.pushExpectedEvent(NextEvent.PAYMENT_ERROR);
final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
testListener.assertListenerStatus();
// Artificially move the transaction status to UNKNOWN
final String paymentStateName = paymentSMHelper.getErroredStateForTransaction(TransactionType.AUTHORIZE).toString();
testListener.pushExpectedEvent(NextEvent.PAYMENT_PLUGIN_ERROR);
paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), null, payment.getId(), TransactionType.AUTHORIZE, paymentStateName, paymentStateName, payment.getTransactions().get(0).getId(), TransactionStatus.UNKNOWN, requestedAmount, account.getCurrency(), "foo", "bar", internalCallContext);
testListener.assertListenerStatus();
final List<PaymentTransactionModelDao> paymentTransactionHistoryBeforeJanitor = getPaymentTransactionHistory(transactionExternalKey);
Assert.assertEquals(paymentTransactionHistoryBeforeJanitor.size(), 3);
// Move clock for notification to be processed
testListener.pushExpectedEvent(NextEvent.PAYMENT_ERROR);
clock.addDeltaFromReality(5 * 60 * 1000);
assertNotificationsCompleted(internalCallContext, 5);
testListener.assertListenerStatus();
// Proves the Janitor ran (and updated the transaction)
final List<PaymentTransactionModelDao> paymentTransactionHistoryAfterJanitor = getPaymentTransactionHistory(transactionExternalKey);
Assert.assertEquals(paymentTransactionHistoryAfterJanitor.size(), 4);
Assert.assertEquals(paymentTransactionHistoryAfterJanitor.get(3).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
final Payment updatedPayment = paymentApi.getPayment(payment.getId(), false, false, ImmutableList.<PluginProperty>of(), callContext);
// Janitor should have moved us to PAYMENT_FAILURE
assertEquals(updatedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
}
use of org.killbill.billing.payment.dao.PaymentTransactionModelDao in project killbill by killbill.
the class TestJanitor method testUnknownEntriesWithExceptions.
@Test(groups = "slow")
public void testUnknownEntriesWithExceptions() throws PaymentApiException, EventBusException {
final BigDecimal requestedAmount = BigDecimal.TEN;
final String paymentExternalKey = "minus";
final String transactionExternalKey = "plus";
// Make sure the state as seen by the plugin will be in PaymentPluginStatus.ERROR, which will be returned later to Janitor
mockPaymentProviderPlugin.makeNextPaymentFailWithException();
try {
testListener.pushExpectedEvent(NextEvent.PAYMENT_PLUGIN_ERROR);
paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(), paymentExternalKey, transactionExternalKey, ImmutableList.<PluginProperty>of(), callContext);
} catch (PaymentApiException ignore) {
testListener.assertListenerStatus();
}
final Payment payment = paymentApi.getPaymentByExternalKey(paymentExternalKey, false, false, ImmutableList.<PluginProperty>of(), callContext);
// Artificially move the transaction status to UNKNOWN
final String paymentStateName = paymentSMHelper.getErroredStateForTransaction(TransactionType.AUTHORIZE).toString();
testListener.pushExpectedEvent(NextEvent.PAYMENT_PLUGIN_ERROR);
paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), null, payment.getId(), TransactionType.AUTHORIZE, paymentStateName, paymentStateName, payment.getTransactions().get(0).getId(), TransactionStatus.UNKNOWN, requestedAmount, account.getCurrency(), "foo", "bar", internalCallContext);
testListener.assertListenerStatus();
// Move clock for notification to be processed
clock.addDeltaFromReality(5 * 60 * 1000);
// NO because we will keep retrying as we can't fix it...
//assertNotificationsCompleted(internalCallContext, 5);
final List<PaymentTransactionModelDao> paymentTransactionHistoryBeforeJanitor = getPaymentTransactionHistory(transactionExternalKey);
Assert.assertEquals(paymentTransactionHistoryBeforeJanitor.size(), 3);
// Nothing new happened
final List<PaymentTransactionModelDao> paymentTransactionHistoryAfterJanitor = getPaymentTransactionHistory(transactionExternalKey);
Assert.assertEquals(paymentTransactionHistoryAfterJanitor.size(), 3);
}
use of org.killbill.billing.payment.dao.PaymentTransactionModelDao in project killbill by killbill.
the class InvoicePaymentControlPluginApi method computeNextRetryDate.
private DateTime computeNextRetryDate(final String paymentExternalKey, final boolean isApiAPayment, final InternalCallContext internalContext) {
// Don't retry call that come from API.
if (isApiAPayment) {
return null;
}
final List<PaymentTransactionModelDao> purchasedTransactions = getPurchasedTransactions(paymentExternalKey, internalContext);
if (purchasedTransactions.size() == 0) {
return null;
}
final PaymentTransactionModelDao lastTransaction = purchasedTransactions.get(purchasedTransactions.size() - 1);
switch(lastTransaction.getTransactionStatus()) {
case PAYMENT_FAILURE:
return getNextRetryDateForPaymentFailure(purchasedTransactions, internalContext);
case PLUGIN_FAILURE:
return getNextRetryDateForPluginFailure(purchasedTransactions, internalContext);
case UNKNOWN:
default:
return null;
}
}
use of org.killbill.billing.payment.dao.PaymentTransactionModelDao in project killbill by killbill.
the class PaymentProcessor method findTransactionToCompleteAndRunSanityChecks.
private PaymentTransactionModelDao findTransactionToCompleteAndRunSanityChecks(final PaymentModelDao paymentModelDao, final Iterable<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment, final PaymentStateContext paymentStateContext, final InternalCallContext internalCallContext) throws PaymentApiException {
final Collection<PaymentTransactionModelDao> completionCandidates = new LinkedList<PaymentTransactionModelDao>();
for (final PaymentTransactionModelDao paymentTransactionModelDao : paymentTransactionsForCurrentPayment) {
// Check if we already have a transaction for that id or key
if (!(paymentStateContext.getTransactionId() != null && paymentTransactionModelDao.getId().equals(paymentStateContext.getTransactionId())) && !(paymentStateContext.getPaymentTransactionExternalKey() != null && paymentTransactionModelDao.getTransactionExternalKey().equals(paymentStateContext.getPaymentTransactionExternalKey()))) {
// Sanity: if not, prevent multiple PENDING transactions for initial calls (cannot be enforced by the state machine unfortunately)
if ((paymentTransactionModelDao.getTransactionType() == TransactionType.AUTHORIZE || paymentTransactionModelDao.getTransactionType() == TransactionType.PURCHASE || paymentTransactionModelDao.getTransactionType() == TransactionType.CREDIT) && paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PENDING) {
throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_OPERATION, paymentTransactionModelDao.getTransactionType(), paymentModelDao.getStateName());
} else {
continue;
}
}
// Sanity: if we already have a transaction for that id or key, the transaction type must match
if (paymentTransactionModelDao.getTransactionType() != paymentStateContext.getTransactionType()) {
throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "transactionType", String.format("%s doesn't match existing transaction type %s", paymentStateContext.getTransactionType(), paymentTransactionModelDao.getTransactionType()));
}
// UNKNOWN transactions are potential candidates, we'll invoke the Janitor first though
if (paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PENDING || paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.UNKNOWN) {
completionCandidates.add(paymentTransactionModelDao);
}
}
Preconditions.checkState(Iterables.<PaymentTransactionModelDao>size(completionCandidates) <= 1, "There should be at most one completion candidate");
return Iterables.<PaymentTransactionModelDao>getLast(completionCandidates, null);
}
use of org.killbill.billing.payment.dao.PaymentTransactionModelDao in project killbill by killbill.
the class PaymentProcessor method invokeJanitor.
private PaymentModelDao invokeJanitor(final PaymentModelDao curPaymentModelDao, final Collection<PaymentTransactionModelDao> curTransactionsModelDao, @Nullable final Iterable<PaymentTransactionInfoPlugin> pluginTransactions, final InternalTenantContext internalTenantContext) {
// Need to filter for optimized codepaths looking up by account_record_id
final Iterable<PaymentTransactionModelDao> filteredTransactions = Iterables.filter(curTransactionsModelDao, new Predicate<PaymentTransactionModelDao>() {
@Override
public boolean apply(final PaymentTransactionModelDao curPaymentTransactionModelDao) {
return curPaymentTransactionModelDao.getPaymentId().equals(curPaymentModelDao.getId());
}
});
PaymentModelDao newPaymentModelDao = curPaymentModelDao;
final Collection<PaymentTransactionModelDao> transactionsModelDao = new LinkedList<PaymentTransactionModelDao>();
for (final PaymentTransactionModelDao curPaymentTransactionModelDao : filteredTransactions) {
PaymentTransactionModelDao newPaymentTransactionModelDao = curPaymentTransactionModelDao;
final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin = findPaymentTransactionInfoPlugin(newPaymentTransactionModelDao, pluginTransactions);
if (paymentTransactionInfoPlugin != null) {
// Make sure to invoke the Janitor task in case the plugin fixes its state on the fly
// See https://github.com/killbill/killbill/issues/341
final boolean hasChanged = incompletePaymentTransactionTask.updatePaymentAndTransactionIfNeededWithAccountLock(newPaymentModelDao, newPaymentTransactionModelDao, paymentTransactionInfoPlugin, internalTenantContext);
if (hasChanged) {
newPaymentModelDao = paymentDao.getPayment(newPaymentModelDao.getId(), internalTenantContext);
newPaymentTransactionModelDao = paymentDao.getPaymentTransaction(newPaymentTransactionModelDao.getId(), internalTenantContext);
}
}
transactionsModelDao.add(newPaymentTransactionModelDao);
}
curTransactionsModelDao.clear();
curTransactionsModelDao.addAll(transactionsModelDao);
return newPaymentModelDao;
}
Aggregations