Search in sources :

Example 6 with PaymentTransactionModelDao

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);
}
Also used : Payment(org.killbill.billing.payment.api.Payment) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) BigDecimal(java.math.BigDecimal) Test(org.testng.annotations.Test)

Example 7 with PaymentTransactionModelDao

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);
}
Also used : Payment(org.killbill.billing.payment.api.Payment) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) BigDecimal(java.math.BigDecimal) Test(org.testng.annotations.Test)

Example 8 with PaymentTransactionModelDao

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;
    }
}
Also used : PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao)

Example 9 with PaymentTransactionModelDao

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);
}
Also used : PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) LinkedList(java.util.LinkedList)

Example 10 with PaymentTransactionModelDao

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;
}
Also used : PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PaymentModelDao(org.killbill.billing.payment.dao.PaymentModelDao) PaymentTransactionInfoPlugin(org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin) LinkedList(java.util.LinkedList)

Aggregations

PaymentTransactionModelDao (org.killbill.billing.payment.dao.PaymentTransactionModelDao)39 PaymentModelDao (org.killbill.billing.payment.dao.PaymentModelDao)17 PaymentApiException (org.killbill.billing.payment.api.PaymentApiException)16 Test (org.testng.annotations.Test)16 UUID (java.util.UUID)12 BigDecimal (java.math.BigDecimal)10 PaymentAttemptModelDao (org.killbill.billing.payment.dao.PaymentAttemptModelDao)10 Predicate (com.google.common.base.Predicate)8 Payment (org.killbill.billing.payment.api.Payment)7 Account (org.killbill.billing.account.api.Account)6 PaymentTransactionInfoPlugin (org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin)6 ImmutableList (com.google.common.collect.ImmutableList)5 List (java.util.List)5 LinkedList (java.util.LinkedList)4 LocalDate (org.joda.time.LocalDate)4 Invoice (org.killbill.billing.invoice.api.Invoice)4 PluginProperty (org.killbill.billing.payment.api.PluginProperty)4 ArrayList (java.util.ArrayList)3 TimeoutException (java.util.concurrent.TimeoutException)3 OperationException (org.killbill.automaton.OperationException)3