killbill-aplcache

payment: implement external payments An external payment

7/27/2012 8:05:01 PM

Details

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 f6e6eea..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;
@@ -28,17 +29,20 @@ public interface PaymentApi {
     public Payment createPayment(final Account account, final UUID invoiceId, final BigDecimal amount, final CallContext context)
             throws PaymentApiException;
 
+    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;
@@ -50,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/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());
     }
 
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 c2e2fb2..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
@@ -21,7 +21,6 @@ import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 
-import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.payment.core.PaymentMethodProcessor;
@@ -29,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;
@@ -48,7 +48,12 @@ public class DefaultPaymentApi implements PaymentApi {
     @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);
+        return paymentProcessor.createPayment(account, invoiceId, amount, context, true, false);
+    }
+
+    @Override
+    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
@@ -71,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);
@@ -90,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)
@@ -122,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 c6d29a0..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,9 +201,19 @@ public class PaymentProcessor extends ProcessorBase {
         }
     }
 
-    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);
+    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();
+        }
 
         try {
             return paymentPluginDispatcher.dispatchWithAccountLock(new CallableWithAccountLock<Payment>(locker,
@@ -225,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);
                     }
                 }
             }));
@@ -285,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) {
@@ -297,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);
     }
 
@@ -388,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/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..8b80f43
--- /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
+    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/TestRetryService.java b/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
index aef8f1b..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, invoice.getId(), amount, context, false);
+            paymentProcessor.createPayment(account, invoice.getId(), amount, context, false, false);
         } catch (PaymentApiException e) {
             failed = true;
         }
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());
+    }
 }