killbill-memoizeit

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
index 7c0a317..a2728ec 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestInvoicePayment.java
@@ -335,7 +335,7 @@ public class TestInvoicePayment extends TestIntegrationBase {
 
         // Trigger chargeback in the original currency
         payment1 = createChargeBackAndCheckForCompletion(account, payment1, new BigDecimal("225.44"), Currency.EUR, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
-        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(new BigDecimal("24.51")), 0);
+        Assert.assertEquals(payment1.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
         Assert.assertEquals(payment1.getTransactions().size(), 2);
         Assert.assertEquals(payment1.getTransactions().get(0).getAmount().compareTo(new BigDecimal("249.95")), 0);
         Assert.assertEquals(payment1.getTransactions().get(0).getCurrency(), Currency.USD);
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
index 33b8730..7303f80 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
@@ -30,7 +30,10 @@ import org.joda.time.DateTime;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.entity.EntityBase;
 
+import com.google.common.base.Function;
 import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
@@ -80,13 +83,77 @@ public class DefaultPayment extends EntityBase implements Payment {
             }
         }
 
-        final BigDecimal chargebackAmount = getChargebackAmount(transactions);
-
-        this.authAmount = getAmountForType(nonVoidedTransactions, TransactionType.AUTHORIZE);
-        this.captureAmount = getAmountForType(nonVoidedTransactions, TransactionType.CAPTURE).add(chargebackAmount.negate()).max(BigDecimal.ZERO);
-        this.purchasedAmount = getAmountForType(nonVoidedTransactions, TransactionType.PURCHASE).add(chargebackAmount.negate()).max(BigDecimal.ZERO);
-        this.creditAmount = getAmountForType(nonVoidedTransactions, TransactionType.CREDIT);
-        this.refundAmount = getAmountForType(nonVoidedTransactions, TransactionType.REFUND);
+        final Collection<PaymentTransaction> chargebackTransactions = getChargebackTransactions(transactions);
+        final Currency chargebackProcessedCurrency = getCurrencyForTransactions(chargebackTransactions, true);
+        final BigDecimal chargebackProcessedAmount = chargebackProcessedCurrency == null ? BigDecimal.ZERO : getAmountForTransactions(chargebackTransactions, true);
+        final Currency chargebackCurrency = getCurrencyForTransactions(chargebackTransactions, false);
+        final BigDecimal chargebackAmount = chargebackCurrency == null ? BigDecimal.ZERO : getAmountForTransactions(chargebackTransactions, false);
+
+        PaymentTransaction transactionToUseForCurrency = Iterables.<PaymentTransaction>getFirst(Iterables.<PaymentTransaction>filter(transactions,
+                                                                                                                                     new Predicate<PaymentTransaction>() {
+                                                                                                                                         @Override
+                                                                                                                                         public boolean apply(final PaymentTransaction transaction) {
+                                                                                                                                             return (transaction.getTransactionType() == TransactionType.AUTHORIZE ||
+                                                                                                                                                     transaction.getTransactionType() == TransactionType.PURCHASE ||
+                                                                                                                                                     transaction.getTransactionType() == TransactionType.CREDIT) &&
+                                                                                                                                                    (TransactionStatus.SUCCESS.equals(transaction.getTransactionStatus()) ||
+                                                                                                                                                     TransactionStatus.PENDING.equals(transaction.getTransactionStatus()));
+                                                                                                                                         }
+                                                                                                                                     }), null);
+        if (transactionToUseForCurrency == null) {
+            // No successful one, take the last non-successful one then
+            transactionToUseForCurrency = Iterables.<PaymentTransaction>getLast(Iterables.<PaymentTransaction>filter(transactions,
+                                                                                                                     new Predicate<PaymentTransaction>() {
+                                                                                                                         @Override
+                                                                                                                         public boolean apply(final PaymentTransaction transaction) {
+                                                                                                                             return transaction.getTransactionType() == TransactionType.AUTHORIZE ||
+                                                                                                                                    transaction.getTransactionType() == TransactionType.PURCHASE ||
+                                                                                                                                    transaction.getTransactionType() == TransactionType.CREDIT;
+                                                                                                                         }
+                                                                                                                     }), null);
+        }
+        this.currency = transactionToUseForCurrency == null ? null : transactionToUseForCurrency.getCurrency();
+
+        this.authAmount = getAmountForTransactions(this.currency,
+                                                   nonVoidedTransactions,
+                                                   TransactionType.AUTHORIZE,
+                                                   chargebackTransactions,
+                                                   chargebackProcessedAmount,
+                                                   chargebackProcessedCurrency,
+                                                   chargebackAmount,
+                                                   chargebackCurrency);
+        this.captureAmount = getAmountForTransactions(this.currency,
+                                                      nonVoidedTransactions,
+                                                      TransactionType.CAPTURE,
+                                                      chargebackTransactions,
+                                                      chargebackProcessedAmount,
+                                                      chargebackProcessedCurrency,
+                                                      chargebackAmount,
+                                                      chargebackCurrency);
+        this.purchasedAmount = getAmountForTransactions(this.currency,
+                                                        nonVoidedTransactions,
+                                                        TransactionType.PURCHASE,
+                                                        chargebackTransactions,
+                                                        chargebackProcessedAmount,
+                                                        chargebackProcessedCurrency,
+                                                        chargebackAmount,
+                                                        chargebackCurrency);
+        this.creditAmount = getAmountForTransactions(this.currency,
+                                                     nonVoidedTransactions,
+                                                     TransactionType.CREDIT,
+                                                     chargebackTransactions,
+                                                     chargebackProcessedAmount,
+                                                     chargebackProcessedCurrency,
+                                                     chargebackAmount,
+                                                     chargebackCurrency);
+        this.refundAmount = getAmountForTransactions(this.currency,
+                                                     nonVoidedTransactions,
+                                                     TransactionType.REFUND,
+                                                     chargebackTransactions,
+                                                     chargebackProcessedAmount,
+                                                     chargebackProcessedCurrency,
+                                                     chargebackAmount,
+                                                     chargebackCurrency);
 
         this.isAuthVoided = Iterables.<PaymentTransaction>tryFind(voidedTransactions,
                                                                   new Predicate<PaymentTransaction>() {
@@ -95,11 +162,9 @@ public class DefaultPayment extends EntityBase implements Payment {
                                                                           return input.getTransactionType() == TransactionType.AUTHORIZE && TransactionStatus.SUCCESS.equals(input.getTransactionStatus());
                                                                       }
                                                                   }).isPresent();
-
-        this.currency = !transactions.isEmpty() ? transactions.get(0).getCurrency() : null;
     }
 
