killbill-aplcache

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
index 81d7a94..b501af7 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
@@ -116,6 +116,20 @@ public class TestPaymentRefund extends TestIntegrationBase {
         }
     }
 
+    @Test(groups = "slow", description = "https://github.com/killbill/killbill/issues/255",
+            expectedExceptions = PaymentApiException.class, expectedExceptionsMessageRegExp = "Payment method .* does not exist")
+    public void testRefundWithDeletedPaymentMethod() throws Exception {
+
+        // delete payment method
+        busHandler.pushExpectedEvent(NextEvent.TAG);
+        paymentApi.deletePaymentMethod(account, account.getPaymentMethodId(), true, true, new ArrayList<PluginProperty>(), callContext);
+        assertListenerStatus();
+
+        // try to create a refund for a payment with its payment method deleted
+        paymentApi.createRefund(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(),
+                                UUID.randomUUID().toString(), PLUGIN_PROPERTIES, callContext);
+    }
+
     private void setupRefundTest() throws Exception {
 
         final int billingDay = 31;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
index 8c14531..f2f1a01 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
@@ -145,6 +145,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
                            @ApiResponse(code = 404, message = "Account or payment not found")})
     public Response createRefundWithAdjustments(final InvoicePaymentTransactionJson json,
                                                 @PathParam("paymentId") final String paymentId,
+                                                @QueryParam(QUERY_PAYMENT_EXTERNAL) @DefaultValue("false") final Boolean externalPayment,
                                                 @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                                 @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                                 @HeaderParam(HDR_REASON) final String reason,
@@ -160,6 +161,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
 
         final Iterable<PluginProperty> pluginProperties;
         final String transactionExternalKey = json.getTransactionExternalKey() != null ? json.getTransactionExternalKey() : UUIDs.randomUUID().toString();
+        final String paymentExternalKey = json.getPaymentExternalKey() != null ? json.getPaymentExternalKey() : UUIDs.randomUUID().toString();
         if (json.isAdjusted() != null && json.isAdjusted()) {
             if (json.getAdjustments() != null && json.getAdjustments().size() > 0) {
                 final Map<UUID, BigDecimal> adjustments = new HashMap<UUID, BigDecimal>();
@@ -177,8 +179,17 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
             pluginProperties = extractPluginProperties(pluginPropertiesString);
         }
 
-        final Payment result = paymentApi.createRefundWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(), transactionExternalKey,
-                                                                               pluginProperties, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
+        final Payment result;
+        if (externalPayment) {
+
+            result = paymentApi.createCreditWithPaymentControl(account, null, null, json.getAmount(), account.getCurrency(),
+                                                               paymentExternalKey, transactionExternalKey, pluginProperties,
+                                                               createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
+        } else {
+            result = paymentApi.createRefundWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(), transactionExternalKey,
+                                                               pluginProperties, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
+        }
+
         return uriBuilder.buildResponse(InvoicePaymentResource.class, "getInvoicePayment", result.getId(), uriInfo.getBaseUri().toString());
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index a5f5ab1..ba275b6 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -648,7 +648,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         verifyNonNullOrEmpty(payment.getAccountId(), "InvoicePaymentJson accountId needs to be set",
                              payment.getTargetInvoiceId(), "InvoicePaymentJson targetInvoiceId needs to be set",
                              payment.getPurchasedAmount(), "InvoicePaymentJson purchasedAmount needs to be set");
-        Preconditions.checkArgument(!externalPayment || payment.getPaymentMethodId() == null, "InvoicePaymentJson should not contain a paymwentMethodId when this is an external payment");
+        Preconditions.checkArgument(!externalPayment || payment.getPaymentMethodId() == null, "InvoicePaymentJson should not contain a paymentMethodId when this is an external payment");
 
 
         final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
index 394320d..f1430b9 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
@@ -299,6 +299,10 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
             throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, "paymentMethodId", "should not be null");
         }
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+
+        // TODO validate if the code is located properly here
+        // The code should understand that the external payment method needs to be created
+        // if it doesn't exist yet and trigger a CREDIT using the (built-in) external payment plugin.
         final UUID nonNulPaymentMethodId = (paymentMethodId != null) ?
                                            paymentMethodId :
                                            paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
@@ -499,7 +503,6 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
                                 @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
                                 final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
         checkNotNullParameter(account, "account");
-        checkNotNullParameter(paymentMethodId, "paymentMethodId");
         if (paymentId == null) {
             checkNotNullParameter(amount, "amount");
             checkNotNullParameter(currency, "currency");
@@ -513,7 +516,11 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
             logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, null);
 
             final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-            payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+            final UUID nonNulPaymentMethodId = (paymentMethodId != null) ?
+                                               paymentMethodId :
+                                               paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+
+            payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
                                                                   SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
 
             paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -545,7 +552,6 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         }
 
         checkNotNullParameter(account, "account");
