killbill-memoizeit

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>