-    private static BigDecimal getChargebackAmount(final Iterable<PaymentTransaction> transactions) {
+    private static Collection<PaymentTransaction> getChargebackTransactions(final Collection<PaymentTransaction> transactions) {
         final Collection<String> successfulChargebackExternalKeys = new HashSet<String>();
 
         for (final PaymentTransaction transaction : transactions) {
@@ -111,37 +176,112 @@ public class DefaultPayment extends EntityBase implements Payment {
             }
         }
 
-        return getAmountForType(Iterables.<PaymentTransaction>filter(transactions, new Predicate<PaymentTransaction>() {
-                                    @Override
-                                    public boolean apply(final PaymentTransaction input) {
-                                        return successfulChargebackExternalKeys.contains(input.getExternalKey());
-                                    }
-                                }),
-                                TransactionType.CHARGEBACK);
+        return Collections2.<PaymentTransaction>filter(transactions, new Predicate<PaymentTransaction>() {
+            @Override
+            public boolean apply(final PaymentTransaction input) {
+                return successfulChargebackExternalKeys.contains(input.getExternalKey());
+            }
+        });
     }
 
-    private static BigDecimal getAmountForType(final Iterable<PaymentTransaction> transactions, final TransactionType transactiontype) {
-        BigDecimal result = BigDecimal.ZERO;
-        BigDecimal processedResult = BigDecimal.ZERO;
-        boolean shouldUseProcessedAmount = true;
-
-        for (final PaymentTransaction transaction : transactions) {
-            if (transaction.getTransactionType() != transactiontype || !TransactionStatus.SUCCESS.equals(transaction.getTransactionStatus())) {
-                continue;
+    private static BigDecimal getAmountForTransactions(final Currency paymentCurrency,
+                                                       final Collection<PaymentTransaction> transactions,
+                                                       final TransactionType transactiontype,
+                                                       final Collection<PaymentTransaction> chargebackTransactions,
+                                                       final BigDecimal chargebackProcessedAmount,
+                                                       final Currency chargebackProcessedCurrency,
+                                                       final BigDecimal chargebackAmount,
+                                                       final Currency chargebackCurrency) {
+        final Collection<PaymentTransaction> candidateTransactions = Collections2.<PaymentTransaction>filter(transactions,
+                                                                                                             new Predicate<PaymentTransaction>() {
+                                                                                                                 @Override
+                                                                                                                 public boolean apply(final PaymentTransaction transaction) {
+                                                                                                                     return transaction.getTransactionType() == transactiontype && TransactionStatus.SUCCESS.equals(transaction.getTransactionStatus());
+                                                                                                                 }
+                                                                                                             });
+
+        final boolean takeChargebacksIntoAccount = ImmutableList.<TransactionType>of(TransactionType.CAPTURE, TransactionType.PURCHASE).contains(transactiontype);
+        Currency currencyForTransactions = getCurrencyForTransactions(candidateTransactions, true);
+        if (currencyForTransactions == null || currencyForTransactions != paymentCurrency) {
+            currencyForTransactions = getCurrencyForTransactions(candidateTransactions, false);
+            if (currencyForTransactions == null) {
+                // Multiple currencies - cannot compute the total
+                return BigDecimal.ZERO;
+            } else if (currencyForTransactions != paymentCurrency) {
+                // Different currency than the main payment currency
+                return BigDecimal.ZERO;
+            } else {
+                final BigDecimal amountForTransactions = getAmountForTransactions(candidateTransactions, false);
+                return getAmountForTransactions(amountForTransactions,
+                                                takeChargebacksIntoAccount,
+                                                currencyForTransactions,
+                                                chargebackTransactions,
+                                                chargebackProcessedAmount,
+                                                chargebackProcessedCurrency,
+                                                chargebackAmount,
+                                                chargebackCurrency);
             }
+        } else {
+            final BigDecimal amountForTransactions = getAmountForTransactions(candidateTransactions, true);
+            return getAmountForTransactions(amountForTransactions,
+                                            takeChargebacksIntoAccount,
+                                            currencyForTransactions,
+                                            chargebackTransactions,
+                                            chargebackProcessedAmount,
+                                            chargebackProcessedCurrency,
+                                            chargebackAmount,
+                                            chargebackCurrency);
+        }
+    }
 
-            result = result.add(transaction.getAmount());
+    private static BigDecimal getAmountForTransactions(final BigDecimal amountForTransactions,
+                                                       final boolean takeChargebacksIntoAccount,
+                                                       final Currency currencyForTransactions,
+                                                       final Collection<PaymentTransaction> chargebackTransactions,
+                                                       final BigDecimal chargebackProcessedAmount,
+                                                       final Currency chargebackProcessedCurrency,
+                                                       final BigDecimal chargebackAmount,
+                                                       final Currency chargebackCurrency) {
+        if (!takeChargebacksIntoAccount) {
+            return amountForTransactions;
+        }
 
-            shouldUseProcessedAmount = shouldUseProcessedAmount && transaction.getCurrency().equals(transaction.getProcessedCurrency()) && transaction.getProcessedAmount() != null;
-            processedResult = shouldUseProcessedAmount ? processedResult.add(transaction.getProcessedAmount()) : BigDecimal.ZERO;
+        final BigDecimal chargebackAmountInCorrectCurrency;
+        if (currencyForTransactions == chargebackProcessedCurrency) {
+            chargebackAmountInCorrectCurrency = chargebackProcessedAmount;
+        } else if (currencyForTransactions == chargebackCurrency) {
+            chargebackAmountInCorrectCurrency = chargebackAmount;
+        } else if (!chargebackTransactions.isEmpty()) {
+            // Payment has chargebacks but in a different currency - zero-out the payment
+            chargebackAmountInCorrectCurrency = amountForTransactions;
+        } else {
+            chargebackAmountInCorrectCurrency = BigDecimal.ZERO;
+        }
+        return amountForTransactions.add(chargebackAmountInCorrectCurrency.negate()).max(BigDecimal.ZERO);
+    }
 
-            // For multi-step AUTH, don't sum the individual transactions
-            if (TransactionType.AUTHORIZE.equals(transactiontype)) {
-                break;
+    private static BigDecimal getAmountForTransactions(final Iterable<PaymentTransaction> candidateTransactions, final boolean useProcessedValues) {
+        BigDecimal amount = BigDecimal.ZERO;
+        for (final PaymentTransaction transaction : candidateTransactions) {
+            if (useProcessedValues) {
+                amount = amount.add(transaction.getProcessedAmount());
+            } else {
+                amount = amount.add(transaction.getAmount());
             }
         }
+        return amount;
+    }
+
+    private static Currency getCurrencyForTransactions(final Collection<PaymentTransaction> candidateTransactions, final boolean useProcessedValues) {
+        final Collection<Currency> currencies = new HashSet<Currency>(Collections2.<PaymentTransaction, Currency>transform(candidateTransactions,
+                                                                                                                           new Function<PaymentTransaction, Currency>() {
+                                                                                                                               @Override
+                                                                                                                               public Currency apply(final PaymentTransaction transaction) {
+                                                                                                                                   return useProcessedValues ? transaction.getProcessedCurrency() : transaction.getCurrency();
+                                                                                                                               }
+                                                                                                                           }));
 
-        return shouldUseProcessedAmount ? processedResult : result;
+        return currencies.size() > 1 ? null : Iterables.<Currency>getFirst(currencies, null);
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index e4d9edf..f9b3dde 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
@@ -169,30 +169,33 @@ public class PaymentProcessor extends ProcessorBase {
 
         final Map<UUID, PaymentPluginApi> paymentPluginByPaymentMethodId = new HashMap<UUID, PaymentPluginApi>();
         final Collection<UUID> absentPlugins = new HashSet<UUID>();
-        return Lists.<PaymentModelDao, Payment>transform(paymentsModelDao,
-                                                         new Function<PaymentModelDao, Payment>() {
-                                                             @Override
-                                                             public Payment apply(final PaymentModelDao paymentModelDao) {
-                                                                 List<PaymentTransactionInfoPlugin> pluginInfo = null;
-
-                                                                 if (withPluginInfo) {
-                                                                     PaymentPluginApi pluginApi = paymentPluginByPaymentMethodId.get(paymentModelDao.getPaymentMethodId());
-                                                                     if (pluginApi == null && !absentPlugins.contains(paymentModelDao.getPaymentMethodId())) {
-                                                                         try {
-                                                                             pluginApi = getPaymentProviderPlugin(paymentModelDao.getPaymentMethodId(), tenantContext);
-                                                                             paymentPluginByPaymentMethodId.put(paymentModelDao.getPaymentMethodId(), pluginApi);
-                                                                         } catch (final PaymentApiException e) {
-                                                                             log.warn("Unable to retrieve pluginApi for payment method " + paymentModelDao.getPaymentMethodId());
-                                                                             absentPlugins.add(paymentModelDao.getPaymentMethodId());
-                                                                         }
-                                                                     }
-
-                                                                     pluginInfo = getPaymentTransactionInfoPluginsIfNeeded(pluginApi, paymentModelDao, context);
-                                                                 }
-
-                                                                 return toPayment(paymentModelDao, transactionsModelDao, pluginInfo, tenantContext);
-                                                             }
-                                                         });
+        final List<Payment> transformedPayments = Lists.<PaymentModelDao, Payment>transform(paymentsModelDao,
+                                                                                            new Function<PaymentModelDao, Payment>() {
+                                                                                                @Override
+                                                                                                public Payment apply(final PaymentModelDao paymentModelDao) {
+                                                                                                    List<PaymentTransactionInfoPlugin> pluginInfo = null;
+
+                                                                                                    if (withPluginInfo) {
+                                                                                                        PaymentPluginApi pluginApi = paymentPluginByPaymentMethodId.get(paymentModelDao.getPaymentMethodId());
+                                                                                                        if (pluginApi == null && !absentPlugins.contains(paymentModelDao.getPaymentMethodId())) {
+                                                                                                            try {
+                                                                                                                pluginApi = getPaymentProviderPlugin(paymentModelDao.getPaymentMethodId(), tenantContext);
+                                                                                                                paymentPluginByPaymentMethodId.put(paymentModelDao.getPaymentMethodId(), pluginApi);
+                                                                                                            } catch (final PaymentApiException e) {
+                                                                                                                log.warn("Unable to retrieve pluginApi for payment method " + paymentModelDao.getPaymentMethodId());
+                                                                                                                absentPlugins.add(paymentModelDao.getPaymentMethodId());
+                                                                                                            }
+                                                                                                        }
+
+                                                                                                        pluginInfo = getPaymentTransactionInfoPluginsIfNeeded(pluginApi, paymentModelDao, context);
+                                                                                                    }
+
+                                                                                                    return toPayment(paymentModelDao, transactionsModelDao, pluginInfo, tenantContext);
+                                                                                                }
+                                                                                            });
+
+        // Copy the transformed list, so the transformation function is applied once (otherwise, the Janitor could be invoked multiple times)
+        return ImmutableList.<Payment>copyOf(transformedPayments);
     }
 
     public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultPayment.java b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultPayment.java
index 7cf7830..6bf1743 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultPayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestDefaultPayment.java
@@ -33,7 +33,6 @@ public class TestDefaultPayment extends PaymentTestSuiteNoDB {
     @Test(groups = "fast")
     public void testAmountsCaptureVoided() throws Exception {
         final UUID paymentId = UUID.randomUUID();
-        final String chargebackExternalKey = UUID.randomUUID().toString();
         final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.AUTHORIZE, TransactionStatus.SUCCESS, BigDecimal.TEN),
                                                                                            buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.CAPTURE, TransactionStatus.SUCCESS, BigDecimal.TEN),
                                                                                            buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.VOID, TransactionStatus.SUCCESS, null));
@@ -47,7 +46,6 @@ public class TestDefaultPayment extends PaymentTestSuiteNoDB {
     @Test(groups = "fast")
     public void testAmountsCaptureVoidedAuthReversed() throws Exception {
         final UUID paymentId = UUID.randomUUID();
-        final String chargebackExternalKey = UUID.randomUUID().toString();
         final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.AUTHORIZE, TransactionStatus.SUCCESS, BigDecimal.TEN),
                                                                                            buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.CAPTURE, TransactionStatus.SUCCESS, BigDecimal.TEN),
                                                                                            buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.VOID, TransactionStatus.SUCCESS, null),
@@ -89,6 +87,22 @@ public class TestDefaultPayment extends PaymentTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
+    public void testAmountsCaptureChargebackReversedMultipleCurrencies() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.AUTHORIZE, TransactionStatus.SUCCESS, BigDecimal.TEN, Currency.EUR),
+                                                                                           buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.CAPTURE, TransactionStatus.SUCCESS, BigDecimal.TEN, Currency.USD),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.ONE, Currency.EUR),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.PAYMENT_FAILURE, BigDecimal.ONE, Currency.EUR));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getCurrency(), Currency.EUR);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.TEN), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+    }
+
+    @Test(groups = "fast")
     public void testAmountsCaptureChargebackReversedAndRefund() throws Exception {
         final UUID paymentId = UUID.randomUUID();
         final String chargebackExternalKey = UUID.randomUUID().toString();
@@ -118,6 +132,19 @@ public class TestDefaultPayment extends PaymentTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
+    public void testAmountsPurchaseChargebackDifferentCurrency() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        final String chargebackExternalKey = UUID.randomUUID().toString();
+        final List<PaymentTransaction> transactions = ImmutableList.<PaymentTransaction>of(buildPaymentTransaction(paymentId, UUID.randomUUID().toString(), TransactionType.PURCHASE, TransactionStatus.SUCCESS, BigDecimal.TEN, Currency.USD),
+                                                                                           buildPaymentTransaction(paymentId, chargebackExternalKey, TransactionType.CHARGEBACK, TransactionStatus.SUCCESS, BigDecimal.ONE, Currency.EUR));
+        final Payment payment = buildPayment(paymentId, transactions);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+    }
+
+    @Test(groups = "fast")
     public void testAmountsPurchaseChargebackReversed() throws Exception {
         final UUID paymentId = UUID.randomUUID();
         final String chargebackExternalKey = UUID.randomUUID().toString();
@@ -163,6 +190,10 @@ public class TestDefaultPayment extends PaymentTestSuiteNoDB {
     }
 
     private PaymentTransaction buildPaymentTransaction(final UUID paymentId, final String externalKey, final TransactionType transactionType, final TransactionStatus transactionStatus, final BigDecimal amount) {
+        return buildPaymentTransaction(paymentId, externalKey, transactionType, transactionStatus, amount, Currency.USD);
+    }
+
+    private PaymentTransaction buildPaymentTransaction(final UUID paymentId, final String externalKey, final TransactionType transactionType, final TransactionStatus transactionStatus, final BigDecimal amount, final Currency currency) {
         return new DefaultPaymentTransaction(UUID.randomUUID(),
                                              UUID.randomUUID(),
                                              externalKey,
@@ -173,9 +204,9 @@ public class TestDefaultPayment extends PaymentTestSuiteNoDB {
                                              clock.getUTCNow(),
                                              transactionStatus,
                                              amount,
-                                             Currency.USD,
+                                             currency,
                                              amount,
-                                             Currency.USD,
+                                             currency,
                                              null,
                                              null,
                                              null);
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
index d60f90b..fe0fe2a 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
@@ -18,7 +18,6 @@
 package org.killbill.billing.payment.core;
 
 import java.math.BigDecimal;
-import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
@@ -28,9 +27,9 @@ import javax.annotation.Nullable;
 
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.events.BusInternalEvent;
 import org.killbill.billing.events.PaymentErrorInternalEvent;
 import org.killbill.billing.events.PaymentInfoInternalEvent;
+import org.killbill.billing.events.PaymentInternalEvent;
 import org.killbill.billing.events.PaymentPluginErrorInternalEvent;
 import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
 import org.killbill.billing.payment.api.Payment;
@@ -58,11 +57,14 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
     private static final BigDecimal TEN = new BigDecimal("10");
     private static final Currency CURRENCY = Currency.BTC;
 
+    private MockPaymentProviderPlugin mockPaymentProviderPlugin;
     private PaymentBusListener paymentBusListener;
     private Account account;
 
     @BeforeMethod(groups = "slow")
     public void setUp() throws Exception {
+        mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME);
+
         account = testHelper.createTestAccount(UUID.randomUUID().toString(), true);
 
         paymentBusListener = new PaymentBusListener();
@@ -70,9 +72,31 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
     }
 
     @Test(groups = "slow")
-    public void testClassicFlow() throws Exception {
+    public void testGetAccountPaymentsWithJanitor() throws Exception {
         final String paymentExternalKey = UUID.randomUUID().toString();
 
+        final Iterable<PluginProperty> pluginPropertiesToDriveTransationToUnknown = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.UNDEFINED, false));
+
+        final String authorizationKey = UUID.randomUUID().toString();
+        final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, authorizationKey,
+                                                                           SHOULD_LOCK_ACCOUNT, pluginPropertiesToDriveTransationToUnknown, callContext, internalCallContext);
+        verifyPayment(authorization, paymentExternalKey, ZERO, ZERO, ZERO, 1);
+        final UUID paymentId = authorization.getId();
+        verifyPaymentTransaction(authorization.getTransactions().get(0), authorizationKey, TransactionType.AUTHORIZE, TEN, paymentId);
+        paymentBusListener.verify(0, 0, 1, account.getId(), paymentId, ZERO, TransactionStatus.UNKNOWN);
+
+        mockPaymentProviderPlugin.overridePaymentPluginStatus(paymentId, authorization.getTransactions().get(0).getId(), PaymentPluginStatus.PROCESSED);
+
+        final List<Payment> payments = paymentProcessor.getAccountPayments(account.getId(), true, callContext, internalCallContext);
+        Assert.assertEquals(payments.size(), 1);
+        verifyPayment(payments.get(0), paymentExternalKey, TEN, ZERO, ZERO, 1);
+        verifyPaymentTransaction(payments.get(0).getTransactions().get(0), authorizationKey, TransactionType.AUTHORIZE, TEN, paymentId);
+        paymentBusListener.verify(1, 0, 1, account.getId(), paymentId, TEN, TransactionStatus.SUCCESS);
+    }
+
+    @Test(groups = "slow")
+    public void testClassicFlow() throws Exception {
+        final String paymentExternalKey = UUID.randomUUID().toString();
 
         final Iterable<PluginProperty> pluginPropertiesToDriveTransationToPending = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, PaymentPluginStatus.PENDING, false));
 