-        checkNotNullParameter(paymentMethodId, "paymentMethodId");
         if (paymentId == null) {
             checkNotNullParameter(amount, "amount");
             checkNotNullParameter(currency, "currency");
@@ -559,7 +565,15 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
             logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
             final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-            payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+
+            // TODO validate if the code is located properly here
+            // The code should understand that the external payment method needs to be created
+            // if it doesn't exist yet and trigger a CREDIT using the (built-in) external payment plugin.
+            final UUID nonNulPaymentMethodId = (paymentMethodId != null) ?
+                                               paymentMethodId :
+                                               paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+
+            payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
                                                                                properties, paymentControlPluginNames, callContext, internalCallContext);
 
             paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
index 012d7c0..de91986 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -68,6 +68,7 @@ import org.killbill.billing.payment.core.sm.payments.VoidCompleted;
 import org.killbill.billing.payment.core.sm.payments.VoidInitiated;
 import org.killbill.billing.payment.core.sm.payments.VoidOperation;
 import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentMethodModelDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
 import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
@@ -128,8 +129,10 @@ public class PaymentAutomatonRunner {
                                                         final Iterable<PluginProperty> properties,
                                                         final CallContext callContext,
                                                         final InternalCallContext internalCallContext) throws PaymentApiException {
+
         // Retrieve the payment id from the payment external key if needed
         final UUID effectivePaymentId = paymentId != null ? paymentId : retrievePaymentId(paymentExternalKey, paymentTransactionExternalKey, internalCallContext);
+        verifyPaymentMethodExistsForRefund(effectivePaymentId, transactionType, internalCallContext);
 
         return new PaymentStateContext(isApiPayment,
                                        effectivePaymentId,
@@ -284,4 +287,15 @@ public class PaymentAutomatonRunner {
 
         return paymentIdCandidate;
     }
+
+    private void verifyPaymentMethodExistsForRefund(final UUID paymentId, final TransactionType transactionType, final InternalCallContext internalCallContext) throws PaymentApiException {
+        if (TransactionType.REFUND.equals(transactionType) && (paymentId != null)) {
+            final PaymentModelDao payment = paymentDao.getPayment(paymentId, internalCallContext);
+            final PaymentMethodModelDao paymentMethod = paymentDao.getPaymentMethod(payment.getPaymentMethodId(), internalCallContext);
+            if (paymentMethod == null) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, payment.getPaymentMethodId());
+            }
+        }
+    }
+
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
index 912d736..e9441b1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/invoice/InvoicePaymentControlPluginApi.java
@@ -131,7 +131,8 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         Preconditions.checkArgument(paymentControlContext.getPaymentApiType() == PaymentApiType.PAYMENT_TRANSACTION);
         Preconditions.checkArgument(transactionType == TransactionType.PURCHASE ||
                                     transactionType == TransactionType.REFUND ||
-                                    transactionType == TransactionType.CHARGEBACK);
+                                    transactionType == TransactionType.CHARGEBACK ||
+                                   transactionType == TransactionType.CREDIT);
 
         final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
         switch (transactionType) {
@@ -141,6 +142,8 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                 return getPluginRefundResult(paymentControlContext, pluginProperties, internalContext);
             case CHARGEBACK:
                 return new DefaultPriorPaymentControlResult(false, paymentControlContext.getAmount());
+            case CREDIT:
+                return getPluginCreditResult(paymentControlContext, pluginProperties, internalContext);
             default:
                 throw new IllegalStateException("Unexpected transactionType " + transactionType);
         }
