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 {