@@ -209,8 +233,8 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
     private static final class PaymentBusListener {
 
         private final List<PaymentInfoInternalEvent> paymentInfoEvents = new LinkedList<PaymentInfoInternalEvent>();
-        private final Collection<BusInternalEvent> paymentErrorEvents = new LinkedList<BusInternalEvent>();
-        private final Collection<BusInternalEvent> paymentPluginErrorEvents = new LinkedList<BusInternalEvent>();
+        private final List<PaymentInternalEvent> paymentErrorEvents = new LinkedList<PaymentInternalEvent>();
+        private final List<PaymentInternalEvent> paymentPluginErrorEvents = new LinkedList<PaymentInternalEvent>();
 
         @Subscribe
         public void paymentInfo(final PaymentInfoInternalEvent event) {
@@ -228,20 +252,28 @@ public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
         }
 
         private void verify(final int eventNb, final UUID accountId, final UUID paymentId, final BigDecimal amount, final TransactionStatus transactionStatus) throws Exception {
+            verify(eventNb, 0, 0, accountId, paymentId, amount, transactionStatus);
+        }
+
+        private void verify(final int nbInfoEvents, final int nbErrorEvents, final int nbPluginErrorEvents, final UUID accountId, final UUID paymentId, final BigDecimal amount, final TransactionStatus transactionStatus) throws Exception {
             Awaitility.await()
                       .until(new Callable<Boolean>() {
                           @Override
                           public Boolean call() throws Exception {
-                              return paymentInfoEvents.size() == eventNb;
+                              return paymentInfoEvents.size() == nbInfoEvents && paymentErrorEvents.size() == nbErrorEvents && paymentPluginErrorEvents.size() == nbPluginErrorEvents;
                           }
                       });
-            Assert.assertEquals(paymentErrorEvents.size(), 0);
-            Assert.assertEquals(paymentPluginErrorEvents.size(), 0);
 
-            verify(paymentInfoEvents.get(eventNb - 1), accountId, paymentId, amount, transactionStatus);
+            if (transactionStatus == TransactionStatus.SUCCESS || transactionStatus == TransactionStatus.PENDING) {
+                verify(paymentInfoEvents.get(paymentInfoEvents.size() - 1), accountId, paymentId, amount, transactionStatus);
+            } else if (transactionStatus == TransactionStatus.PAYMENT_FAILURE) {
+                verify(paymentErrorEvents.get(paymentErrorEvents.size() - 1), accountId, paymentId, amount, transactionStatus);
+            } else {
+                verify(paymentPluginErrorEvents.get(paymentPluginErrorEvents.size() - 1), accountId, paymentId, amount, transactionStatus);
+            }
         }
 
-        private void verify(final PaymentInfoInternalEvent event, final UUID accountId, final UUID paymentId, @Nullable final BigDecimal amount, final TransactionStatus transactionStatus) {
+        private void verify(final PaymentInternalEvent event, final UUID accountId, final UUID paymentId, @Nullable final BigDecimal amount, final TransactionStatus transactionStatus) {
             Assert.assertEquals(event.getPaymentId(), paymentId);
             Assert.assertEquals(event.getAccountId(), accountId);
             if (amount == null) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
index d6fb5f3..8adabea 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -21,6 +21,7 @@ package org.killbill.billing.payment.provider;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -49,7 +50,6 @@ import org.killbill.billing.util.entity.DefaultPagination;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.clock.Clock;
 
-import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -241,6 +241,30 @@ public class MockPaymentProviderPlugin implements PaymentPluginApi {
         }
     }
 
+    public void overridePaymentPluginStatus(final UUID kbPaymentId, final UUID kbTransactionId, final PaymentPluginStatus status) {
+        final List<PaymentTransactionInfoPlugin> existingTransactions = paymentTransactions.remove(kbPaymentId.toString());
+        final List<PaymentTransactionInfoPlugin> newTransactions = new LinkedList<PaymentTransactionInfoPlugin>();
+        paymentTransactions.put(kbPaymentId.toString(), newTransactions);
+
+        for (final PaymentTransactionInfoPlugin existingTransaction : existingTransactions) {
+            if (existingTransaction.getKbTransactionPaymentId().equals(kbTransactionId)) {
+                final PaymentTransactionInfoPlugin newTransaction = new DefaultNoOpPaymentInfoPlugin(existingTransaction.getKbPaymentId(),
+                                                                                                     existingTransaction.getKbTransactionPaymentId(),
+                                                                                                     existingTransaction.getTransactionType(),
+                                                                                                     existingTransaction.getAmount(),
+                                                                                                     existingTransaction.getCurrency(),
+                                                                                                     existingTransaction.getEffectiveDate(),
+                                                                                                     existingTransaction.getCreatedDate(),
+                                                                                                     status,
+                                                                                                     existingTransaction.getGatewayErrorCode(),
+                                                                                                     existingTransaction.getGatewayError());
+                newTransactions.add(newTransaction);
+            } else {
+                newTransactions.add(existingTransaction);
+            }
+        }
+    }
+
     @Override
     public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentPluginApiException {