killbill-memoizeit

Changes

account/pom.xml 2(+1 -1)

api/pom.xml 2(+1 -1)

beatrix/pom.xml 2(+1 -1)

catalog/pom.xml 2(+1 -1)

invoice/pom.xml 2(+1 -1)

jaxrs/pom.xml 2(+1 -1)

junction/pom.xml 2(+1 -1)

overdue/pom.xml 2(+1 -1)

payment/pom.xml 2(+1 -1)

pom.xml 2(+1 -1)

server/pom.xml 2(+1 -1)

util/pom.xml 2(+1 -1)

Details

account/pom.xml 2(+1 -1)

diff --git a/account/pom.xml b/account/pom.xml
index 03aadad..2b14a3f 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
diff --git a/analytics/pom.xml b/analytics/pom.xml
index a221105..cc45ee2 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-analytics</artifactId>

api/pom.xml 2(+1 -1)

diff --git a/api/pom.xml b/api/pom.xml
index 146503d..e8ed611 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-api</artifactId>
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index 479966a..dc7488c 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.payment.api;
 
 import java.math.BigDecimal;
@@ -25,23 +26,23 @@ import com.ning.billing.util.callcontext.CallContext;
 
 public interface PaymentApi {
 
-    public Payment createPayment(final String accountKey, final UUID invoiceId, final BigDecimal amount, final CallContext context)
+    public Payment createPayment(final Account account, final UUID invoiceId, final BigDecimal amount, final CallContext context)
             throws PaymentApiException;
 
-    public Payment createPayment(final Account account, final UUID invoiceId, final BigDecimal amount, final CallContext context)
+    public Payment createExternalPayment(final Account account, final UUID invoiceId, final BigDecimal amount, final CallContext context)
             throws PaymentApiException;
 
     public Refund getRefund(final UUID refundId)
-    throws PaymentApiException;
+            throws PaymentApiException;
 
     public Refund createRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final boolean isAdjusted, final CallContext context)
-    throws PaymentApiException;
+            throws PaymentApiException;
 
     public List<Refund> getAccountRefunds(final Account account)
-    throws PaymentApiException;
+            throws PaymentApiException;
 
     public List<Refund> getPaymentRefunds(final UUID paymentId)
-    throws PaymentApiException;
+            throws PaymentApiException;
 
     public List<Payment> getInvoicePayments(final UUID invoiceId)
             throws PaymentApiException;
@@ -53,8 +54,8 @@ public interface PaymentApi {
             throws PaymentApiException;
 
     /*
-    * Payment method Apis
-    */
+     * Payment method Apis
+     */
     public Set<String> getAvailablePlugins();
 
     public String initializeAccountPlugin(final String pluginName, final Account account)
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentMethodPlugin.java b/api/src/main/java/com/ning/billing/payment/api/PaymentMethodPlugin.java
index cacdf3a..b0151ae 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentMethodPlugin.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentMethodPlugin.java
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.payment.api;
 
 import java.util.List;
@@ -28,12 +29,12 @@ public interface PaymentMethodPlugin {
     public String getValueString(String key);
 
     public class PaymentMethodKVInfo {
+
         private final String key;
         private final Object value;
         private final Boolean isUpdatable;
 
         public PaymentMethodKVInfo(final String key, final Object value, final Boolean isUpdatable) {
-            super();
             this.key = key;
             this.value = value;
             this.isUpdatable = isUpdatable;
@@ -50,5 +51,48 @@ public interface PaymentMethodPlugin {
         public Boolean getIsUpdatable() {
             return isUpdatable;
         }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("PaymentMethodKVInfo");
+            sb.append("{key='").append(key).append('\'');
+            sb.append(", value=").append(value);
+            sb.append(", isUpdatable=").append(isUpdatable);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            final PaymentMethodKVInfo that = (PaymentMethodKVInfo) o;
+
+            if (isUpdatable != null ? !isUpdatable.equals(that.isUpdatable) : that.isUpdatable != null) {
+                return false;
+            }
+            if (key != null ? !key.equals(that.key) : that.key != null) {
+                return false;
+            }
+            if (value != null ? !value.equals(that.value) : that.value != null) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = key != null ? key.hashCode() : 0;
+            result = 31 * result + (value != null ? value.hashCode() : 0);
+            result = 31 * result + (isUpdatable != null ? isUpdatable.hashCode() : 0);
+            return result;
+        }
     }
 }
diff --git a/api/src/main/java/com/ning/billing/payment/plugin/api/PaymentPluginApi.java b/api/src/main/java/com/ning/billing/payment/plugin/api/PaymentPluginApi.java
index 5f8fe2d..dd54118 100644
--- a/api/src/main/java/com/ning/billing/payment/plugin/api/PaymentPluginApi.java
+++ b/api/src/main/java/com/ning/billing/payment/plugin/api/PaymentPluginApi.java
@@ -33,11 +33,11 @@ public interface PaymentPluginApi {
     public PaymentInfoPlugin getPaymentInfo(UUID paymentId)
             throws PaymentPluginApiException;
 
-    public void processRefund(Account account, UUID paymentId, BigDecimal refundAmout)
-    throws PaymentPluginApiException;
+    public void processRefund(final Account account, final UUID paymentId, BigDecimal refundAmount)
+            throws PaymentPluginApiException;
 
     public int getNbRefundForPaymentAmount(final Account account, final UUID paymentId, final BigDecimal refundAmount)
-        throws PaymentPluginApiException;
+            throws PaymentPluginApiException;
 
     public String createPaymentProviderAccount(Account account)
             throws PaymentPluginApiException;

beatrix/pom.xml 2(+1 -1)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 100c119..351e867 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>

catalog/pom.xml 2(+1 -1)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 4d891e3..c222110 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index eb87818..935e9a0 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>

invoice/pom.xml 2(+1 -1)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 5a05993..1f4329a 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>

jaxrs/pom.xml 2(+1 -1)

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index be19405..cf3b2bd 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-jaxrs</artifactId>
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index 7a9c201..20560e6 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -64,6 +64,7 @@ import com.ning.billing.util.api.CustomFieldUserApi;
 import com.ning.billing.util.api.TagApiException;
 import com.ning.billing.util.api.TagDefinitionApiException;
 import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.dao.ObjectType;
 
 import com.google.inject.Inject;
@@ -188,12 +189,16 @@ public class InvoiceResource extends JaxRsResourceBase {
                                          @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                          @HeaderParam(HDR_REASON) final String reason,
                                          @HeaderParam(HDR_COMMENT) final String comment) throws AccountApiException, PaymentApiException {
+        final Account account = accountApi.getAccountById(UUID.fromString(payment.getAccountId()));
+
+        final UUID invoiceId = UUID.fromString(payment.getInvoiceId());
+        final CallContext callContext = context.createContext(createdBy, reason, comment);
         if (externalPayment) {
-            return Response.status(Status.BAD_REQUEST).entity("External payments have not been implemented yet").build();
+            paymentApi.createExternalPayment(account, invoiceId, payment.getAmount(), callContext);
+        } else {
+            paymentApi.createPayment(account, invoiceId, payment.getAmount(), callContext);
         }
 
-        final Account account = accountApi.getAccountById(UUID.fromString(payment.getAccountId()));
-        paymentApi.createPayment(account, UUID.fromString(payment.getInvoiceId()), null, context.createContext(createdBy, reason, comment));
         return uriBuilder.buildResponse(InvoiceResource.class, "getPayments", payment.getInvoiceId());
     }
 

junction/pom.xml 2(+1 -1)

diff --git a/junction/pom.xml b/junction/pom.xml
index 4e0975a..e18c456 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-junction</artifactId>

overdue/pom.xml 2(+1 -1)

diff --git a/overdue/pom.xml b/overdue/pom.xml
index 2884d12..45d6b25 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-overdue</artifactId>

payment/pom.xml 2(+1 -1)

diff --git a/payment/pom.xml b/payment/pom.xml
index 3288ae8..7d7f623 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>
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 9e2f0af..b4631a2 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
@@ -17,14 +17,10 @@
 package com.ning.billing.payment.api;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
-import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.payment.core.PaymentMethodProcessor;
@@ -32,8 +28,9 @@ import com.ning.billing.payment.core.PaymentProcessor;
 import com.ning.billing.payment.core.RefundProcessor;
 import com.ning.billing.util.callcontext.CallContext;
 
-public class DefaultPaymentApi implements PaymentApi {
+import com.google.inject.Inject;
 
+public class DefaultPaymentApi implements PaymentApi {
 
     private final PaymentMethodProcessor methodProcessor;
     private final PaymentProcessor paymentProcessor;
@@ -49,15 +46,14 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public Payment createPayment(final String accountKey, final UUID invoiceId, final BigDecimal amount, final CallContext context)
-            throws PaymentApiException {
-        return paymentProcessor.createPayment(accountKey, invoiceId, amount, context, true);
+    public Payment createPayment(final Account account, final UUID invoiceId,
+                                 final BigDecimal amount, final CallContext context) throws PaymentApiException {
+        return paymentProcessor.createPayment(account, invoiceId, amount, context, true, false);
     }
 
     @Override
-    public Payment createPayment(final Account account, final UUID invoiceId,
-                                 final BigDecimal amount, final CallContext context) throws PaymentApiException {
-        return paymentProcessor.createPayment(account, invoiceId, amount, context, true);
+    public Payment createExternalPayment(final Account account, final UUID invoiceId, final BigDecimal amount, final CallContext context) throws PaymentApiException {
+        return paymentProcessor.createPayment(account, invoiceId, amount, context, true, true);
     }
 
     @Override
@@ -80,16 +76,14 @@ public class DefaultPaymentApi implements PaymentApi {
         return paymentProcessor.getAccountPayments(accountId);
     }
 
-
-
     @Override
-    public Refund getRefund(UUID refundId) throws PaymentApiException {
+    public Refund getRefund(final UUID refundId) throws PaymentApiException {
         return refundProcessor.getRefund(refundId);
     }
 
     @Override
-    public Refund createRefund(Account account, UUID paymentId,
-            BigDecimal refundAmount, boolean isAdjusted, CallContext context)
+    public Refund createRefund(final Account account, final UUID paymentId,
+                               final BigDecimal refundAmount, final boolean isAdjusted, final CallContext context)
             throws PaymentApiException {
         if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
             throw new PaymentApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL);
@@ -99,31 +93,28 @@ public class DefaultPaymentApi implements PaymentApi {
     }
 
     @Override
-    public List<Refund> getAccountRefunds(Account account)
+    public List<Refund> getAccountRefunds(final Account account)
             throws PaymentApiException {
         return refundProcessor.getAccountRefunds(account);
     }
 
     @Override
-    public List<Refund> getPaymentRefunds(UUID paymentId)
+    public List<Refund> getPaymentRefunds(final UUID paymentId)
             throws PaymentApiException {
         return refundProcessor.getPaymentRefunds(paymentId);
     }
 
-
     @Override
     public Set<String> getAvailablePlugins() {
         return methodProcessor.getAvailablePlugins();
     }
 
-
     @Override
     public String initializeAccountPlugin(final String pluginName, final Account account)
             throws PaymentApiException {
         return methodProcessor.initializeAccountPlugin(pluginName, account);
     }
 
-
     @Override
     public UUID addPaymentMethod(final String pluginName, final Account account,
                                  final boolean setDefault, final PaymentMethodPlugin paymentMethodInfo, final CallContext context)
@@ -131,7 +122,6 @@ public class DefaultPaymentApi implements PaymentApi {
         return methodProcessor.addPaymentMethod(pluginName, account, setDefault, paymentMethodInfo, context);
     }
 
-
     @Override
     public List<PaymentMethod> refreshPaymentMethods(final String pluginName,
                                                      final Account account, final CallContext context)
diff --git a/payment/src/main/java/com/ning/billing/payment/bus/InvoiceHandler.java b/payment/src/main/java/com/ning/billing/payment/bus/InvoiceHandler.java
index 527a776..a690ea7 100644
--- a/payment/src/main/java/com/ning/billing/payment/bus/InvoiceHandler.java
+++ b/payment/src/main/java/com/ning/billing/payment/bus/InvoiceHandler.java
@@ -70,7 +70,7 @@ public class InvoiceHandler {
 
             final CallContext context = new DefaultCallContext("PaymentRequestProcessor", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken(), clock);
             account = accountUserApi.getAccountById(event.getAccountId());
-            paymentProcessor.createPayment(account, event.getInvoiceId(), null, context, false);
+            paymentProcessor.createPayment(account, event.getInvoiceId(), null, context, false, false);
         } catch (AccountApiException e) {
             log.error("Failed to process invoice payment", e);
         } catch (PaymentApiException e) {
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java
index d0f9612..83e4146 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentMethodProcessor.java
@@ -42,10 +42,13 @@ import com.ning.billing.payment.api.DefaultPaymentMethodPlugin;
 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.PaymentMethodPlugin.PaymentMethodKVInfo;
 import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.dao.PaymentMethodModelDao;
 import com.ning.billing.payment.plugin.api.PaymentPluginApi;
 import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
+import com.ning.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
+import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.callcontext.CallContext;
@@ -189,6 +192,27 @@ public class PaymentMethodProcessor extends ProcessorBase {
         return (result.size() == 0) ? null : result.get(0);
     }
 
+    public PaymentMethod getExternalPaymentMethod(final Account account) throws PaymentApiException {
+        final List<PaymentMethod> paymentMethods = getPaymentMethods(account, false);
+        for (final PaymentMethod paymentMethod : paymentMethods) {
+            if (ExternalPaymentProviderPlugin.PLUGIN_NAME.equals(paymentMethod.getPluginName())) {
+                return paymentMethod;
+            }
+        }
+
+        return null;
+    }
+
+    public ExternalPaymentProviderPlugin getExternalPaymentProviderPlugin(final Account account, final CallContext context) throws PaymentApiException {
+        // Check if this account has already used the external payment plugin
+        // If not, it's the first time - add a payment method for it
+        if (getExternalPaymentMethod(account) == null) {
+            final DefaultNoOpPaymentMethodPlugin props = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), false, ImmutableList.<PaymentMethodKVInfo>of());
+            addPaymentMethod(ExternalPaymentProviderPlugin.PLUGIN_NAME, account, false, props, context);
+        }
+
+        return (ExternalPaymentProviderPlugin) pluginRegistry.getPlugin(ExternalPaymentProviderPlugin.PLUGIN_NAME);
+    }
 
     private List<PaymentMethod> getPaymentMethodInternal(final List<PaymentMethodModelDao> paymentMethodModels, final UUID accountId, final String accountKey, final boolean withPluginDetail)
             throws PaymentApiException {
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 f820705..e0e64f4 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
@@ -15,6 +15,7 @@
  */
 package com.ning.billing.payment.core;
 
+import javax.annotation.Nullable;
 import javax.inject.Inject;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -78,6 +79,7 @@ import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 
 public class PaymentProcessor extends ProcessorBase {
 
+    private final PaymentMethodProcessor paymentMethodProcessor;
     private final InvoicePaymentApi invoicePaymentApi;
     private final TagUserApi tagUserApi;
     private final FailedPaymentRetryServiceScheduler failedPaymentRetryService;
@@ -95,19 +97,21 @@ public class PaymentProcessor extends ProcessorBase {
 
     @Inject
     public PaymentProcessor(final PaymentProviderPluginRegistry pluginRegistry,
-            final AccountUserApi accountUserApi,
-            final InvoicePaymentApi invoicePaymentApi,
-            final TagUserApi tagUserApi,
-            final FailedPaymentRetryServiceScheduler failedPaymentRetryService,
-            final PluginFailureRetryServiceScheduler pluginFailureRetryService,
-            final AutoPayRetryServiceScheduler autoPayoffRetryService,
-            final PaymentDao paymentDao,
-            final Bus eventBus,
-            final Clock clock,
-            final GlobalLocker locker,
-            @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
-            final CallContextFactory factory) {
+                            final PaymentMethodProcessor paymentMethodProcessor,
+                            final AccountUserApi accountUserApi,
+                            final InvoicePaymentApi invoicePaymentApi,
+                            final TagUserApi tagUserApi,
+                            final FailedPaymentRetryServiceScheduler failedPaymentRetryService,
+                            final PluginFailureRetryServiceScheduler pluginFailureRetryService,
+                            final AutoPayRetryServiceScheduler autoPayoffRetryService,
+                            final PaymentDao paymentDao,
+                            final Bus eventBus,
+                            final Clock clock,
+                            final GlobalLocker locker,
+                            @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                            final CallContextFactory factory) {
         super(pluginRegistry, accountUserApi, eventBus, paymentDao, locker, executor);
+        this.paymentMethodProcessor = paymentMethodProcessor;
         this.invoicePaymentApi = invoicePaymentApi;
         this.tagUserApi = tagUserApi;
         this.failedPaymentRetryService = failedPaymentRetryService;
@@ -197,22 +201,19 @@ public class PaymentProcessor extends ProcessorBase {
         }
     }
 
-
-    public Payment createPayment(final String accountKey, final UUID invoiceId, final BigDecimal inputAmount, final CallContext context, final boolean isInstantPayment)
-    throws PaymentApiException {
-        try {
-            final Account account = accountUserApi.getAccountByKey(accountKey);
-            return createPayment(account, invoiceId, inputAmount, context, isInstantPayment);
-        } catch (AccountApiException e) {
-            throw new PaymentApiException(e);
+    public Payment createPayment(final Account account, final UUID invoiceId, @Nullable final BigDecimal inputAmount,
+                                 final CallContext context, final boolean isInstantPayment, final boolean isExternalPayment)
+            throws PaymentApiException {
+        // Use the special external payment plugin to handle external payments
+        final PaymentPluginApi plugin;
+        final UUID paymentMethodId;
+        if (isExternalPayment) {
+            plugin = paymentMethodProcessor.getExternalPaymentProviderPlugin(account, context);
+            paymentMethodId = paymentMethodProcessor.getExternalPaymentMethod(account).getId();
+        } else {
+            plugin = getPaymentProviderPlugin(account);
+            paymentMethodId = account.getPaymentMethodId();
         }
-    }
-
-
-
-    public Payment createPayment(final Account account, final UUID invoiceId, final BigDecimal inputAmount, final CallContext context, final boolean isInstantPayment)
-    throws PaymentApiException {
-        final PaymentPluginApi plugin = getPaymentProviderPlugin(account);
 
         try {
             return paymentPluginDispatcher.dispatchWithAccountLock(new CallableWithAccountLock<Payment>(locker,
@@ -238,9 +239,9 @@ public class PaymentProcessor extends ProcessorBase {
 
                     final BigDecimal requestedAmount = getAndValidatePaymentAmount(invoice, inputAmount, isInstantPayment);
                     if (isAccountAutoPayOff) {
-                        return processNewPaymentForAutoPayOffWithAccountLocked(account, invoice, requestedAmount, context);
+                        return processNewPaymentForAutoPayOffWithAccountLocked(paymentMethodId, account, invoice, requestedAmount, context);
                     } else {
-                        return processNewPaymentWithAccountLocked(plugin, account, invoice, requestedAmount, isInstantPayment, context);
+                        return processNewPaymentWithAccountLocked(paymentMethodId, plugin, account, invoice, requestedAmount, isInstantPayment, context);
                     }
                 }
             }));
@@ -298,7 +299,7 @@ public class PaymentProcessor extends ProcessorBase {
 
 
 
-    private BigDecimal getAndValidatePaymentAmount(final Invoice invoice, final BigDecimal inputAmount, final boolean isInstantPayment)
+    private BigDecimal getAndValidatePaymentAmount(final Invoice invoice, @Nullable final BigDecimal inputAmount, final boolean isInstantPayment)
     throws PaymentApiException {
 
         if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
@@ -310,7 +311,7 @@ public class PaymentProcessor extends ProcessorBase {
             throw new PaymentApiException(ErrorCode.PAYMENT_AMOUNT_DENIED,
                     invoice.getId(), inputAmount.floatValue(), invoice.getBalance().floatValue());
         }
-        BigDecimal result =  inputAmount != null ? inputAmount : invoice.getBalance();
+        final BigDecimal result =  inputAmount != null ? inputAmount : invoice.getBalance();
         return result.setScale(2, RoundingMode.HALF_EVEN);
     }
 
@@ -401,24 +402,21 @@ public class PaymentProcessor extends ProcessorBase {
         }
     }
 
-    private Payment processNewPaymentForAutoPayOffWithAccountLocked(final Account account, final Invoice invoice, final BigDecimal requestedAmount, final CallContext context)
-    throws PaymentApiException {
-
-        final PaymentStatus paymentStatus =  PaymentStatus.AUTO_PAY_OFF;
+    private Payment processNewPaymentForAutoPayOffWithAccountLocked(final UUID paymentMethodId, final Account account, final Invoice invoice,
+                                                                    final BigDecimal requestedAmount, final CallContext context)
+            throws PaymentApiException {
+        final PaymentStatus paymentStatus = PaymentStatus.AUTO_PAY_OFF;
 
-        final PaymentModelDao paymentInfo = new PaymentModelDao(account.getId(), invoice.getId(), account.getPaymentMethodId(), requestedAmount, invoice.getCurrency(), clock.getUTCNow(), paymentStatus);
+        final PaymentModelDao paymentInfo = new PaymentModelDao(account.getId(), invoice.getId(), paymentMethodId, requestedAmount, invoice.getCurrency(), clock.getUTCNow(), paymentStatus);
         final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), paymentInfo.getId(), paymentStatus, clock.getUTCNow(), requestedAmount);
 
         paymentDao.insertPaymentWithAttempt(paymentInfo, attempt, context);
         return new DefaultPayment(paymentInfo, Collections.singletonList(attempt), Collections.<RefundModelDao>emptyList());
     }
 
-
-    private Payment processNewPaymentWithAccountLocked(final PaymentPluginApi plugin, final Account account, final Invoice invoice,
-            final BigDecimal requestedAmount, final boolean isInstantPayment, final CallContext context) throws PaymentApiException {
-
-
-        final PaymentModelDao payment = new PaymentModelDao(account.getId(), invoice.getId(), account.getPaymentMethodId(), requestedAmount.setScale(2, RoundingMode.HALF_EVEN), invoice.getCurrency(), clock.getUTCNow());
+    private Payment processNewPaymentWithAccountLocked(final UUID paymentMethodId, final PaymentPluginApi plugin, final Account account, final Invoice invoice,
+                                                       final BigDecimal requestedAmount, final boolean isInstantPayment, final CallContext context) throws PaymentApiException {
+        final PaymentModelDao payment = new PaymentModelDao(account.getId(), invoice.getId(), paymentMethodId, requestedAmount.setScale(2, RoundingMode.HALF_EVEN), invoice.getCurrency(), clock.getUTCNow());
         final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), payment.getId(), clock.getUTCNow(), requestedAmount);
 
         final PaymentModelDao savedPayment = paymentDao.insertPaymentWithAttempt(payment, attempt, context);
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
index b7ed65f..b75f21c 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/AuditedPaymentDao.java
@@ -17,7 +17,9 @@ package com.ning.billing.payment.dao;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 
 import org.skife.jdbi.v2.IDBI;
@@ -208,6 +210,7 @@ public class AuditedPaymentDao implements PaymentDao {
             public List<PaymentMethodModelDao> inTransaction(final PaymentMethodSqlDao transactional, final TransactionStatus status) throws Exception {
                 final List<PaymentMethodModelDao> existingPaymentMethods = getPaymentMethodsInTransaction(transactional, accountId);
 
+                final Set<String> externalPaymentIdProcessed = new HashSet<String>();
                 for (final PaymentMethodModelDao finalPaymentMethod : paymentMethods) {
                     boolean isExistingPaymentMethod = false;
 
@@ -218,11 +221,8 @@ public class AuditedPaymentDao implements PaymentDao {
                             break;
                         } else if (existingPaymentMethod.equalsButActive(finalPaymentMethod)) {
                             // We already have it but its status has changed - update it accordingly
-                            if (finalPaymentMethod.isActive()) {
-                                undeletedPaymentMethodInTransaction(transactional, existingPaymentMethod.getId());
-                            } else {
-                                deletedPaymentMethodInTransaction(transactional, existingPaymentMethod.getId());
-                            }
+                            // Note - in the remote system, the payment method will always be active
+                            undeletedPaymentMethodInTransaction(transactional, existingPaymentMethod.getId());
                             isExistingPaymentMethod = true;
                             break;
                         }
@@ -232,6 +232,15 @@ public class AuditedPaymentDao implements PaymentDao {
                     if (!isExistingPaymentMethod) {
                         insertPaymentMethodInTransaction(transactional, finalPaymentMethod, context);
                     }
+
+                    externalPaymentIdProcessed.add(finalPaymentMethod.getExternalId());
+                }
+
+                // Finally, mark as deleted the ones that don't exist in the specified list (remote system)
+                for (final PaymentMethodModelDao existingPaymentMethod : existingPaymentMethods) {
+                    if (!externalPaymentIdProcessed.contains(existingPaymentMethod.getExternalId())) {
+                        deletedPaymentMethodInTransaction(transactional, existingPaymentMethod.getId());
+                    }
                 }
 
                 return getPaymentMethodsInTransaction(transactional, accountId);
diff --git a/payment/src/main/java/com/ning/billing/payment/glue/DefaultPaymentProviderPluginRegistryProvider.java b/payment/src/main/java/com/ning/billing/payment/glue/DefaultPaymentProviderPluginRegistryProvider.java
new file mode 100644
index 0000000..5a92d7f
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/glue/DefaultPaymentProviderPluginRegistryProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.glue;
+
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.payment.provider.DefaultPaymentProviderPluginRegistry;
+import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class DefaultPaymentProviderPluginRegistryProvider implements Provider<DefaultPaymentProviderPluginRegistry> {
+
+    private final PaymentConfig paymentConfig;
+    private final ExternalPaymentProviderPlugin externalPaymentProviderPlugin;
+
+    @Inject
+    public DefaultPaymentProviderPluginRegistryProvider(final PaymentConfig paymentConfig, final ExternalPaymentProviderPlugin externalPaymentProviderPlugin) {
+        this.paymentConfig = paymentConfig;
+        this.externalPaymentProviderPlugin = externalPaymentProviderPlugin;
+    }
+
+    @Override
+    public DefaultPaymentProviderPluginRegistry get() {
+        final DefaultPaymentProviderPluginRegistry pluginRegistry = new DefaultPaymentProviderPluginRegistry(paymentConfig);
+
+        // Make the external payment provider plugin available by default
+        pluginRegistry.register(externalPaymentProviderPlugin, ExternalPaymentProviderPlugin.PLUGIN_NAME);
+
+        return pluginRegistry;
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java b/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java
index 348a0ed..9e601aa 100644
--- a/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java
@@ -25,9 +25,6 @@ import org.skife.config.ConfigSource;
 import org.skife.config.ConfigurationObjectFactory;
 import org.skife.config.SimplePropertyConfigSource;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.inject.AbstractModule;
-import com.google.inject.name.Names;
 import com.ning.billing.config.PaymentConfig;
 import com.ning.billing.payment.api.DefaultPaymentApi;
 import com.ning.billing.payment.api.PaymentApi;
@@ -39,7 +36,6 @@ import com.ning.billing.payment.core.PaymentProcessor;
 import com.ning.billing.payment.core.RefundProcessor;
 import com.ning.billing.payment.dao.AuditedPaymentDao;
 import com.ning.billing.payment.dao.PaymentDao;
-import com.ning.billing.payment.provider.DefaultPaymentProviderPluginRegistry;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
 import com.ning.billing.payment.retry.AutoPayRetryService;
 import com.ning.billing.payment.retry.AutoPayRetryService.AutoPayRetryServiceScheduler;
@@ -48,6 +44,10 @@ import com.ning.billing.payment.retry.FailedPaymentRetryService.FailedPaymentRet
 import com.ning.billing.payment.retry.PluginFailureRetryService;
 import com.ning.billing.payment.retry.PluginFailureRetryService.PluginFailureRetryServiceScheduler;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
 public class PaymentModule extends AbstractModule {
     private static final int PLUGIN_NB_THREADS = 3;
     private static final String PLUGIN_THREAD_PREFIX = "Plugin-th-";
@@ -107,7 +107,8 @@ public class PaymentModule extends AbstractModule {
         final PaymentConfig paymentConfig = factory.build(PaymentConfig.class);
 
         bind(PaymentConfig.class).toInstance(paymentConfig);
-        bind(PaymentProviderPluginRegistry.class).to(DefaultPaymentProviderPluginRegistry.class).asEagerSingleton();
+        bind(PaymentProviderPluginRegistry.class).toProvider(DefaultPaymentProviderPluginRegistryProvider.class).asEagerSingleton();
+
         bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
         bind(InvoiceHandler.class).asEagerSingleton();
         bind(TagHandler.class).asEagerSingleton();
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java
index 3dd0a01..135ba1b 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.payment.provider;
 
 import java.math.BigDecimal;
@@ -31,7 +32,6 @@ public class DefaultNoOpPaymentInfoPlugin implements PaymentInfoPlugin {
 
     public DefaultNoOpPaymentInfoPlugin(final BigDecimal amount, final DateTime effectiveDate,
                                         final DateTime createdDate, final PaymentPluginStatus status, final String error) {
-        super();
         this.amount = amount;
         this.effectiveDate = effectiveDate;
         this.createdDate = createdDate;
@@ -39,13 +39,11 @@ public class DefaultNoOpPaymentInfoPlugin implements PaymentInfoPlugin {
         this.error = error;
     }
 
-
     @Override
     public BigDecimal getAmount() {
         return amount;
     }
 
-
     @Override
     public DateTime getEffectiveDate() {
         return effectiveDate;
@@ -71,15 +69,66 @@ public class DefaultNoOpPaymentInfoPlugin implements PaymentInfoPlugin {
         return null;
     }
 
-
     @Override
     public String getExtFirstReferenceId() {
         return null;
     }
 
-
     @Override
     public String getExtSecondReferenceId() {
         return null;
     }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultNoOpPaymentInfoPlugin");
+        sb.append("{amount=").append(amount);
+        sb.append(", effectiveDate=").append(effectiveDate);
+        sb.append(", createdDate=").append(createdDate);
+        sb.append(", status=").append(status);
+        sb.append(", error='").append(error).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultNoOpPaymentInfoPlugin that = (DefaultNoOpPaymentInfoPlugin) o;
+
+        if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
+            return false;
+        }
+        if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+            return false;
+        }
+        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+            return false;
+        }
+        if (error != null ? !error.equals(that.error) : that.error != null) {
+            return false;
+        }
+        if (status != that.status) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = amount != null ? amount.hashCode() : 0;
+        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+        result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+        result = 31 * result + (status != null ? status.hashCode() : 0);
+        result = 31 * result + (error != null ? error.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentMethodPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentMethodPlugin.java
index af88c29..f84b111 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentMethodPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentMethodPlugin.java
@@ -13,6 +13,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.payment.provider;
 
 import java.util.List;
@@ -22,8 +23,8 @@ import com.ning.billing.payment.api.PaymentMethodPlugin;
 
 public class DefaultNoOpPaymentMethodPlugin implements PaymentMethodPlugin {
 
-    private String externalId;
-    private boolean isDefault;
+    private final String externalId;
+    private final boolean isDefault;
     private List<PaymentMethodKVInfo> props;
 
     public DefaultNoOpPaymentMethodPlugin(final PaymentMethodPlugin src) {
@@ -34,7 +35,6 @@ public class DefaultNoOpPaymentMethodPlugin implements PaymentMethodPlugin {
 
     public DefaultNoOpPaymentMethodPlugin(final String externalId, final boolean isDefault,
                                           final List<PaymentMethodKVInfo> props) {
-        super();
         this.externalId = externalId;
         this.isDefault = isDefault;
         this.props = props;
@@ -55,14 +55,6 @@ public class DefaultNoOpPaymentMethodPlugin implements PaymentMethodPlugin {
         return props;
     }
 
-    public void setExternalId(final String externalId) {
-        this.externalId = externalId;
-    }
-
-    public void setDefault(final boolean isDefault) {
-        this.isDefault = isDefault;
-    }
-
     public void setProps(final List<PaymentMethodKVInfo> props) {
         this.props = props;
     }
@@ -72,11 +64,56 @@ public class DefaultNoOpPaymentMethodPlugin implements PaymentMethodPlugin {
         if (props == null) {
             return null;
         }
+
         for (final PaymentMethodKVInfo cur : props) {
             if (cur.getKey().equals(key)) {
                 return cur.getValue().toString();
             }
         }
+
         return null;
     }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("DefaultNoOpPaymentMethodPlugin");
+        sb.append("{externalId='").append(externalId).append('\'');
+        sb.append(", isDefault=").append(isDefault);
+        sb.append(", props=").append(props);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DefaultNoOpPaymentMethodPlugin that = (DefaultNoOpPaymentMethodPlugin) o;
+
+        if (isDefault != that.isDefault) {
+            return false;
+        }
+        if (externalId != null ? !externalId.equals(that.externalId) : that.externalId != null) {
+            return false;
+        }
+        if (props != null ? !props.equals(that.props) : that.props != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = externalId != null ? externalId.hashCode() : 0;
+        result = 31 * result + (isDefault ? 1 : 0);
+        result = 31 * result + (props != null ? props.hashCode() : 0);
+        return result;
+    }
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
index c43b64b..2db4092 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
@@ -24,7 +24,6 @@ import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.payment.api.PaymentMethodPlugin;
 import com.ning.billing.payment.plugin.api.NoOpPaymentPluginApi;
@@ -34,15 +33,24 @@ import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
 import com.ning.billing.payment.plugin.api.PaymentProviderAccount;
 import com.ning.billing.util.clock.Clock;
 
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.inject.Inject;
+
 public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
+
+    private static final String PLUGIN_NAME = "__NO_OP__";
+
     private final AtomicBoolean makeNextInvoiceFailWithError = new AtomicBoolean(false);
     private final AtomicBoolean makeNextInvoiceFailWithException = new AtomicBoolean(false);
     private final AtomicBoolean makeAllInvoicesFailWithError = new AtomicBoolean(false);
-    private final Map<UUID, PaymentInfoPlugin> payments = new ConcurrentHashMap<UUID, PaymentInfoPlugin>();
 
+    private final Map<UUID, PaymentInfoPlugin> payments = new ConcurrentHashMap<UUID, PaymentInfoPlugin>();
+    // Note: we can't use HashMultiMap as we care about storing duplicate key/value pairs
+    private final Multimap<UUID, BigDecimal> refunds = LinkedListMultimap.<UUID, BigDecimal>create();
     private final Map<String, List<PaymentMethodPlugin>> paymentMethods = new ConcurrentHashMap<String, List<PaymentMethodPlugin>>();
-
     private final Map<String, PaymentProviderAccount> accounts = new ConcurrentHashMap<String, PaymentProviderAccount>();
+
     private final Clock clock;
 
     @Inject
@@ -73,13 +81,11 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
         makeAllInvoicesFailWithError.set(failure);
     }
 
-
     @Override
     public String getName() {
-        return null;
+        return PLUGIN_NAME;
     }
 
-
     @Override
     public PaymentInfoPlugin processPayment(final String externalKey, final UUID paymentId, final BigDecimal amount) throws PaymentPluginApiException {
         if (makeNextInvoiceFailWithException.getAndSet(false)) {
@@ -92,7 +98,6 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
         return result;
     }
 
-
     @Override
     public PaymentInfoPlugin getPaymentInfo(final UUID paymentId) throws PaymentPluginApiException {
         final PaymentInfoPlugin payment = payments.get(paymentId);
@@ -128,11 +133,9 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
         }
         pms.add(realWithID);
 
-
         return realWithID.getExternalPaymentMethodId();
     }
 
-
     @Override
     public void updatePaymentMethod(final String accountKey, final PaymentMethodPlugin paymentMethodProps)
             throws PaymentPluginApiException {
@@ -144,11 +147,9 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
 
     @Override
     public void deletePaymentMethod(final String accountKey, final String paymentMethodId) throws PaymentPluginApiException {
-
         PaymentMethodPlugin toBeDeleted = null;
         final List<PaymentMethodPlugin> pms = paymentMethods.get(accountKey);
         if (pms != null) {
-
             for (final PaymentMethodPlugin cur : pms) {
                 if (cur.getExternalPaymentMethodId().equals(paymentMethodId)) {
                     toBeDeleted = cur;
@@ -156,6 +157,7 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
                 }
             }
         }
+
         if (toBeDeleted != null) {
             pms.remove(toBeDeleted);
         }
@@ -170,7 +172,42 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
     @Override
     public PaymentMethodPlugin getPaymentMethodDetail(final String accountKey, final String externalPaymentId)
             throws PaymentPluginApiException {
-        return getPaymentMethodDetail(accountKey, externalPaymentId);
+        return getPaymentMethod(accountKey, externalPaymentId);
+    }
+
+    @Override
+    public void setDefaultPaymentMethod(final String accountKey, final String externalPaymentId) throws PaymentPluginApiException {
+    }
+
+    @Override
+    public void processRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount) throws PaymentPluginApiException {
+        final PaymentInfoPlugin paymentInfoPlugin = getPaymentInfo(paymentId);
+        if (paymentInfoPlugin == null) {
+            throw new PaymentPluginApiException("", String.format("No payment found for paymentId %s (plugin %s)", paymentId, getName()));
+        }
+
+        BigDecimal maxAmountRefundable = paymentInfoPlugin.getAmount();
+        for (final BigDecimal refund : refunds.get(paymentId)) {
+            maxAmountRefundable = maxAmountRefundable.add(refund.negate());
+        }
+        if (maxAmountRefundable.compareTo(refundAmount) < 0) {
+            throw new PaymentPluginApiException("", String.format("Refund amount of %s for paymentId %s is bigger than the payment amount %s (plugin %s)",
+                                                                  refundAmount, paymentId, paymentInfoPlugin.getAmount(), getName()));
+        }
+
+        refunds.put(paymentId, refundAmount);
+    }
+
+    @Override
+    public int getNbRefundForPaymentAmount(final Account account, final UUID paymentId, final BigDecimal refundAmount) throws PaymentPluginApiException {
+        int nbRefunds = 0;
+        for (final BigDecimal amount : refunds.get(paymentId)) {
+            if (amount.compareTo(refundAmount) == 0) {
+                nbRefunds++;
+            }
+        }
+
+        return nbRefunds;
     }
 
     private DefaultNoOpPaymentMethodPlugin getPaymentMethod(final String accountKey, final String externalPaymentId) {
@@ -178,28 +215,13 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
         if (pms == null) {
             return null;
         }
+
         for (final PaymentMethodPlugin cur : pms) {
             if (cur.getExternalPaymentMethodId().equals(externalPaymentId)) {
                 return (DefaultNoOpPaymentMethodPlugin) cur;
             }
         }
-        return null;
-    }
-
-    @Override
-    public void setDefaultPaymentMethod(final String accountKey,
-                                        final String externalPaymentId) throws PaymentPluginApiException {
-    }
 
-    @Override
-    public void processRefund(Account account, UUID paymentId,
-            BigDecimal refundAmout) throws PaymentPluginApiException {
-    }
-
-    @Override
-    public int getNbRefundForPaymentAmount(Account account, UUID paymentId,
-            BigDecimal refundAmount) throws PaymentPluginApiException {
-        return 0;
+        return null;
     }
-
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/DefaultPaymentProviderPluginRegistry.java b/payment/src/main/java/com/ning/billing/payment/provider/DefaultPaymentProviderPluginRegistry.java
index 200eaca..2ffa03c 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/DefaultPaymentProviderPluginRegistry.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/DefaultPaymentProviderPluginRegistry.java
@@ -20,11 +20,12 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import com.google.common.base.Strings;
-import com.google.inject.Inject;
 import com.ning.billing.config.PaymentConfig;
 import com.ning.billing.payment.plugin.api.PaymentPluginApi;
 
+import com.google.common.base.Strings;
+import com.google.inject.Inject;
+
 public class DefaultPaymentProviderPluginRegistry implements PaymentProviderPluginRegistry {
 
     private final String defaultPlugin;
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java
new file mode 100644
index 0000000..52fcb0b
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import com.ning.billing.util.clock.Clock;
+
+import com.google.inject.Inject;
+
+/**
+ * Special plugin used to record external payments (i.e. payments not issued by Killbill), such as checks.
+ * <p/>
+ * The implementation is very similar to the no-op plugin, which it extends. This can potentially be an issue
+ * if Killbill is processing a lot of external payments as they are all kept in memory.
+ * TODO: do something about it
+ */
+public class ExternalPaymentProviderPlugin extends DefaultNoOpPaymentProviderPlugin {
+
+    public static final String PLUGIN_NAME = "__EXTERNAL_PAYMENT__";
+
+    @Inject
+    public ExternalPaymentProviderPlugin(final Clock clock) {
+        super(clock);
+    }
+
+    @Override
+    public String getName() {
+        return PLUGIN_NAME;
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index a1a649e..88369bf 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -157,7 +157,7 @@ public class TestPaymentApi extends PaymentTestSuite {
                                                             Currency.USD));
 
         try {
-            final Payment paymentInfo = paymentApi.createPayment(account.getExternalKey(), invoice.getId(), requestedAmount, context);
+            final Payment paymentInfo = paymentApi.createPayment(account, invoice.getId(), requestedAmount, context);
             if (expectedAmount == null) {
                 fail("Expected to fail because requested amount > invoice amount");
             }
diff --git a/payment/src/test/java/com/ning/billing/payment/core/TestPaymentMethodProcessor.java b/payment/src/test/java/com/ning/billing/payment/core/TestPaymentMethodProcessor.java
new file mode 100644
index 0000000..a349629
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/core/TestPaymentMethodProcessor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.core;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.payment.PaymentTestSuite;
+import com.ning.billing.payment.api.PaymentMethod;
+import com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.provider.DefaultPaymentProviderPluginRegistry;
+import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.clock.ClockMock;
+import com.ning.billing.util.globallocker.GlobalLocker;
+
+public class TestPaymentMethodProcessor extends PaymentTestSuite {
+
+    private PaymentMethodProcessor processor;
+
+    @BeforeMethod(groups = "fast")
+    public void setUp() throws Exception {
+        final DefaultPaymentProviderPluginRegistry pluginRegistry = new DefaultPaymentProviderPluginRegistry(Mockito.mock(PaymentConfig.class));
+        pluginRegistry.register(new ExternalPaymentProviderPlugin(new ClockMock()), ExternalPaymentProviderPlugin.PLUGIN_NAME);
+
+        final AccountUserApi accountUserApi = Mockito.mock(AccountUserApi.class);
+        final Bus bus = Mockito.mock(Bus.class);
+        final MockPaymentDao paymentDao = new MockPaymentDao();
+        final GlobalLocker globalLocker = Mockito.mock(GlobalLocker.class);
+        final ExecutorService executorService = Mockito.mock(ExecutorService.class);
+        processor = new PaymentMethodProcessor(pluginRegistry, accountUserApi, bus, paymentDao, globalLocker, executorService);
+    }
+
+    @Test(groups = "fast")
+    public void testGetExternalPaymentProviderPlugin() throws Exception {
+        final UUID accountId = UUID.randomUUID();
+        final Account account = Mockito.mock(Account.class);
+        Mockito.when(account.getId()).thenReturn(accountId);
+        Mockito.when(account.getExternalKey()).thenReturn(accountId.toString());
+        final CallContext context = Mockito.mock(CallContext.class);
+
+        Assert.assertEquals(processor.getPaymentMethods(account, false).size(), 0);
+
+        // The first call should create the payment method
+        final ExternalPaymentProviderPlugin providerPlugin = processor.getExternalPaymentProviderPlugin(account, context);
+        Assert.assertEquals(providerPlugin.getName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
+        final List<PaymentMethod> paymentMethods = processor.getPaymentMethods(account, false);
+        Assert.assertEquals(paymentMethods.size(), 1);
+        Assert.assertEquals(paymentMethods.get(0).getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
+        Assert.assertEquals(paymentMethods.get(0).getAccountId(), account.getId());
+
+        // The succeeding calls should not create any other payment method
+        final UUID externalPaymentMethodId = paymentMethods.get(0).getId();
+        for (int i = 0; i < 50; i++) {
+            final ExternalPaymentProviderPlugin foundProviderPlugin = processor.getExternalPaymentProviderPlugin(account, context);
+            Assert.assertEquals(foundProviderPlugin.getName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
+
+            final List<PaymentMethod> foundPaymentMethods = processor.getPaymentMethods(account, false);
+            Assert.assertEquals(foundPaymentMethods.size(), 1);
+            Assert.assertEquals(foundPaymentMethods.get(0).getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
+            Assert.assertEquals(foundPaymentMethods.get(0).getAccountId(), account.getId());
+            Assert.assertEquals(foundPaymentMethods.get(0).getId(), externalPaymentMethodId);
+        }
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/glue/TestDefaultPaymentProviderPluginRegistryProvider.java b/payment/src/test/java/com/ning/billing/payment/glue/TestDefaultPaymentProviderPluginRegistryProvider.java
new file mode 100644
index 0000000..ef6ffde
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/glue/TestDefaultPaymentProviderPluginRegistryProvider.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.glue;
+
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.payment.PaymentTestSuite;
+import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+import com.ning.billing.util.clock.Clock;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+public class TestDefaultPaymentProviderPluginRegistryProvider extends PaymentTestSuite {
+
+    @Test(groups = "fast")
+    public void testInjection() throws Exception {
+        final Injector injector = Guice.createInjector(new AbstractModule() {
+            @Override
+            protected void configure() {
+                bind(PaymentConfig.class).toInstance(Mockito.mock(PaymentConfig.class));
+                bind(Clock.class).toInstance(Mockito.mock(Clock.class));
+
+                bind(PaymentProviderPluginRegistry.class)
+                        .toProvider(DefaultPaymentProviderPluginRegistryProvider.class)
+                        .asEagerSingleton();
+            }
+        });
+
+        final PaymentProviderPluginRegistry registry = injector.getInstance(PaymentProviderPluginRegistry.class);
+        Assert.assertNotNull(registry.getPlugin(ExternalPaymentProviderPlugin.PLUGIN_NAME));
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java
new file mode 100644
index 0000000..158f30e
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.payment.PaymentTestSuite;
+import com.ning.billing.payment.plugin.api.PaymentInfoPlugin.PaymentPluginStatus;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestDefaultNoOpPaymentInfoPlugin extends PaymentTestSuite {
+
+    private final Clock clock = new ClockMock();
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final BigDecimal amount = new BigDecimal("1.394810E-3");
+        final DateTime effectiveDate = clock.getUTCNow().plusDays(1);
+        final DateTime createdDate = clock.getUTCNow();
+        final PaymentPluginStatus status = PaymentPluginStatus.UNDEFINED;
+        final String error = UUID.randomUUID().toString();
+
+        final DefaultNoOpPaymentInfoPlugin info = new DefaultNoOpPaymentInfoPlugin(amount, effectiveDate, createdDate,
+                                                                                   status, error);
+        Assert.assertEquals(info, info);
+
+        final DefaultNoOpPaymentInfoPlugin sameInfo = new DefaultNoOpPaymentInfoPlugin(amount, effectiveDate, createdDate,
+                                                                                       status, error);
+        Assert.assertEquals(sameInfo, info);
+
+        final DefaultNoOpPaymentInfoPlugin otherInfo = new DefaultNoOpPaymentInfoPlugin(amount, effectiveDate, createdDate,
+                                                                                        status, UUID.randomUUID().toString());
+        Assert.assertNotEquals(otherInfo, info);
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/TestDefaultNoOpPaymentMethodPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/TestDefaultNoOpPaymentMethodPlugin.java
new file mode 100644
index 0000000..d093077
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/TestDefaultNoOpPaymentMethodPlugin.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.payment.PaymentTestSuite;
+import com.ning.billing.payment.api.PaymentMethodPlugin.PaymentMethodKVInfo;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestDefaultNoOpPaymentMethodPlugin extends PaymentTestSuite {
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final String externalId = UUID.randomUUID().toString();
+        final boolean isDefault = false;
+        final List<PaymentMethodKVInfo> props = ImmutableList.<PaymentMethodKVInfo>of(new PaymentMethodKVInfo(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false));
+
+        final DefaultNoOpPaymentMethodPlugin paymentMethod = new DefaultNoOpPaymentMethodPlugin(externalId, isDefault, props);
+        Assert.assertEquals(paymentMethod, paymentMethod);
+
+        final DefaultNoOpPaymentMethodPlugin samePaymentMethod = new DefaultNoOpPaymentMethodPlugin(externalId, isDefault, props);
+        Assert.assertEquals(samePaymentMethod, paymentMethod);
+
+        final DefaultNoOpPaymentMethodPlugin otherPaymentMethod = new DefaultNoOpPaymentMethodPlugin(externalId, isDefault, ImmutableList.<PaymentMethodKVInfo>of());
+        Assert.assertNotEquals(otherPaymentMethod, paymentMethod);
+    }
+
+    @Test(groups = "fast")
+    public void testEqualsForPaymentMethodKVInfo() throws Exception {
+        final String key = UUID.randomUUID().toString();
+        final Object value = UUID.randomUUID();
+        final boolean updatable = false;
+
+        final PaymentMethodKVInfo kvInfo = new PaymentMethodKVInfo(key, value, updatable);
+        Assert.assertEquals(kvInfo, kvInfo);
+
+        final PaymentMethodKVInfo sameKvInfo = new PaymentMethodKVInfo(key, value, updatable);
+        Assert.assertEquals(sameKvInfo, kvInfo);
+
+        final PaymentMethodKVInfo otherKvInfo = new PaymentMethodKVInfo(key, value, !updatable);
+        Assert.assertNotEquals(otherKvInfo, kvInfo);
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/TestExternalPaymentProviderPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/TestExternalPaymentProviderPlugin.java
new file mode 100644
index 0000000..ff07534
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/TestExternalPaymentProviderPlugin.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.payment.provider;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.Seconds;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.payment.PaymentTestSuite;
+import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
+import com.ning.billing.payment.plugin.api.PaymentInfoPlugin.PaymentPluginStatus;
+import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestExternalPaymentProviderPlugin extends PaymentTestSuite {
+
+    private final Clock clock = new ClockMock();
+    private ExternalPaymentProviderPlugin plugin;
+
+    @BeforeMethod(groups = "fast")
+    public void setUp() throws Exception {
+        plugin = new ExternalPaymentProviderPlugin(clock);
+    }
+
+    @Test(groups = "fast")
+    public void testGetName() throws Exception {
+        Assert.assertEquals(plugin.getName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
+    }
+
+    @Test(groups = "fast")
+    public void testProcessPayment() throws Exception {
+        final String externalKey = UUID.randomUUID().toString();
+        final UUID paymentId = UUID.randomUUID();
+        final BigDecimal amount = BigDecimal.TEN;
+        final PaymentInfoPlugin paymentInfoPlugin = plugin.processPayment(externalKey, paymentId, amount);
+
+        Assert.assertEquals(paymentInfoPlugin.getAmount(), amount);
+        Assert.assertEquals(Seconds.secondsBetween(paymentInfoPlugin.getCreatedDate(), clock.getUTCNow()).getSeconds(), 0);
+        Assert.assertEquals(Seconds.secondsBetween(paymentInfoPlugin.getEffectiveDate(), clock.getUTCNow()).getSeconds(), 0);
+        Assert.assertNull(paymentInfoPlugin.getExtFirstReferenceId());
+        Assert.assertNull(paymentInfoPlugin.getExtSecondReferenceId());
+        Assert.assertNull(paymentInfoPlugin.getGatewayError());
+        Assert.assertNull(paymentInfoPlugin.getGatewayErrorCode());
+        Assert.assertEquals(paymentInfoPlugin.getStatus(), PaymentPluginStatus.PROCESSED);
+
+        final PaymentInfoPlugin retrievedPaymentInfoPlugin = plugin.getPaymentInfo(paymentId);
+        Assert.assertEquals(retrievedPaymentInfoPlugin, paymentInfoPlugin);
+    }
+
+    @Test(groups = "fast", expectedExceptions = PaymentPluginApiException.class)
+    public void testRefundForNonExistingPayment() throws Exception {
+        plugin.processRefund(Mockito.mock(Account.class), UUID.randomUUID(), BigDecimal.ONE);
+    }
+
+    @Test(groups = "fast", expectedExceptions = PaymentPluginApiException.class)
+    public void testRefundTooLarge() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        plugin.processPayment(UUID.randomUUID().toString(), paymentId, BigDecimal.ZERO);
+
+        plugin.processRefund(Mockito.mock(Account.class), paymentId, BigDecimal.ONE);
+    }
+
+    @Test(groups = "fast")
+    public void testRefundTooLargeMultipleTimes() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        plugin.processPayment(UUID.randomUUID().toString(), paymentId, BigDecimal.TEN);
+
+        final Account account = Mockito.mock(Account.class);
+        for (int i = 0; i < 10; i++) {
+            plugin.processRefund(account, paymentId, BigDecimal.ONE);
+        }
+
+        try {
+            plugin.processRefund(account, paymentId, BigDecimal.ONE);
+            Assert.fail("Shouldn't have been able to refund");
+        } catch (PaymentPluginApiException e) {
+            Assert.assertTrue(true);
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testRefund() throws Exception {
+        // An external payment refund would be e.g. a check that we trash
+        final String externalKey = UUID.randomUUID().toString();
+        final UUID paymentId = UUID.randomUUID();
+        final BigDecimal amount = BigDecimal.TEN;
+        plugin.processPayment(externalKey, paymentId, amount);
+
+        plugin.processRefund(Mockito.mock(Account.class), paymentId, BigDecimal.ONE);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), UUID.randomUUID(), BigDecimal.ONE), 0);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, BigDecimal.TEN), 0);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, BigDecimal.ONE), 1);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, new BigDecimal("5")), 0);
+
+        // Try multiple refunds
+
+        plugin.processRefund(Mockito.mock(Account.class), paymentId, BigDecimal.ONE);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), UUID.randomUUID(), BigDecimal.ONE), 0);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, BigDecimal.TEN), 0);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, BigDecimal.ONE), 2);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, new BigDecimal("5")), 0);
+
+        plugin.processRefund(Mockito.mock(Account.class), paymentId, BigDecimal.ONE);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), UUID.randomUUID(), BigDecimal.ONE), 0);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, BigDecimal.TEN), 0);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, BigDecimal.ONE), 3);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, new BigDecimal("5")), 0);
+
+        plugin.processRefund(Mockito.mock(Account.class), paymentId, new BigDecimal("5"));
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), UUID.randomUUID(), BigDecimal.ONE), 0);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, BigDecimal.TEN), 0);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, BigDecimal.ONE), 3);
+        Assert.assertEquals(plugin.getNbRefundForPaymentAmount(Mockito.mock(Account.class), paymentId, new BigDecimal("5")), 1);
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestRetryService.java b/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
index 9a002cb..0c021a6 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
@@ -172,7 +172,7 @@ public class TestRetryService extends PaymentTestSuite {
         setPaymentFailure(failureType);
         boolean failed = false;
         try {
-            paymentProcessor.createPayment(account.getExternalKey(), invoice.getId(), amount, context, false);
+            paymentProcessor.createPayment(account, invoice.getId(), amount, context, false, false);
         } catch (PaymentApiException e) {
             failed = true;
         }

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 8267e5d..7d00037 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
     <groupId>com.ning.billing</groupId>
     <artifactId>killbill</artifactId>
     <packaging>pom</packaging>
-    <version>0.1.25-SNAPSHOT</version>
+    <version>0.1.26-SNAPSHOT</version>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>
     <url>http://github.com/ning/killbill</url>

server/pom.xml 2(+1 -1)

diff --git a/server/pom.xml b/server/pom.xml
index fd40af9..656702f 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-server</artifactId>
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
index b6b73fb..4efe6cb 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -36,14 +36,18 @@ import com.ning.billing.jaxrs.json.AccountJson;
 import com.ning.billing.jaxrs.json.BundleJsonNoSubscriptions;
 import com.ning.billing.jaxrs.json.InvoiceJsonSimple;
 import com.ning.billing.jaxrs.json.PaymentJsonSimple;
+import com.ning.billing.jaxrs.json.PaymentMethodJson;
 import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
 import com.ning.billing.jaxrs.resources.JaxrsResource;
+import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
 import com.ning.http.client.Response;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.collect.ImmutableMap;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
 public class TestInvoice extends TestJaxrsBase {
@@ -222,4 +226,71 @@ public class TestInvoice extends TestJaxrsBase {
             assertTrue(cur.getAmount().compareTo(objFromJson.get(0).getAmount()) == 0);
         }
     }
+
+    @Test(groups = "slow")
+    public void testExternalPayment() throws Exception {
+        // Create an account with no payment method
+        final AccountJson accountJson = createAccount("eraahahildo", "sheqrgfhwe", "eraahahildo@yahoo.com");
+        assertNotNull(accountJson);
+
+        // Add a bundle, subscription and move the clock to get the first invoice
+        final BundleJsonNoSubscriptions bundleJson = createBundle(accountJson.getAccountId(), "317199");
+        assertNotNull(bundleJson);
+        final SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
+        assertNotNull(subscriptionJson);
+        clock.addMonths(1);
+        crappyWaitForLackOfProperSynchonization();
+
+        final String paymentsURI = JaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAccountId() + "/" + JaxrsResource.PAYMENTS;
+
+        // Verify we didn't get any payment
+        final Response noPaymentsResponse = doGet(paymentsURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(noPaymentsResponse.getStatusCode(), Status.OK.getStatusCode());
+        final String noPaymentsBaseJson = noPaymentsResponse.getResponseBody();
+        final List<PaymentJsonSimple> noPaymentsFromJson = mapper.readValue(noPaymentsBaseJson, new TypeReference<List<PaymentJsonSimple>>() {});
+        assertEquals(noPaymentsFromJson.size(), 0);
+
+        // Get the invoices
+        final Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(JaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAccountId());
+        final String invoicesURI = JaxrsResource.INVOICES_PATH;
+        final Response invoicesResponse = doGet(invoicesURI, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(invoicesResponse.getStatusCode(), Status.OK.getStatusCode());
+        final String invoicesBaseJson = invoicesResponse.getResponseBody();
+        final List<InvoiceJsonSimple> invoices = mapper.readValue(invoicesBaseJson, new TypeReference<List<InvoiceJsonSimple>>() {});
+        assertNotNull(invoices);
+        // 2 invoices but look for the non zero dollar one
+        assertEquals(invoices.size(), 2);
+        final String invoiceId = invoices.get(1).getInvoiceId();
+
+        // Post an external payment
+        final BigDecimal paidAmount = BigDecimal.TEN;
+        final PaymentJsonSimple payment = new PaymentJsonSimple(paidAmount, BigDecimal.ZERO, accountJson.getAccountId(),
+                                                                invoiceId, null, null, null, null, 0,
+                                                                null, null, null, null, null, null);
+        final String postJson = mapper.writeValueAsString(payment);
+        final String paymentURI = JaxrsResource.INVOICES_PATH + "/" + invoiceId + "/" + JaxrsResource.PAYMENTS;
+        final Response paymentResponse = doPost(paymentURI, postJson, ImmutableMap.<String, String>of("externalPayment", "true"), DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(paymentResponse.getStatusCode(), Status.CREATED.getStatusCode());
+
+        // Verify we indeed got the payment
+        final Response paymentsResponse = doGet(paymentsURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(paymentsResponse.getStatusCode(), Status.OK.getStatusCode());
+        final String paymentsBaseJson = paymentsResponse.getResponseBody();
+        final List<PaymentJsonSimple> paymentsFromJson = mapper.readValue(paymentsBaseJson, new TypeReference<List<PaymentJsonSimple>>() {});
+        assertEquals(paymentsFromJson.size(), 1);
+        assertEquals(paymentsFromJson.get(0).getPaidAmount().compareTo(paidAmount), 0);
+
+        // Check the PaymentMethod from paymentMethodId returned in the Payment object
+        final String paymentMethodId = paymentsFromJson.get(0).getPaymentMethodId();
+        final String paymentMethodURI = JaxrsResource.PAYMENT_METHODS_PATH + "/" + paymentMethodId;
+
+        final Response paymentMethodResponse = doGet(paymentMethodURI, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(paymentMethodResponse.getStatusCode(), Status.OK.getStatusCode());
+        final PaymentMethodJson paymentMethodJson = mapper.readValue(paymentMethodResponse.getResponseBody(), PaymentMethodJson.class);
+        assertEquals(paymentMethodJson.getPaymentMethodId(), paymentMethodId);
+        assertEquals(paymentMethodJson.getAccountId(), accountJson.getAccountId());
+        assertEquals(paymentMethodJson.getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
+        assertNull(paymentMethodJson.getPluginInfo());
+    }
 }

util/pom.xml 2(+1 -1)

diff --git a/util/pom.xml b/util/pom.xml
index dcafe4e..298a452 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.25-SNAPSHOT</version>
+        <version>0.1.26-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>