Skip to content

Commit

Permalink
Merge pull request #1216 from killbill/fix-for-1061
Browse files Browse the repository at this point in the history
Make Janitor notification processing invoke the control state machine
  • Loading branch information
sbrossie committed Sep 19, 2019
2 parents de2a198 + 4c90363 commit 7baaa3e
Show file tree
Hide file tree
Showing 14 changed files with 821 additions and 499 deletions.

Large diffs are not rendered by default.

@@ -1,6 +1,6 @@
/*
* Copyright 2014-2018 Groupon, Inc
* Copyright 2014-2018 The Billing Project, LLC
* Copyright 2014-2019 Groupon, Inc
* Copyright 2014-2019 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
Expand All @@ -25,6 +25,7 @@
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.payment.core.PaymentTransactionInfoPluginConverter;
import org.killbill.billing.payment.core.janitor.IncompletePaymentAttemptTask;
import org.killbill.billing.payment.core.sm.PaymentAutomatonDAOHelper;
import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
import org.killbill.billing.payment.dao.PaymentDao;
Expand Down Expand Up @@ -55,6 +56,8 @@ public DefaultAdminPaymentApi(final PaymentConfig paymentConfig,
this.internalCallContextFactory = internalCallContextFactory;
}

// Very similar implementation as the Janitor (see IncompletePaymentTransactionTask / IncompletePaymentAttemptTask)
// The code is different enough to make it difficult to share unfortunately
@Override
public void fixPaymentTransactionState(final Payment payment,
final PaymentTransaction paymentTransaction,
Expand Down Expand Up @@ -108,19 +111,24 @@ public void fixPaymentTransactionState(final Payment payment,
}
}

paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(),
null,
payment.getId(),
paymentTransaction.getTransactionType(),
final PaymentAutomatonDAOHelper paymentAutomatonDAOHelper = new PaymentAutomatonDAOHelper(paymentDao,
internalCallContext,
paymentSMHelper);
paymentAutomatonDAOHelper.processPaymentInfoPlugin(transactionStatus,
paymentTransaction.getPaymentInfoPlugin(),
currentPaymentStateName,
lastSuccessPaymentState,
// In case of success, and if we don't have any plugin details,
// assume the full amount was paid to correctly reconcile invoice payments
// See https://github.com/killbill/killbill/issues/1061#issuecomment-521911301
paymentTransaction.getAmount(),
paymentTransaction.getCurrency(),
payment.getAccountId(),
null,
payment.getId(),
paymentTransaction.getId(),
transactionStatus,
paymentTransaction.getProcessedAmount(),
paymentTransaction.getProcessedCurrency(),
paymentTransaction.getGatewayErrorCode(),
paymentTransaction.getGatewayErrorMsg(),
internalCallContext);
paymentTransaction.getTransactionType(),
true);

// If there is a payment attempt associated with that transaction, we need to update it as well
final List<PaymentAttemptModelDao> paymentAttemptsModelDao = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransaction.getExternalKey(), internalCallContext);
Expand Down
@@ -1,6 +1,6 @@
/*
* Copyright 2014-2018 Groupon, Inc
* Copyright 2014-2018 The Billing Project, LLC
* Copyright 2014-2019 Groupon, Inc
* Copyright 2014-2019 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
Expand All @@ -17,7 +17,7 @@

package org.killbill.billing.payment.core;

import java.util.List;
import java.util.UUID;

import javax.inject.Inject;

Expand All @@ -26,10 +26,9 @@
import org.killbill.billing.invoice.api.InvoiceInternalApi;
import org.killbill.billing.payment.core.janitor.IncompletePaymentAttemptTask;
import org.killbill.billing.payment.core.janitor.IncompletePaymentTransactionTask;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentModelDao;
import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
import org.killbill.billing.tag.TagInternalApi;
import org.killbill.billing.util.callcontext.InternalCallContextFactory;
import org.killbill.clock.Clock;
Expand All @@ -38,9 +37,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;

public class PaymentControlAwareRefresher extends PaymentRefresher {

private static final Logger log = LoggerFactory.getLogger(PaymentControlAwareRefresher.class);
Expand All @@ -64,20 +60,14 @@ public PaymentControlAwareRefresher(final PaymentPluginServiceRegistration payme
}

@Override
protected void onJanitorChange(final PaymentTransactionModelDao curPaymentTransactionModelDao, final InternalTenantContext internalTenantContext) {
// If there is a payment attempt associated with that transaction, we need to update it as well
final List<PaymentAttemptModelDao> paymentAttemptsModelDao = paymentDao.getPaymentAttemptByTransactionExternalKey(curPaymentTransactionModelDao.getTransactionExternalKey(), internalTenantContext);
final PaymentAttemptModelDao paymentAttemptModelDao = Iterables.<PaymentAttemptModelDao>tryFind(paymentAttemptsModelDao,
new Predicate<PaymentAttemptModelDao>() {
@Override
public boolean apply(final PaymentAttemptModelDao input) {
return curPaymentTransactionModelDao.getId().equals(input.getTransactionId());
}
}).orNull();
if (paymentAttemptModelDao != null) {
// We can re-use the logic from IncompletePaymentAttemptTask as it is doing very similar work (i.e. run the completion part of
// the state machine to call the plugins and update the attempt in the right terminal state)
incompletePaymentAttemptTask.doIteration(paymentAttemptModelDao);
}
protected boolean invokeJanitor(final UUID accountId,
final PaymentTransactionModelDao paymentTransactionModelDao,
final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin,
final InternalTenantContext internalTenantContext) {
return incompletePaymentAttemptTask.updatePaymentAndTransactionIfNeeded(accountId,
paymentTransactionModelDao.getId(),
paymentTransactionModelDao.getTransactionStatus(),
paymentTransactionInfoPlugin,
internalTenantContext);
}
}
@@ -1,6 +1,6 @@
/*
* Copyright 2014-2018 Groupon, Inc
* Copyright 2014-2018 The Billing Project, LLC
* Copyright 2014-2019 Groupon, Inc
* Copyright 2014-2019 The Billing Project, LLC
*
* The Billing Project licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
Expand Down Expand Up @@ -87,6 +87,7 @@
import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPagination;
import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationFromPlugins;

// Retrieve payment(s), making sure the Janitor is invoked (on-the-fly Janitor)
public class PaymentRefresher extends ProcessorBase {

private static final Logger log = LoggerFactory.getLogger(PaymentRefresher.class);
Expand All @@ -113,9 +114,18 @@ public PaymentRefresher(final PaymentPluginServiceRegistration paymentPluginServ
this.incompletePaymentTransactionTask = incompletePaymentTransactionTask;
}

protected void onJanitorChange(final PaymentTransactionModelDao curPaymentTransactionModelDao,
final InternalTenantContext internalTenantContext) {}
protected boolean invokeJanitor(final UUID accountId,
final PaymentTransactionModelDao paymentTransactionModelDao,
final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin,
final InternalTenantContext internalTenantContext) {
return incompletePaymentTransactionTask.updatePaymentAndTransactionIfNeeded(accountId,
paymentTransactionModelDao.getId(),
paymentTransactionModelDao.getTransactionStatus(),
paymentTransactionInfoPlugin,
internalTenantContext);
}

// Invoke the Janitor on-the-fly for all GET operations and for most payment operations (except notifyPendingPaymentOfStateChanged)
public PaymentModelDao invokeJanitor(final PaymentModelDao curPaymentModelDao,
final Collection<PaymentTransactionModelDao> curTransactionsModelDao,
@Nullable final Iterable<PaymentTransactionInfoPlugin> pluginTransactions,
Expand All @@ -129,12 +139,13 @@ public PaymentModelDao invokeJanitor(final PaymentModelDao curPaymentModelDao,
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);
final boolean hasChanged = invokeJanitor(newPaymentModelDao.getAccountId(),
newPaymentTransactionModelDao,
paymentTransactionInfoPlugin,
internalTenantContext);
if (hasChanged) {
newPaymentModelDao = paymentDao.getPayment(newPaymentModelDao.getId(), internalTenantContext);
newPaymentTransactionModelDao = paymentDao.getPaymentTransaction(newPaymentTransactionModelDao.getId(), internalTenantContext);

onJanitorChange(curPaymentTransactionModelDao, internalTenantContext);
}
} else {
log.warn("Unable to find transaction={} from pluginTransactions={}", curPaymentTransactionModelDao, pluginTransactions);
Expand Down Expand Up @@ -400,7 +411,7 @@ private Payment toPayment(final PaymentModelDao paymentModelDao, @Nullable final
return toPayment(paymentModelDao, transactionsForPayment, pluginTransactions, withAttempts, tenantContextWithAccountRecordId);
}

// Used in bulk get API (getAccountPayments)
// Used in both single get APIs and bulk get APIs
private Payment toPayment(final PaymentModelDao curPaymentModelDao, final Collection<PaymentTransactionModelDao> allTransactionsModelDao, @Nullable final Iterable<PaymentTransactionInfoPlugin> pluginTransactions, final boolean withAttempts, final InternalTenantContext internalTenantContext) {
// Need to filter for optimized codepaths looking up by account_record_id
final Collection<PaymentTransactionModelDao> transactionsModelDao = new LinkedList<PaymentTransactionModelDao>(Collections2.filter(allTransactionsModelDao, new Predicate<PaymentTransactionModelDao>() {
Expand Down

0 comments on commit 7baaa3e

Please sign in to comment.