@@ -151,7 +154,8 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         final TransactionType transactionType = paymentControlContext.getTransactionType();
         Preconditions.checkArgument(transactionType == TransactionType.PURCHASE ||
                                     transactionType == TransactionType.REFUND ||
-                                    transactionType == TransactionType.CHARGEBACK);
+                                    transactionType == TransactionType.CHARGEBACK ||
+                                    transactionType == TransactionType.CREDIT);
 
         final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
         try {
@@ -216,6 +220,10 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                     }
                     break;
 
+                case CREDIT:
+                    // TODO implement
+                    break;
+
                 default:
                     throw new IllegalStateException("Unexpected transactionType " + transactionType);
             }
@@ -401,6 +409,11 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         return new DefaultPriorPaymentControlResult(isAborted, amountToBeRefunded);
     }
 
+    private PriorPaymentControlResult getPluginCreditResult(final PaymentControlContext paymentControlPluginContext, final Iterable<PluginProperty> pluginProperties, final InternalCallContext internalContext) throws PaymentControlApiException {
+        // TODO implement
+        return new DefaultPriorPaymentControlResult(false, paymentControlPluginContext.getAmount());
+    }
+
     private Map<UUID, BigDecimal> extractIdsWithAmountFromProperties(final Iterable<PluginProperty> properties) {
         final PluginProperty prop = getPluginProperty(properties, PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY);
         if (prop == null) {

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 12119d8..03d6a18 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.123</version>
+        <version>0.124-SNAPSHOT</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.17.3-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
index 1380883..4854bc7 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/KillbillClient.java
@@ -36,7 +36,12 @@ import org.killbill.billing.client.model.PaymentMethod;
 import org.killbill.billing.client.model.PaymentMethodPluginDetail;
 import org.killbill.billing.client.model.PluginProperty;
 import org.killbill.billing.client.model.Subscription;
+import org.killbill.billing.client.model.Tags;
+import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
+import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.tag.ControlTagType;
 
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 
 public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedDB {
@@ -111,6 +116,16 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
         return killBillClient.getAccount(input.getExternalKey());
     }
 
+    protected Account createAccountWithExternalPaymentMethod() throws Exception {
+        final Account input = createAccount();
+
+        final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
+        final PaymentMethod paymentMethodJson = new PaymentMethod(null, UUIDs.randomUUID().toString(), input.getAccountId(),
+                                                                  true, ExternalPaymentProviderPlugin.PLUGIN_NAME, info);
+        killBillClient.createPaymentMethod(paymentMethodJson, requestOptions);
+        return killBillClient.getAccount(input.getExternalKey(), requestOptions);
+    }
+
     protected Account createAccount() throws Exception {
         return createAccount(null);
     }
@@ -147,6 +162,24 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
         return accountJson;
     }
 
+    protected Account createAccountWithExternalPMBundleAndSubscriptionAndManualPayTagAndWaitForFirstInvoice() throws Exception {
+        final Account accountJson = createAccountWithExternalPaymentMethod();
+        assertNotNull(accountJson);
+
+        final Tags accountTag = killBillClient.createAccountTag(accountJson.getAccountId(), ControlTagType.MANUAL_PAY.getId(), requestOptions);
+        assertNotNull(accountTag);
+        assertEquals(accountTag.get(0).getTagDefinitionId(), ControlTagType.MANUAL_PAY.getId());
+
+        // Add a bundle, subscription and move the clock to get the first invoice
+        final Subscription subscriptionJson = createEntitlement(accountJson.getAccountId(), UUID.randomUUID().toString(), "Shotgun",
+                                                                ProductCategory.BASE, BillingPeriod.MONTHLY, true);
+        assertNotNull(subscriptionJson);
+        clock.addDays(32);
+        crappyWaitForLackOfProperSynchonization();
+
+        return accountJson;
+    }
+
     protected Account createAccountNoPMBundleAndSubscription() throws Exception {
         // Create an account with no payment method
         final Account accountJson = createAccount();
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExternalRefund.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExternalRefund.java
new file mode 100644
index 0000000..e364682
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExternalRefund.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
+ *
+ * The Billing Project 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 org.killbill.billing.jaxrs;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.Invoice;
+import org.killbill.billing.client.model.InvoiceItem;
+import org.killbill.billing.client.model.InvoicePayment;
+import org.killbill.billing.client.model.InvoicePaymentTransaction;
+import org.killbill.billing.client.model.Invoices;
+import org.killbill.billing.client.model.Payment;
+import org.killbill.billing.client.model.Payments;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+public class TestExternalRefund extends TestJaxrsBase {
+
+    @Test(groups = "slow", description = "#255 - Scenario 1 - Can refund a manual payment though an external refund")
+    public void testManualPaymentAndExternalRefund() throws Exception {
+        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account accountJson = createAccountWithExternalPMBundleAndSubscriptionAndManualPayTagAndWaitForFirstInvoice();
+
+        final Invoices invoicesForAccount = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), requestOptions);
+        final Invoice unpaidInvoice = invoicesForAccount.get(1);
+        assertEquals(unpaidInvoice.getBalance().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+        final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+        assertEquals(paymentsForAccount.size(), 0);
+
+        final InvoicePayment invoicePaymentRequest = new InvoicePayment();
+        invoicePaymentRequest.setTargetInvoiceId(unpaidInvoice.getInvoiceId());
+        invoicePaymentRequest.setAccountId(accountJson.getAccountId());
+        invoicePaymentRequest.setCurrency(unpaidInvoice.getCurrency().toString());
+        invoicePaymentRequest.setPurchasedAmount(unpaidInvoice.getAmount());
+        final InvoicePayment invoicePayment = killBillClient.createInvoicePayment(invoicePaymentRequest, true, requestOptions);
+        assertEquals(invoicePayment.getPurchasedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+        assertEquals(invoicePayment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+
+        final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+        invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+        invoicePaymentTransactionRequest.setPaymentId(invoicePayment.getPaymentId());
+        final InvoicePayment invoicePaymentRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, requestOptions);
+
+        assertNotNull(invoicePaymentRefund);
+        assertEquals(invoicePaymentRefund.getPurchasedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+        assertEquals(invoicePaymentRefund.getRefundedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+    }
+
+    @Test(groups = "slow", description = "#255 - Scenario 2a - Can refund an automatic payment though an external refund")
+    public void testAutomaticPaymentAndExternalRefund() throws Exception {
+        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+        // delete PM
+        killBillClient.deletePaymentMethod(accountJson.getPaymentMethodId(), true, true, requestOptions);
+        final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+        final Payment payment = paymentsForAccount.get(paymentsForAccount.size() - 1);
+
+        // external refund
+        final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+        invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+        invoicePaymentTransactionRequest.setCurrency(accountJson.getCurrency().toString());
+        invoicePaymentTransactionRequest.setPaymentId(payment.getPaymentId());
+        final InvoicePayment invoicePaymentRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, true, requestOptions);
+        assertNotNull(invoicePaymentRefund);
+        assertEquals(invoicePaymentRefund.getCreditedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+        // TODO check account balance
+        // TODO check invoice items
+
+    }
+
+    @Test(groups = "slow", description = "#255 - Scenario 2a - Can refund an automatic payment though an external refund over item adjustments")
+    public void testAutomaticPaymentAndExternalRefundWithAdjustments() throws Exception {
+        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+        // delete PM
+        killBillClient.deletePaymentMethod(accountJson.getPaymentMethodId(), true, true, requestOptions);
+        final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+        final Payment payment = paymentsForAccount.get(paymentsForAccount.size() - 1);
+
+        final Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, true, requestOptions);
+        final List<InvoiceItem> itemsToBeAdjusted = invoices.get(1).getItems();
+
+        // external refund
+        final InvoicePaymentTransaction invoicePaymentTransactionRequest = new InvoicePaymentTransaction();
+        invoicePaymentTransactionRequest.setAmount(BigDecimal.valueOf(249.95));
+        invoicePaymentTransactionRequest.setCurrency(accountJson.getCurrency().toString());
+        invoicePaymentTransactionRequest.setPaymentId(payment.getPaymentId());
+        invoicePaymentTransactionRequest.setIsAdjusted(true);
+        invoicePaymentTransactionRequest.setAdjustments(itemsToBeAdjusted);
+        final InvoicePayment invoicePaymentRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, true, requestOptions);
+        assertNotNull(invoicePaymentRefund);
+        assertEquals(invoicePaymentRefund.getCreditedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+        // TODO check account balance
+        // TODO check invoice items
+
+    }
+
+    @Test(groups = "slow", description = "#255 - Scenario 2b - Can refund an automatic payment though an external refund")
+    public void testAutomaticPaymentAndRefundWithDifferentPM() throws Exception {
+        final DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+
+        final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        // TODO complete test
+
+    }
+
+}