killbill-memoizeit
Changes
pom.xml 2(+1 -1)
Details
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index 0ee1c3b..d54d44a 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -74,6 +74,10 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
+ public void notifyPendingPaymentOfStateChanged(final Account account, final UUID paymentId, final boolean isSuccess, final CallContext context) throws PaymentApiException {
+ }
+
+ @Override
public Payment retryPayment(final Account account, final UUID paymentId, final CallContext context) throws PaymentApiException {
final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), context);
paymentProcessor.retryPaymentFromApi(paymentId, internalCallContext);
@@ -125,6 +129,10 @@ public class DefaultPaymentApi implements PaymentApi {
}
@Override
+ public void notifyPendingRefundOfStateChanged(final Account account, final UUID paymentId, final boolean isSuccess, final CallContext context) throws PaymentApiException {
+ }
+
+ @Override
public Refund createRefundWithAdjustment(final Account account, final UUID paymentId, final BigDecimal refundAmount, final CallContext context) throws PaymentApiException {
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new PaymentApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL);
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
index addd642..195e6de 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
@@ -16,72 +16,71 @@
package com.ning.billing.payment.core;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterators;
-import com.google.inject.name.Named;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.ning.billing.ErrorCode;
import com.ning.billing.ObjectType;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountInternalApi;
import com.ning.billing.bus.api.PersistentBus;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.clock.Clock;
import com.ning.billing.commons.locker.GlobalLocker;
+import com.ning.billing.events.BusInternalEvent;
+import com.ning.billing.events.PaymentErrorInternalEvent;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceInternalApi;
import com.ning.billing.osgi.api.OSGIServiceRegistration;
import com.ning.billing.payment.api.DefaultPayment;
import com.ning.billing.payment.api.DefaultPaymentErrorEvent;
import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
-import com.ning.billing.payment.api.DefaultPaymentMethod;
import com.ning.billing.payment.api.DefaultPaymentPluginErrorEvent;
import com.ning.billing.payment.api.Payment;
import com.ning.billing.payment.api.PaymentApiException;
-import com.ning.billing.payment.api.PaymentMethod;
-import com.ning.billing.payment.api.PaymentMethodPlugin;
import com.ning.billing.payment.api.PaymentStatus;
import com.ning.billing.payment.dao.PaymentAttemptModelDao;
import com.ning.billing.payment.dao.PaymentDao;
-import com.ning.billing.payment.dao.PaymentMethodModelDao;
import com.ning.billing.payment.dao.PaymentModelDao;
import com.ning.billing.payment.dao.RefundModelDao;
import com.ning.billing.payment.dispatcher.PluginDispatcher;
import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
import com.ning.billing.payment.plugin.api.PaymentPluginApi;
import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
+import com.ning.billing.payment.plugin.api.PaymentPluginStatus;
import com.ning.billing.payment.retry.AutoPayRetryService.AutoPayRetryServiceScheduler;
import com.ning.billing.payment.retry.FailedPaymentRetryService.FailedPaymentRetryServiceScheduler;
import com.ning.billing.payment.retry.PluginFailureRetryService.PluginFailureRetryServiceScheduler;
-import com.ning.billing.callcontext.InternalCallContext;
-import com.ning.billing.callcontext.InternalTenantContext;
+import com.ning.billing.tag.TagInternalApi;
import com.ning.billing.util.config.PaymentConfig;
import com.ning.billing.util.dao.NonEntityDao;
-import com.ning.billing.events.BusInternalEvent;
-import com.ning.billing.events.PaymentErrorInternalEvent;
-import com.ning.billing.account.api.AccountInternalApi;
-import com.ning.billing.invoice.api.InvoiceInternalApi;
-import com.ning.billing.tag.TagInternalApi;
import com.ning.billing.util.entity.DefaultPagination;
import com.ning.billing.util.entity.Pagination;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
+import com.google.inject.name.Named;
import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
@@ -103,7 +102,6 @@ public class PaymentProcessor extends ProcessorBase {
private static final Logger log = LoggerFactory.getLogger(PaymentProcessor.class);
-
@Inject
public PaymentProcessor(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
final PaymentMethodProcessor paymentMethodProcessor,
@@ -220,7 +218,6 @@ public class PaymentProcessor extends ProcessorBase {
return getPayments(paymentDao.getPaymentsForInvoice(invoiceId, context), context);
}
-
public List<Payment> getAccountPayments(final UUID accountId, final InternalTenantContext context) {
return getPayments(paymentDao.getPaymentsForAccount(accountId, context), context);
}
@@ -248,45 +245,45 @@ public class PaymentProcessor extends ProcessorBase {
try {
voidPluginDispatcher.dispatchWithAccountLock(new CallableWithAccountLock<Void>(locker,
- account.getExternalKey(),
- new WithAccountLockCallback<Void>() {
-
- @Override
- public Void doOperation() throws PaymentApiException {
-
- final List<PaymentModelDao> payments = paymentDao.getPaymentsForAccount(account.getId(), context);
- final Collection<PaymentModelDao> paymentsToBeCompleted = Collections2.filter(payments, new Predicate<PaymentModelDao>() {
- @Override
- public boolean apply(final PaymentModelDao in) {
- // Payments left in AUTO_PAY_OFF or for which we did not retry enough
- return (in.getPaymentStatus() == PaymentStatus.AUTO_PAY_OFF ||
- in.getPaymentStatus() == PaymentStatus.PAYMENT_FAILURE ||
- in.getPaymentStatus() == PaymentStatus.PLUGIN_FAILURE ||
- in.getPaymentStatus() == PaymentStatus.UNKNOWN);
- }
- });
- // Insert one retry event for each payment left in AUTO_PAY_OFF
- for (PaymentModelDao cur : paymentsToBeCompleted) {
- switch (cur.getPaymentStatus()) {
- case AUTO_PAY_OFF:
- autoPayoffRetryService.scheduleRetry(cur.getId(), clock.getUTCNow());
- break;
- case PAYMENT_FAILURE:
- scheduleRetryOnPaymentFailure(cur.getId(), context);
- break;
- case PLUGIN_FAILURE:
- case UNKNOWN:
- scheduleRetryOnPluginFailure(cur.getId(), context);
- break;
- default:
- // Impossible...
- throw new RuntimeException("Unexpected case " + cur.getPaymentStatus());
- }
-
- }
- return null;
- }
- }));
+ account.getExternalKey(),
+ new WithAccountLockCallback<Void>() {
+
+ @Override
+ public Void doOperation() throws PaymentApiException {
+
+ final List<PaymentModelDao> payments = paymentDao.getPaymentsForAccount(account.getId(), context);
+ final Collection<PaymentModelDao> paymentsToBeCompleted = Collections2.filter(payments, new Predicate<PaymentModelDao>() {
+ @Override
+ public boolean apply(final PaymentModelDao in) {
+ // Payments left in AUTO_PAY_OFF or for which we did not retry enough
+ return (in.getPaymentStatus() == PaymentStatus.AUTO_PAY_OFF ||
+ in.getPaymentStatus() == PaymentStatus.PAYMENT_FAILURE ||
+ in.getPaymentStatus() == PaymentStatus.PLUGIN_FAILURE ||
+ in.getPaymentStatus() == PaymentStatus.UNKNOWN);
+ }
+ });
+ // Insert one retry event for each payment left in AUTO_PAY_OFF
+ for (PaymentModelDao cur : paymentsToBeCompleted) {
+ switch (cur.getPaymentStatus()) {
+ case AUTO_PAY_OFF:
+ autoPayoffRetryService.scheduleRetry(cur.getId(), clock.getUTCNow());
+ break;
+ case PAYMENT_FAILURE:
+ scheduleRetryOnPaymentFailure(cur.getId(), context);
+ break;
+ case PLUGIN_FAILURE:
+ case UNKNOWN:
+ scheduleRetryOnPluginFailure(cur.getId(), context);
+ break;
+ default:
+ // Impossible...
+ throw new RuntimeException("Unexpected case " + cur.getPaymentStatus());
+ }
+
+ }
+ return null;
+ }
+ }));
} catch (TimeoutException e) {
throw new PaymentApiException(ErrorCode.UNEXPECTED_ERROR, "Unexpected timeout for payment creation (AUTO_PAY_OFF)");
}
@@ -309,64 +306,64 @@ public class PaymentProcessor extends ProcessorBase {
try {
return paymentPluginDispatcher.dispatchWithAccountLock(new CallableWithAccountLock<Payment>(locker,
- account.getExternalKey(),
- new WithAccountLockCallback<Payment>() {
-
- @Override
- public Payment doOperation() throws PaymentApiException {
-
- try {
- // First, rebalance CBA and retrieve the latest version of the invoice
- final Invoice invoice = rebalanceAndGetInvoice(account.getId(), invoiceId, context);
- if (invoice == null || invoice.isMigrationInvoice()) {
- log.error("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
- return null;
- }
-
- // Second, validate the payment amount. We want to bail as early as possible if e.g. the balance is zero
- final BigDecimal requestedAmount = getAndValidatePaymentAmount(invoice, inputAmount, isInstantPayment);
-
- // Third, retrieve the payment method and associated plugin
- final PaymentPluginApi plugin;
- final UUID paymentMethodId;
- try {
- // Use the special external payment plugin to handle external payments
- if (isExternalPayment) {
- plugin = externalPaymentPlugin;
- paymentMethodId = paymentMethodProcessor.getExternalPaymentMethod(account, context).getId();
- } else {
- plugin = getPaymentProviderPlugin(account, context);
- paymentMethodId = account.getPaymentMethodId();
- }
- } catch (PaymentApiException e) {
-
- // Insert a payment entry with one attempt in a terminal state to keep a record of the failure
- processNewPaymentForMissingDefaultPaymentMethodWithAccountLocked(account, invoice, requestedAmount, context);
-
- // This event will be caught by overdue to refresh the overdue state, if needed.
- // Note that at this point, we don't know the exact invoice balance (see getAndValidatePaymentAmount() below).
- // This means that events will be posted for null and zero dollar invoices (e.g. trials).
- final PaymentErrorInternalEvent event = new DefaultPaymentErrorEvent(account.getId(), invoiceId, null,
- ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD.toString(),
- context.getAccountRecordId(), context.getTenantRecordId(),
- context.getUserToken());
- postPaymentEvent(event, account.getId(), context);
- throw e;
- }
-
- final boolean isAccountAutoPayOff = isAccountAutoPayOff(account.getId(), context);
- setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(account.getId(), paymentMethodId, isAccountAutoPayOff, context, isInstantPayment);
-
- if (!isInstantPayment && isAccountAutoPayOff) {
- return processNewPaymentForAutoPayOffWithAccountLocked(paymentMethodId, account, invoice, requestedAmount, context);
- } else {
- return processNewPaymentWithAccountLocked(paymentMethodId, plugin, account, invoice, requestedAmount, isInstantPayment, context);
- }
- } catch (InvoiceApiException e) {
- throw new PaymentApiException(e);
- }
- }
- }));
+ account.getExternalKey(),
+ new WithAccountLockCallback<Payment>() {
+
+ @Override
+ public Payment doOperation() throws PaymentApiException {
+
+ try {
+ // First, rebalance CBA and retrieve the latest version of the invoice
+ final Invoice invoice = rebalanceAndGetInvoice(account.getId(), invoiceId, context);
+ if (invoice == null || invoice.isMigrationInvoice()) {
+ log.error("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
+ return null;
+ }
+
+ // Second, validate the payment amount. We want to bail as early as possible if e.g. the balance is zero
+ final BigDecimal requestedAmount = getAndValidatePaymentAmount(invoice, inputAmount, isInstantPayment);
+
+ // Third, retrieve the payment method and associated plugin
+ final PaymentPluginApi plugin;
+ final UUID paymentMethodId;
+ try {
+ // Use the special external payment plugin to handle external payments
+ if (isExternalPayment) {
+ plugin = externalPaymentPlugin;
+ paymentMethodId = paymentMethodProcessor.getExternalPaymentMethod(account, context).getId();
+ } else {
+ plugin = getPaymentProviderPlugin(account, context);
+ paymentMethodId = account.getPaymentMethodId();
+ }
+ } catch (PaymentApiException e) {
+
+ // Insert a payment entry with one attempt in a terminal state to keep a record of the failure
+ processNewPaymentForMissingDefaultPaymentMethodWithAccountLocked(account, invoice, requestedAmount, context);
+
+ // This event will be caught by overdue to refresh the overdue state, if needed.
+ // Note that at this point, we don't know the exact invoice balance (see getAndValidatePaymentAmount() below).
+ // This means that events will be posted for null and zero dollar invoices (e.g. trials).
+ final PaymentErrorInternalEvent event = new DefaultPaymentErrorEvent(account.getId(), invoiceId, null,
+ ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD.toString(),
+ context.getAccountRecordId(), context.getTenantRecordId(),
+ context.getUserToken());
+ postPaymentEvent(event, account.getId(), context);
+ throw e;
+ }
+
+ final boolean isAccountAutoPayOff = isAccountAutoPayOff(account.getId(), context);
+ setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(account.getId(), paymentMethodId, isAccountAutoPayOff, context, isInstantPayment);
+
+ if (!isInstantPayment && isAccountAutoPayOff) {
+ return processNewPaymentForAutoPayOffWithAccountLocked(paymentMethodId, account, invoice, requestedAmount, context);
+ } else {
+ return processNewPaymentWithAccountLocked(paymentMethodId, plugin, account, invoice, requestedAmount, isInstantPayment, context);
+ }
+ } catch (InvoiceApiException e) {
+ throw new PaymentApiException(e);
+ }
+ }
+ }));
} catch (TimeoutException e) {
if (isInstantPayment) {
throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_TIMEOUT, account.getId(), invoiceId);
@@ -388,25 +385,53 @@ public class PaymentProcessor extends ProcessorBase {
}
}
+ public void notifyPendingPaymentOfStateChanged(final Account account, final UUID paymentId, final boolean isSuccess, final InternalCallContext context)
+ throws PaymentApiException {
+
+ try {
+ voidPluginDispatcher.dispatchWithAccountLock(
+ new CallableWithAccountLock<Void>(locker,
+ account.getExternalKey(),
+ new WithAccountLockCallback<Void>() {
+ @Override
+ public Void doOperation() throws PaymentApiException {
+
+ final PaymentModelDao payment = paymentDao.getPayment(paymentId, context);
+ if (payment == null) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
+ }
+ if (payment.getPaymentStatus() != PaymentStatus.PENDING) {
+ throw new PaymentApiException(ErrorCode.PAYMENT_NOT_PENDING, paymentId);
+ }
+
+ final List<PaymentAttemptModelDao> attempts = paymentDao.getAttemptsForPayment(paymentId, context);
+ final PaymentAttemptModelDao lastAttempt = attempts.get(attempts.size() - 1);
+ final PaymentStatus newPaymentStatus = isSuccess ? PaymentStatus.SUCCESS : PaymentStatus.PAYMENT_FAILURE_ABORTED;
+ paymentDao.updatePaymentAndAttemptOnCompletion(paymentId, newPaymentStatus, payment.getProcessedAmount(), payment.getProcessedCurrency(), lastAttempt.getId(),null, null, context);
+ return null;
+ }
+ }));
+ } catch (TimeoutException e) {
+ }
+ }
+
private void setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(final UUID accountId, final UUID paymentMethodId, final boolean isAccountAutoPayOff,
final InternalCallContext context, final boolean isInstantPayment)
throws PaymentApiException {
final PaymentModelDao lastPaymentForPaymentMethod = paymentDao.getLastPaymentForPaymentMethod(accountId, paymentMethodId, context);
final boolean isLastPaymentForPaymentMethodBad = lastPaymentForPaymentMethod != null &&
- (lastPaymentForPaymentMethod.getPaymentStatus() == PaymentStatus.PLUGIN_FAILURE_ABORTED ||
- lastPaymentForPaymentMethod.getPaymentStatus() == PaymentStatus.UNKNOWN);
-
+ (lastPaymentForPaymentMethod.getPaymentStatus() == PaymentStatus.PLUGIN_FAILURE_ABORTED ||
+ lastPaymentForPaymentMethod.getPaymentStatus() == PaymentStatus.UNKNOWN);
if (isLastPaymentForPaymentMethodBad &&
- !isInstantPayment &&
- !isAccountAutoPayOff) {
+ !isInstantPayment &&
+ !isAccountAutoPayOff) {
log.warn(String.format("Setting account %s into AUTO_PAY_OFF because of bad payment %s", accountId, lastPaymentForPaymentMethod.getId()));
setAccountAutoPayOff(accountId, context);
}
}
-
private BigDecimal getAndValidatePaymentAmount(final Invoice invoice, @Nullable final BigDecimal inputAmount, final boolean isInstantPayment)
throws PaymentApiException {
@@ -414,16 +439,15 @@ public class PaymentProcessor extends ProcessorBase {
throw new PaymentApiException(ErrorCode.PAYMENT_NULL_INVOICE, invoice.getId());
}
if (isInstantPayment &&
- inputAmount != null &&
- invoice.getBalance().compareTo(inputAmount) < 0) {
+ inputAmount != null &&
+ invoice.getBalance().compareTo(inputAmount) < 0) {
throw new PaymentApiException(ErrorCode.PAYMENT_AMOUNT_DENIED,
- invoice.getId(), inputAmount.floatValue(), invoice.getBalance().floatValue());
+ invoice.getId(), inputAmount.floatValue(), invoice.getBalance().floatValue());
}
final BigDecimal result = inputAmount != null ? inputAmount : invoice.getBalance();
return result.setScale(2, RoundingMode.HALF_UP);
}
-
public void retryAutoPayOff(final UUID paymentId, final InternalCallContext context) {
retryFailedPaymentInternal(paymentId, context, PaymentStatus.AUTO_PAY_OFF);
}
@@ -441,9 +465,9 @@ public class PaymentProcessor extends ProcessorBase {
public void retryPaymentFromApi(final UUID paymentId, final InternalCallContext context) {
log.info("Retrying payment " + paymentId + " time = " + clock.getUTCNow());
retryFailedPaymentInternal(paymentId, context, PaymentStatus.UNKNOWN,
- PaymentStatus.AUTO_PAY_OFF,
- PaymentStatus.PAYMENT_FAILURE,
- PaymentStatus.PLUGIN_FAILURE);
+ PaymentStatus.AUTO_PAY_OFF,
+ PaymentStatus.PAYMENT_FAILURE,
+ PaymentStatus.PLUGIN_FAILURE);
}
private void retryFailedPaymentInternal(final UUID paymentId, final InternalCallContext context, final PaymentStatus... expectedPaymentStates) {
@@ -465,42 +489,42 @@ public class PaymentProcessor extends ProcessorBase {
final PaymentPluginApi plugin = getPaymentProviderPlugin(account, context);
voidPluginDispatcher.dispatchWithAccountLock(new CallableWithAccountLock<Void>(locker,
- account.getExternalKey(),
- new WithAccountLockCallback<Void>() {
-
- @Override
- public Void doOperation() throws PaymentApiException {
- try {
- // Fetch again with account lock this time
- final PaymentModelDao payment = paymentDao.getPayment(paymentId, context);
- boolean foundExpectedState = false;
- for (final PaymentStatus cur : expectedPaymentStates) {
- if (payment.getPaymentStatus() == cur) {
- foundExpectedState = true;
- break;
- }
- }
- if (!foundExpectedState) {
- log.info("Aborted retry for payment {} because it is {} state", paymentId, payment.getPaymentStatus());
- return null;
- }
-
- final Invoice invoice = rebalanceAndGetInvoice(payment.getAccountId(), payment.getInvoiceId(), context);
- if (invoice == null || invoice.isMigrationInvoice()) {
- return null;
- }
- if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
- log.info("Aborted retry for payment {} because invoice has been paid", paymentId);
- setTerminalStateOnRetryWithAccountLocked(account, invoice, payment, invoice.getBalance(), "Paid invoice", context);
- return null;
- }
- processRetryPaymentWithAccountLocked(plugin, account, invoice, payment, invoice.getBalance(), context);
- return null;
- } catch (InvoiceApiException e) {
- throw new PaymentApiException(e);
- }
- }
- }));
+ account.getExternalKey(),
+ new WithAccountLockCallback<Void>() {
+
+ @Override
+ public Void doOperation() throws PaymentApiException {
+ try {
+ // Fetch again with account lock this time
+ final PaymentModelDao payment = paymentDao.getPayment(paymentId, context);
+ boolean foundExpectedState = false;
+ for (final PaymentStatus cur : expectedPaymentStates) {
+ if (payment.getPaymentStatus() == cur) {
+ foundExpectedState = true;
+ break;
+ }
+ }
+ if (!foundExpectedState) {
+ log.info("Aborted retry for payment {} because it is {} state", paymentId, payment.getPaymentStatus());
+ return null;
+ }
+
+ final Invoice invoice = rebalanceAndGetInvoice(payment.getAccountId(), payment.getInvoiceId(), context);
+ if (invoice == null || invoice.isMigrationInvoice()) {
+ return null;
+ }
+ if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
+ log.info("Aborted retry for payment {} because invoice has been paid", paymentId);
+ setTerminalStateOnRetryWithAccountLocked(account, invoice, payment, invoice.getBalance(), "Paid invoice", context);
+ return null;
+ }
+ processRetryPaymentWithAccountLocked(plugin, account, invoice, payment, invoice.getBalance(), context);
+ return null;
+ } catch (InvoiceApiException e) {
+ throw new PaymentApiException(e);
+ }
+ }
+ }));
} catch (AccountApiException e) {
log.error(String.format("Failed to retry payment for paymentId %s", paymentId), e);
} catch (PaymentApiException e) {
@@ -524,11 +548,10 @@ public class PaymentProcessor extends ProcessorBase {
return fromPaymentModelDao(paymentInfo, null, context);
}
-
private Payment processNewPaymentForMissingDefaultPaymentMethodWithAccountLocked(final Account account, final Invoice invoice,
final BigDecimal requestedAmount, final InternalCallContext context)
throws PaymentApiException {
- final PaymentStatus paymentStatus = PaymentStatus.PAYMENT_FAILURE_ABORTED ;
+ final PaymentStatus paymentStatus = PaymentStatus.PAYMENT_FAILURE_ABORTED;
final PaymentModelDao paymentInfo = new PaymentModelDao(account.getId(), invoice.getId(), MISSING_PAYMENT_METHOD_ID, requestedAmount, invoice.getCurrency(), clock.getUTCNow(), paymentStatus);
final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), paymentInfo.getId(), MISSING_PAYMENT_METHOD_ID, paymentStatus, clock.getUTCNow(),
@@ -538,7 +561,6 @@ public class PaymentProcessor extends ProcessorBase {
return fromPaymentModelDao(paymentInfo, null, context);
}
-
private Payment processNewPaymentWithAccountLocked(final UUID paymentMethodId, final PaymentPluginApi plugin, final Account account, final Invoice invoice,
final BigDecimal requestedAmount, final boolean isInstantPayment, final InternalCallContext context) throws PaymentApiException {
final PaymentModelDao payment = new PaymentModelDao(account.getId(), invoice.getId(), paymentMethodId, requestedAmount.setScale(2, RoundingMode.HALF_UP), invoice.getCurrency(), clock.getUTCNow());
@@ -584,7 +606,6 @@ public class PaymentProcessor extends ProcessorBase {
return processPaymentWithAccountLocked(plugin, account, invoice, payment, attempt, false, context);
}
-
private Payment processPaymentWithAccountLocked(final PaymentPluginApi plugin, final Account account, final Invoice invoice,
final PaymentModelDao paymentInput, final PaymentAttemptModelDao attemptInput, final boolean isInstantPayment, final InternalCallContext context)
throws PaymentApiException {
@@ -613,8 +634,11 @@ public class PaymentProcessor extends ProcessorBase {
}
switch (paymentPluginInfo.getStatus()) {
case PROCESSED:
+ case PENDING:
+
// Update Payment/PaymentAttempt status
- paymentStatus = PaymentStatus.SUCCESS;
+ paymentStatus = paymentPluginInfo.getStatus() == PaymentPluginStatus.PROCESSED ? PaymentStatus.SUCCESS : PaymentStatus.PENDING;
+
// In case of success we are using the amount/currency as returned by the plugin-- if plugin decides to mess with amount and currency, we track it there
paymentDao.updatePaymentAndAttemptOnCompletion(paymentInput.getId(), paymentStatus, paymentPluginInfo.getAmount(), paymentPluginInfo.getCurrency(),
attemptInput.getId(), paymentPluginInfo.getGatewayErrorCode(), null, context);
@@ -626,21 +650,24 @@ public class PaymentProcessor extends ProcessorBase {
// NOTE that we are not using the amount/currency as returned by the plugin but the requested amount/currency; if plugin decide to change the currency
// at the time of payment, we want to stay consistent with the currency on the account
invoiceApi.notifyOfPayment(invoice.getId(),
- payment.getAmount(),
- payment.getCurrency(),
- paymentPluginInfo.getCurrency(),
- payment.getId(),
- payment.getEffectiveDate(),
- context);
+ payment.getAmount(),
+ payment.getCurrency(),
+ paymentPluginInfo.getCurrency(),
+ payment.getId(),
+ payment.getEffectiveDate(),
+ context);
// Create Bus event
event = new DefaultPaymentInfoEvent(account.getId(),
- invoice.getId(), payment.getId(), payment.getAmount(), payment.getPaymentNumber(), paymentStatus,
- payment.getEffectiveDate(),
- context.getAccountRecordId(),
- context.getTenantRecordId(),
- context.getUserToken()
- );
+ invoice.getId(),
+ payment.getId(),
+ payment.getAmount(),
+ payment.getPaymentNumber(),
+ paymentStatus,
+ payment.getEffectiveDate(),
+ context.getAccountRecordId(),
+ context.getTenantRecordId(),
+ context.getUserToken());
break;
case ERROR:
@@ -656,10 +683,10 @@ public class PaymentProcessor extends ProcessorBase {
attemptInput.getId(), paymentPluginInfo.getGatewayErrorCode(), paymentPluginInfo.getGatewayError(), context);
log.info(String.format("Could not process payment for account %s, invoice %s, error = %s",
- account.getId(), invoice.getId(), paymentPluginInfo.getGatewayError()));
+ account.getId(), invoice.getId(), paymentPluginInfo.getGatewayError()));
event = new DefaultPaymentErrorEvent(account.getId(), invoice.getId(), paymentInput.getId(), paymentPluginInfo.getGatewayError(),
- context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()
+ context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()
);
throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT, account.getId(), paymentPluginInfo.getGatewayError());
@@ -680,7 +707,7 @@ public class PaymentProcessor extends ProcessorBase {
attemptInput.getId(), null, e.getMessage(), context);
event = new DefaultPaymentPluginErrorEvent(account.getId(), invoice.getId(), paymentInput.getId(), e.getMessage(),
- context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()
+ context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()
);
throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT, account.getId(), e.toString());
@@ -705,8 +732,7 @@ public class PaymentProcessor extends ProcessorBase {
private PaymentStatus scheduleRetryOnPaymentFailure(final UUID paymentId, final InternalTenantContext context) {
final List<PaymentAttemptModelDao> allAttempts = paymentDao.getAttemptsForPayment(paymentId, context);
final int retryAttempt = getNumberAttemptsInState(allAttempts,
- PaymentStatus.UNKNOWN, PaymentStatus.PAYMENT_FAILURE);
-
+ PaymentStatus.UNKNOWN, PaymentStatus.PAYMENT_FAILURE);
final boolean isScheduledForRetry = failedPaymentRetryService.scheduleRetry(paymentId, retryAttempt);
diff --git a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
index a3fe237..4077fb6 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/RefundProcessor.java
@@ -35,9 +35,13 @@ import com.ning.billing.ErrorCode;
import com.ning.billing.ObjectType;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountInternalApi;
import com.ning.billing.bus.api.PersistentBus;
+import com.ning.billing.callcontext.InternalCallContext;
+import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.commons.locker.GlobalLocker;
import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceInternalApi;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.osgi.api.OSGIServiceRegistration;
import com.ning.billing.payment.api.DefaultRefund;
@@ -50,16 +54,11 @@ import com.ning.billing.payment.dao.RefundModelDao;
import com.ning.billing.payment.plugin.api.PaymentPluginApi;
import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
-import com.ning.billing.payment.plugin.api.RefundPluginStatus;
+import com.ning.billing.tag.TagInternalApi;
import com.ning.billing.util.callcontext.CallOrigin;
-import com.ning.billing.callcontext.InternalCallContext;
import com.ning.billing.util.callcontext.InternalCallContextFactory;
-import com.ning.billing.callcontext.InternalTenantContext;
import com.ning.billing.util.callcontext.UserType;
import com.ning.billing.util.dao.NonEntityDao;
-import com.ning.billing.account.api.AccountInternalApi;
-import com.ning.billing.invoice.api.InvoiceInternalApi;
-import com.ning.billing.tag.TagInternalApi;
import com.google.common.base.Function;
import com.google.common.base.Objects;
@@ -127,20 +126,28 @@ public class RefundProcessor extends ProcessorBase {
final PaymentPluginApi plugin = getPaymentProviderPlugin(payment.getPaymentMethodId(), context);
final RefundInfoPlugin refundInfoPlugin = plugin.processRefund(account.getId(), paymentId, refundAmount, account.getCurrency(), context.toCallContext(tenantId));
- if (refundInfoPlugin.getStatus() == RefundPluginStatus.PROCESSED) {
- paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.PLUGIN_COMPLETED, refundInfoPlugin.getAmount(), refundInfoPlugin.getCurrency(), context);
+ switch (refundInfoPlugin.getStatus()) {
+ case PROCESSED:
+ paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.PLUGIN_COMPLETED, refundInfoPlugin.getAmount(), refundInfoPlugin.getCurrency(), context);
+
+ invoiceApi.createRefund(paymentId, refundAmount, isAdjusted, invoiceItemIdsWithAmounts, refundInfo.getId(), context);
+
+ paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.COMPLETED, refundInfoPlugin.getAmount(), refundInfoPlugin.getCurrency(), context);
- invoiceApi.createRefund(paymentId, refundAmount, isAdjusted, invoiceItemIdsWithAmounts, refundInfo.getId(), context);
+ return new DefaultRefund(refundInfo.getId(), refundInfo.getCreatedDate(), refundInfo.getUpdatedDate(),
+ paymentId, refundInfo.getAmount(), account.getCurrency(),
+ isAdjusted, refundInfo.getCreatedDate(), RefundStatus.COMPLETED);
- paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.COMPLETED, refundInfoPlugin.getAmount(), refundInfoPlugin.getCurrency(), context);
+ case PENDING:
+ paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.PENDING, refundInfoPlugin.getAmount(), refundInfoPlugin.getCurrency(), context);
+ return new DefaultRefund(refundInfo.getId(), refundInfo.getCreatedDate(), refundInfo.getUpdatedDate(),
+ paymentId, refundInfo.getAmount(), account.getCurrency(),
+ isAdjusted, refundInfo.getCreatedDate(), RefundStatus.PENDING);
- return new DefaultRefund(refundInfo.getId(), refundInfo.getCreatedDate(), refundInfo.getUpdatedDate(),
- paymentId, refundInfo.getAmount(), account.getCurrency(),
- isAdjusted, refundInfo.getCreatedDate(), RefundStatus.COMPLETED);
- } else {
- paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.PLUGIN_ERRORED, refundAmount, account.getCurrency(), context);
- throw new PaymentPluginApiException("Refund error for RefundInfo: " + refundInfo.toString(),
- String.format("Gateway error: %s, Gateway error code: %s, Reference id: %s", refundInfoPlugin.getGatewayError(), refundInfoPlugin.getGatewayErrorCode(), refundInfoPlugin.getReferenceId()));
+ default:
+ paymentDao.updateRefundStatus(refundInfo.getId(), RefundStatus.PLUGIN_ERRORED, refundAmount, account.getCurrency(), context);
+ throw new PaymentPluginApiException("Refund error for RefundInfo: " + refundInfo.toString(),
+ String.format("Gateway error: %s, Gateway error code: %s, Reference id: %s", refundInfoPlugin.getGatewayError(), refundInfoPlugin.getGatewayErrorCode(), refundInfoPlugin.getReferenceId()));
}
} catch (PaymentPluginApiException e) {
throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_REFUND, account.getId(), e.getErrorMessage());
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index 332a2da..7c85b6a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
<parent>
<artifactId>killbill-oss-parent</artifactId>
<groupId>com.ning.billing</groupId>
- <version>0.5.10</version>
+ <version>0.5.12</version>
</parent>
<artifactId>killbill</artifactId>
<version>0.8.7-SNAPSHOT</version>