killbill-memoizeit

Changes

pom.xml 2(+1 -1)

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..ff2ed5a 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
@@ -19,7 +19,9 @@
 package org.killbill.billing.jaxrs.resources;
 
 import java.math.BigDecimal;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -55,6 +57,7 @@ import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PaymentMethod;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.util.UUIDs;
 import org.killbill.billing.util.api.AuditUserApi;
@@ -70,6 +73,7 @@ import org.killbill.clock.Clock;
 import org.killbill.commons.metrics.TimedResource;
 
 import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
@@ -145,6 +149,8 @@ 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_PAYMENT_METHOD_ID) @DefaultValue("") final String paymentMethodId,
                                                 @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                                 @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                                 @HeaderParam(HDR_REASON) final String reason,
@@ -160,6 +166,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 +184,22 @@ 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) {
+            UUID externalPaymentMethodId = Strings.isNullOrEmpty(paymentMethodId) ? null : UUID.fromString(paymentMethodId);
+
+            final Collection<PluginProperty> pluginPropertiesForExternalRefund = new LinkedList<PluginProperty>();
+            Iterables.addAll(pluginPropertiesForExternalRefund, pluginProperties);
+            pluginPropertiesForExternalRefund.add(new PluginProperty("IPCD_PAYMENT_ID", paymentUuid, false));
+
+            result = paymentApi.createCreditWithPaymentControl(account, externalPaymentMethodId, null, json.getAmount(), account.getCurrency(),
+                                                               paymentExternalKey, transactionExternalKey, pluginPropertiesForExternalRefund,
+                                                               createInvoicePaymentControlPluginApiPaymentOptions(true), 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 ac0702a..bb40321 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,7 +299,11 @@ 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);
-        final UUID nonNulPaymentMethodId = (paymentMethodId != null) ?
+
+        // 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 nonNullPaymentMethodId = (paymentMethodId != null) ?
                                            paymentMethodId :
                                            paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
 
@@ -310,7 +314,7 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         try {
             logEnterAPICall(log, transactionType, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, null, paymentControlPluginNames);
 
-            payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+            payment = pluginControlPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
                                                                                  properties, paymentControlPluginNames, callContext, internalCallContext);
 
             paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -498,7 +502,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");
@@ -512,7 +515,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 nonNullPaymentMethodId = (paymentMethodId != null) ?
+                                               paymentMethodId :
+                                               paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+
+            payment = paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, nonNullPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
                                                                   SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
 
             paymentTransaction = findPaymentTransaction(payment, paymentTransactionExternalKey);
@@ -544,7 +551,6 @@ public class DefaultPaymentApi extends DefaultApiBase implements PaymentApi {
         }
 
         checkNotNullParameter(account, "account");
-        checkNotNullParameter(paymentMethodId, "paymentMethodId");
         if (paymentId == null) {
             checkNotNullParameter(amount, "amount");
             checkNotNullParameter(currency, "currency");
@@ -558,7 +564,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 nonNullPaymentMethodId = (paymentMethodId != null) ?
+                                               paymentMethodId :
+                                               paymentMethodProcessor.createOrGetExternalPaymentMethod(UUIDs.randomUUID().toString(), account, properties, callContext, internalCallContext);
+
+            payment = pluginControlPaymentProcessor.createCredit(IS_API_PAYMENT, account, nonNullPaymentMethodId, 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/PaymentAutomatonDAOHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
index fa55ff0..c546b0c 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
@@ -149,13 +149,14 @@ public class PaymentAutomatonDAOHelper {
         paymentStateContext.setPaymentTransactionModelDao(paymentDao.getPaymentTransaction(paymentStateContext.getPaymentTransactionModelDao().getId(), internalCallContext));
     }
 
-    public String getPaymentProviderPluginName() throws PaymentApiException {
+    public String getPaymentProviderPluginName(final boolean includeDeteled) throws PaymentApiException {
         if (pluginName != null) {
             return pluginName;
         }
 
         final UUID paymentMethodId = paymentStateContext.getPaymentMethodId();
-        final PaymentMethodModelDao methodDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodId, internalCallContext);
+        final PaymentMethodModelDao methodDao = (includeDeteled) ? paymentDao.getPaymentMethodIncludedDeleted(paymentMethodId, internalCallContext) :
+                paymentDao.getPaymentMethod(paymentMethodId, internalCallContext);
         if (methodDao == null) {
             throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
         }
@@ -164,7 +165,7 @@ public class PaymentAutomatonDAOHelper {
     }
 
     public PaymentPluginApi getPaymentPluginApi() throws PaymentApiException {
-        final String pluginName = getPaymentProviderPluginName();
+        final String pluginName = getPaymentProviderPluginName(false);
         return getPaymentPluginApi(pluginName);
     }
 
@@ -228,4 +229,8 @@ public class PaymentAutomatonDAOHelper {
                                               gatewayErrorCode,
                                               gatewayErrorMsg);
     }
+
+    public String getPluginName() {
+        return pluginName;
+    }
 }
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..c1304a7 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
@@ -128,6 +128,7 @@ 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);
 
@@ -165,6 +166,7 @@ public class PaymentAutomatonRunner {
         final OperationCallback operationCallback;
         final LeavingStateCallback leavingStateCallback;
         final EnteringStateCallback enteringStateCallback;
+        Boolean includeDeletedPaymentMethod = Boolean.FALSE;
         switch (transactionType) {
             case PURCHASE:
                 operationCallback = new PurchaseOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
@@ -200,12 +202,13 @@ public class PaymentAutomatonRunner {
                 operationCallback = new ChargebackOperation(daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);
                 leavingStateCallback = new ChargebackInitiated(daoHelper, paymentStateContext);
                 enteringStateCallback = new ChargebackCompleted(daoHelper, paymentStateContext);
+                includeDeletedPaymentMethod = Boolean.TRUE;
                 break;
             default:
                 throw new IllegalStateException("Unsupported transaction type " + transactionType);
         }
 
-        runStateMachineOperation(currentStateName, transactionType, leavingStateCallback, operationCallback, enteringStateCallback, paymentStateContext, daoHelper);
+        runStateMachineOperation(currentStateName, transactionType, leavingStateCallback, operationCallback, enteringStateCallback, includeDeletedPaymentMethod, paymentStateContext, daoHelper);
 
         return paymentStateContext.getPaymentId();
     }
@@ -226,10 +229,11 @@ public class PaymentAutomatonRunner {
                                           final LeavingStateCallback leavingStateCallback,
                                           final OperationCallback operationCallback,
                                           final EnteringStateCallback enteringStateCallback,
+                                          final Boolean includeDeletedPaymentMethod,
                                           final PaymentStateContext paymentStateContext,
                                           final PaymentAutomatonDAOHelper daoHelper) throws PaymentApiException {
         try {
-            final StateMachineConfig stateMachineConfig = paymentSMHelper.getStateMachineConfig(daoHelper.getPaymentProviderPluginName(), paymentStateContext.getInternalCallContext());
+            final StateMachineConfig stateMachineConfig = paymentSMHelper.getStateMachineConfig(daoHelper.getPaymentProviderPluginName(includeDeletedPaymentMethod), paymentStateContext.getInternalCallContext());
             final StateMachine initialStateMachine = stateMachineConfig.getStateMachineForState(initialStateName);
             final State initialState = initialStateMachine.getState(initialStateName);
             final Operation operation = paymentSMHelper.getOperationForTransaction(stateMachineConfig, transactionType);
@@ -284,4 +288,5 @@ public class PaymentAutomatonRunner {
 
         return paymentIdCandidate;
     }
+
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java
index fdbe229..d8cf528 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/AuthorizeOperation.java
@@ -44,13 +44,13 @@ public class AuthorizeOperation extends PaymentOperation {
     @Override
     protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
         logger.debug("Starting AUTHORIZE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
-        return plugin.authorizePayment(paymentStateContext.getAccount().getId(),
-                                       paymentStateContext.getPaymentId(),
-                                       paymentStateContext.getTransactionId(),
-                                       paymentStateContext.getPaymentMethodId(),
-                                       paymentStateContext.getAmount(),
-                                       paymentStateContext.getCurrency(),
-                                       paymentStateContext.getProperties(),
-                                       paymentStateContext.getCallContext());
+        return paymentPluginApi.authorizePayment(paymentStateContext.getAccount().getId(),
+                                                 paymentStateContext.getPaymentId(),
+                                                 paymentStateContext.getTransactionId(),
+                                                 paymentStateContext.getPaymentMethodId(),
+                                                 paymentStateContext.getAmount(),
+                                                 paymentStateContext.getCurrency(),
+                                                 paymentStateContext.getProperties(),
+                                                 paymentStateContext.getCallContext());
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java
index cbafd42..6f4440d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CaptureOperation.java
@@ -44,13 +44,13 @@ public class CaptureOperation extends PaymentOperation {
     @Override
     protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
         logger.debug("Starting CAPTURE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
-        return plugin.capturePayment(paymentStateContext.getAccount().getId(),
-                                     paymentStateContext.getPaymentId(),
-                                     paymentStateContext.getTransactionId(),
-                                     paymentStateContext.getPaymentMethodId(),
-                                     paymentStateContext.getAmount(),
-                                     paymentStateContext.getCurrency(),
-                                     paymentStateContext.getProperties(),
-                                     paymentStateContext.getCallContext());
+        return paymentPluginApi.capturePayment(paymentStateContext.getAccount().getId(),
+                                               paymentStateContext.getPaymentId(),
+                                               paymentStateContext.getTransactionId(),
+                                               paymentStateContext.getPaymentMethodId(),
+                                               paymentStateContext.getAmount(),
+                                               paymentStateContext.getCurrency(),
+                                               paymentStateContext.getProperties(),
+                                               paymentStateContext.getCallContext());
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java
index 2d26268..f98908e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/CreditOperation.java
@@ -44,13 +44,13 @@ public class CreditOperation extends PaymentOperation {
     @Override
     protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
         logger.debug("Starting CREDIT for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
-        return plugin.creditPayment(paymentStateContext.getAccount().getId(),
-                                    paymentStateContext.getPaymentId(),
-                                    paymentStateContext.getTransactionId(),
-                                    paymentStateContext.getPaymentMethodId(),
-                                    paymentStateContext.getAmount(),
-                                    paymentStateContext.getCurrency(),
-                                    paymentStateContext.getProperties(),
-                                    paymentStateContext.getCallContext());
+        return paymentPluginApi.creditPayment(paymentStateContext.getAccount().getId(),
+                                              paymentStateContext.getPaymentId(),
+                                              paymentStateContext.getTransactionId(),
+                                              paymentStateContext.getPaymentMethodId(),
+                                              paymentStateContext.getAmount(),
+                                              paymentStateContext.getCurrency(),
+                                              paymentStateContext.getProperties(),
+                                              paymentStateContext.getCallContext());
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
index d6a1e7d..cd77f0c 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PaymentOperation.java
@@ -55,7 +55,7 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
     private final Logger logger = LoggerFactory.getLogger(PaymentOperation.class);
 
     protected final PaymentAutomatonDAOHelper daoHelper;
-    protected PaymentPluginApi plugin;
+    protected PaymentPluginApi paymentPluginApi;
 
     protected PaymentOperation(final GlobalLocker locker,
                                final PaymentAutomatonDAOHelper daoHelper,
@@ -68,17 +68,15 @@ public abstract class PaymentOperation extends OperationCallbackBase<PaymentTran
 
     @Override
     public OperationResult doOperationCallback() throws OperationException {
-        final String pluginName;
         try {
-            pluginName = daoHelper.getPaymentProviderPluginName();
-            this.plugin = daoHelper.getPaymentPluginApi();
+            this.paymentPluginApi = daoHelper.getPaymentPluginApi();
         } catch (final PaymentApiException e) {
             throw convertToUnknownTransactionStatusAndErroredPaymentState(e);
         }
 
         if (paymentStateContext.shouldLockAccountAndDispatch()) {
             // This will already call unwrapExceptionFromDispatchedTask
-            return doOperationCallbackWithDispatchAndAccountLock(pluginName);
+            return doOperationCallbackWithDispatchAndAccountLock(daoHelper.getPluginName());
         } else {
             try {
                 return doSimpleOperationCallback();
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java
index 7fab5e1..aff762f 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/PurchaseOperation.java
@@ -44,13 +44,13 @@ public class PurchaseOperation extends PaymentOperation {
     @Override
     protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
         logger.debug("Starting PURCHASE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
-        return plugin.purchasePayment(paymentStateContext.getAccount().getId(),
-                                      paymentStateContext.getPaymentId(),
-                                      paymentStateContext.getTransactionId(),
-                                      paymentStateContext.getPaymentMethodId(),
-                                      paymentStateContext.getAmount(),
-                                      paymentStateContext.getCurrency(),
-                                      paymentStateContext.getProperties(),
-                                      paymentStateContext.getCallContext());
+        return paymentPluginApi.purchasePayment(paymentStateContext.getAccount().getId(),
+                                                paymentStateContext.getPaymentId(),
+                                                paymentStateContext.getTransactionId(),
+                                                paymentStateContext.getPaymentMethodId(),
+                                                paymentStateContext.getAmount(),
+                                                paymentStateContext.getCurrency(),
+                                                paymentStateContext.getProperties(),
+                                                paymentStateContext.getCallContext());
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java
index b236ac0..1d86abb 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/RefundOperation.java
@@ -44,13 +44,13 @@ public class RefundOperation extends PaymentOperation {
     @Override
     protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
         logger.debug("Starting REFUND for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
-        return plugin.refundPayment(paymentStateContext.getAccount().getId(),
-                                    paymentStateContext.getPaymentId(),
-                                    paymentStateContext.getTransactionId(),
-                                    paymentStateContext.getPaymentMethodId(),
-                                    paymentStateContext.getAmount(),
-                                    paymentStateContext.getCurrency(),
-                                    paymentStateContext.getProperties(),
-                                    paymentStateContext.getCallContext());
+        return paymentPluginApi.refundPayment(paymentStateContext.getAccount().getId(),
+                                              paymentStateContext.getPaymentId(),
+                                              paymentStateContext.getTransactionId(),
+                                              paymentStateContext.getPaymentMethodId(),
+                                              paymentStateContext.getAmount(),
+                                              paymentStateContext.getCurrency(),
+                                              paymentStateContext.getProperties(),
+                                              paymentStateContext.getCallContext());
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java
index b6542d9..c932c59 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/payments/VoidOperation.java
@@ -44,11 +44,11 @@ public class VoidOperation extends PaymentOperation {
     @Override
     protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
         logger.debug("Starting VOID for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
-        return plugin.voidPayment(paymentStateContext.getAccount().getId(),
-                                  paymentStateContext.getPaymentId(),
-                                  paymentStateContext.getTransactionId(),
-                                  paymentStateContext.getPaymentMethodId(),
-                                  paymentStateContext.getProperties(),
-                                  paymentStateContext.getCallContext());
+        return paymentPluginApi.voidPayment(paymentStateContext.getAccount().getId(),
+                                            paymentStateContext.getPaymentId(),
+                                            paymentStateContext.getTransactionId(),
+                                            paymentStateContext.getPaymentMethodId(),
+                                            paymentStateContext.getProperties(),
+                                            paymentStateContext.getCallContext());
     }
 }
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..b8bc30f 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
@@ -93,6 +93,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
     public static final String PROP_IPCD_INVOICE_ID = "IPCD_INVOICE_ID";
     public static final String PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY = "IPCD_REFUND_IDS_AMOUNTS";
     public static final String PROP_IPCD_REFUND_WITH_ADJUSTMENTS = "IPCD_REFUND_WITH_ADJUSTMENTS";
+    public static final String PROP_IPCD_PAYMENT_ID = "IPCD_PAYMENT_ID";
 
     private final PaymentConfig paymentConfig;
     private final InvoiceInternalApi invoiceApi;
@@ -131,7 +132,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 +143,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 +155,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 +221,17 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
                     }
                     break;
 
+                case CREDIT:
+                    final Map<UUID, BigDecimal> idWithAmountMap = extractIdsWithAmountFromProperties(pluginProperties);
+                    final PluginProperty properties = getPluginProperty(pluginProperties, PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
+                    final boolean isInvoiceAdjusted = properties != null ? Boolean.valueOf((String) properties.getValue()) : false;
+
+                    final PluginProperty legacyPayment = getPluginProperty(pluginProperties, PROP_IPCD_PAYMENT_ID);
+                    final UUID paymentId = legacyPayment != null ? (UUID) legacyPayment.getValue() : paymentControlContext.getPaymentId();
+
+                    invoiceApi.recordRefund(paymentId, paymentControlContext.getAmount(), isInvoiceAdjusted, idWithAmountMap, paymentControlContext.getTransactionExternalKey(), internalContext);
+                    break;
+
                 default:
                     throw new IllegalStateException("Unexpected transactionType " + transactionType);
             }
@@ -253,6 +269,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
 
                 nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext);
                 break;
+            case CREDIT:
             case REFUND:
                 // We don't retry REFUND
                 break;
@@ -401,6 +418,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) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
index a6f0f73..cc32c53 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
@@ -123,7 +123,7 @@ public class TestPaymentOperation extends PaymentTestSuiteNoDB {
         final PaymentMethodModelDao paymentMethodModelDao = new PaymentMethodModelDao(paymentStateContext.getPaymentMethodId(), UUID.randomUUID().toString(), clock.getUTCNow(), clock.getUTCNow(),
                                                                                       paymentStateContext.getAccount().getId(), MockPaymentProviderPlugin.PLUGIN_NAME, true);
         final PaymentDao paymentDao = Mockito.mock(PaymentDao.class);
-        Mockito.when(paymentDao.getPaymentMethodIncludedDeleted(paymentStateContext.getPaymentMethodId(), internalCallContext)).thenReturn(paymentMethodModelDao);
+        Mockito.when(paymentDao.getPaymentMethod(paymentStateContext.getPaymentMethodId(), internalCallContext)).thenReturn(paymentMethodModelDao);
 
         final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, clock.getUTCNow(), paymentDao, registry, internalCallContext, eventBus, paymentSMHelper);
         paymentOperation = new PaymentOperationTest(paymentPluginStatus, daoHelper, locker, paymentPluginDispatcher, paymentConfig, paymentStateContext);

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 1bb43b2..0927ec9 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.128</version>
+        <version>0.129-SNAPSHOT</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.17.5-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 4d31f12..8672065 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..80575fd
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExternalRefund.java
@@ -0,0 +1,326 @@
+/*
+ * 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 java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.client.KillBillClientException;
+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.InvoicePayments;
+import org.killbill.billing.client.model.Invoices;
+import org.killbill.billing.client.model.Payment;
+import org.killbill.billing.client.model.PaymentMethod;
+import org.killbill.billing.client.model.PaymentMethodPluginDetail;
+import org.killbill.billing.client.model.Payments;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.payment.api.TransactionType;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+public class TestExternalRefund extends TestJaxrsBase {
+
+    @Test(groups = "slow", description = "#255 - Scenario 0 - Can refund an automatic payment. This is a test to validate the correct behaviour.")
+    public void testAutomaticPaymentAndRefund() 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();
+        final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+        final Payment payment = paymentsForAccount.get(paymentsForAccount.size() - 1);
+
+        Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, true, requestOptions);
+        final List<InvoiceItem> itemsToBeAdjusted = invoices.get(1).getItems();
+
+        // regular 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, requestOptions);
+        assertNotNull(invoicePaymentRefund);
+
+        assertSingleInvoicePaymentRefund(invoicePaymentRefund);
+        assertRefundInvoiceNoAdjustments(accountJson.getAccountId());
+        assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.valueOf(249.95), BigDecimal.ZERO);
+    }
+
+    @Test(groups = "slow", description = "#255 - Scenario 0 - Can refund an automatic payment over item adjustments. This is a test to validate the correct behaviour.")
+    public void testAutomaticPaymentAndRefundWithAdjustments() 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();
+        final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(accountJson.getAccountId(), requestOptions);
+        final Payment payment = paymentsForAccount.get(paymentsForAccount.size() - 1);
+
+        Invoices invoices = killBillClient.getInvoicesForAccount(accountJson.getAccountId(), true, true, requestOptions);
+        final List<InvoiceItem> itemsToBeAdjusted = invoices.get(1).getItems();
+
+        // regular 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, requestOptions);
+        assertNotNull(invoicePaymentRefund);
+
+        assertSingleInvoicePaymentRefund(invoicePaymentRefund);
+        assertRefundInvoiceAdjustments(accountJson.getAccountId());
+        assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.ZERO, BigDecimal.ZERO);
+    }
+
+    @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);
+
+        assertSingleInvoicePaymentRefund(invoicePaymentRefund);
+        assertRefundInvoiceNoAdjustments(accountJson.getAccountId());
+        assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.valueOf(249.95), BigDecimal.ZERO);
+    }
+
+    @Test(groups = "slow", description = "#255 - Scenario 1 - Can refund a manual payment though an external refund over item adjustments")
+    public void testManualPaymentAndExternalRefundWithAdjustments() 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());
+        invoicePaymentTransactionRequest.setIsAdjusted(true);
+        invoicePaymentTransactionRequest.setAdjustments(unpaidInvoice.getItems());
+        final InvoicePayment invoicePaymentRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, requestOptions);
+        assertNotNull(invoicePaymentRefund);
+
+        assertSingleInvoicePaymentRefund(invoicePaymentRefund);
+        assertRefundInvoiceAdjustments(accountJson.getAccountId());
+        assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.ZERO, BigDecimal.ZERO);
+    }
+
+    @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 invoicePaymentExternalRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, true, null, requestOptions);
+        assertNotNull(invoicePaymentExternalRefund);
+
+        assertInvoicePaymentsExternalRefund(accountJson.getAccountId(), invoicePaymentExternalRefund);
+        assertRefundInvoiceNoAdjustments(accountJson.getAccountId());
+        assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.valueOf(249.95), BigDecimal.ZERO);
+
+    }
+
+    @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 invoicePaymentExternalRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, true, null, requestOptions);
+        assertNotNull(invoicePaymentExternalRefund);
+
+        assertInvoicePaymentsExternalRefund(accountJson.getAccountId(), invoicePaymentExternalRefund);
+        assertRefundInvoiceAdjustments(accountJson.getAccountId());
+        assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.ZERO, BigDecimal.ZERO);
+
+    }
+
+    @Test(groups = "slow", description = "#255 - Scenario 2b - Can refund an automatic payment though another existing payment method")
+    public void testAutomaticPaymentAndExternalRefundWithDifferentPM() 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);
+
+        // create another PM
+        final PaymentMethodPluginDetail info = new PaymentMethodPluginDetail();
+        final PaymentMethod paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), accountJson.getAccountId(), false, PLUGIN_NAME, info);
+        final PaymentMethod otherPaymentMethod = killBillClient.createPaymentMethod(paymentMethodJson, 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 invoicePaymentExternalRefund = killBillClient.createInvoicePaymentRefund(invoicePaymentTransactionRequest, true, otherPaymentMethod.getPaymentMethodId(), requestOptions);
+        assertNotNull(invoicePaymentExternalRefund);
+        assertEquals(invoicePaymentExternalRefund.getPaymentMethodId(), otherPaymentMethod.getPaymentMethodId());
+
+        assertInvoicePaymentsExternalRefund(accountJson.getAccountId(), invoicePaymentExternalRefund);
+        assertRefundInvoiceAdjustments(accountJson.getAccountId());
+        assertRefundAccountBalance(accountJson.getAccountId(), BigDecimal.ZERO, BigDecimal.ZERO);
+
+    }
+
+    private void assertRefundInvoiceAdjustments(final UUID accountId) throws KillBillClientException {
+        final Invoices invoices;
+        invoices = killBillClient.getInvoicesForAccount(accountId, true, true, requestOptions);
+        final Invoice invoiceWithRefund = invoices.get(1);
+        assertEquals(invoiceWithRefund.getAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(invoiceWithRefund.getRefundAdj().compareTo(BigDecimal.valueOf(249.95).negate()), 0);
+        assertEquals(invoiceWithRefund.getItems().size(), 2);
+        assertEquals(invoiceWithRefund.getItems().get(0).getItemType(), InvoiceItemType.RECURRING.toString());
+        assertEquals(invoiceWithRefund.getItems().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+        assertEquals(invoiceWithRefund.getItems().get(1).getItemType(), InvoiceItemType.ITEM_ADJ.toString());
+        assertEquals(invoiceWithRefund.getItems().get(1).getAmount().compareTo(BigDecimal.valueOf(249.95).negate()), 0);
+    }
+
+    private void assertRefundInvoiceNoAdjustments(final UUID accountId) throws KillBillClientException {
+        final Invoices invoices = killBillClient.getInvoicesForAccount(accountId, true, true, requestOptions);
+        final Invoice invoiceWithRefund = invoices.get(1);
+        assertEquals(invoiceWithRefund.getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+        assertEquals(invoiceWithRefund.getRefundAdj().compareTo(BigDecimal.valueOf(249.95).negate()), 0);
+        assertEquals(invoiceWithRefund.getItems().size(), 1);
+        assertEquals(invoiceWithRefund.getItems().get(0).getItemType(), InvoiceItemType.RECURRING.toString());
+        assertEquals(invoiceWithRefund.getItems().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+    }
+
+    private void assertRefundAccountBalance(final UUID accountId, final BigDecimal balanceAmount, final BigDecimal cbaAmount) throws KillBillClientException {
+        final Account account = killBillClient.getAccount(accountId, true, true, requestOptions);
+        assertEquals(account.getAccountBalance().compareTo(balanceAmount), 0);
+        assertEquals(account.getAccountCBA().compareTo(cbaAmount), 0);
+    }
+
+    private void assertInvoicePaymentsExternalRefund(final UUID accountId, final InvoicePayment invoicePaymentExternalRefund) throws KillBillClientException {
+        final InvoicePayments invoicePaymentsForAccount = killBillClient.getInvoicePaymentsForAccount(accountId, requestOptions);
+        assertEquals(invoicePaymentsForAccount.size(), 2);
+
+        // INVOICE PAYMENT FOR ORIGINAL PURCHASE
+        final InvoicePayment invoicePaymentPurchase = invoicePaymentsForAccount.get(0);
+        assertEquals(invoicePaymentPurchase.getPurchasedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+        assertEquals(invoicePaymentPurchase.getCreditedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(invoicePaymentPurchase.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE.toString());
+        assertEquals(invoicePaymentPurchase.getTransactions().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+        // INVOICE PAYMENT FOR EXTERNAL REFUND
+        final InvoicePayment creditInvoicePayment = invoicePaymentsForAccount.get(1);
+        assertTrue(creditInvoicePayment.equals(invoicePaymentExternalRefund));
+
+        assertEquals(creditInvoicePayment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(creditInvoicePayment.getCreditedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+        assertEquals(creditInvoicePayment.getTransactions().size(), 1);
+        assertEquals(creditInvoicePayment.getTransactions().get(0).getTransactionType(), TransactionType.CREDIT.toString());
+        assertEquals(creditInvoicePayment.getTransactions().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+    }
+
+    private void assertSingleInvoicePaymentRefund(final InvoicePayment invoicePaymentRefund) {
+        // ONLY ONE INVOICE PAYMENT IS GENERATED FOR BOTH, PURCHASE AND REFUND.
+        assertEquals(invoicePaymentRefund.getPurchasedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+        assertEquals(invoicePaymentRefund.getRefundedAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+
+        assertEquals(invoicePaymentRefund.getTransactions().size(), 2);
+        assertEquals(invoicePaymentRefund.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE.toString());
+        assertEquals(invoicePaymentRefund.getTransactions().get(0).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+        assertEquals(invoicePaymentRefund.getTransactions().get(1).getTransactionType(), TransactionType.REFUND.toString());
+        assertEquals(invoicePaymentRefund.getTransactions().get(1).getAmount().compareTo(BigDecimal.valueOf(249.95)), 0);
+    }
+
+}