killbill-aplcache

Implement chargeback notification API (Payment layer) There

7/1/2014 9:54:03 PM

Details

diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ChargebackResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ChargebackResource.java
index 55615cb..0546cff 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ChargebackResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ChargebackResource.java
@@ -73,6 +73,7 @@ public class ChargebackResource extends JaxRsResourceBase {
         super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
     }
 
+    // STEPH  API needs to be discussed
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
@@ -86,8 +87,8 @@ public class ChargebackResource extends JaxRsResourceBase {
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         final Account account = accountUserApi.getAccountById(UUID.fromString(json.getAccountId()), callContext);
 
-        final DirectPayment payment = paymentApi.notifyChargeback(account, UUID.fromString(json.getChargedBackTransactionId()), json.getChargedBackTransactionId(), json.getAmount(),
-                                                                  Currency.valueOf(json.getCurrency()), callContext);
+        final DirectPayment payment = paymentApi.notifyChargebackWithPaymentControl(account, UUID.fromString(json.getChargedBackTransactionId()), json.getChargedBackTransactionId(), json.getAmount(),
+                                                                  Currency.valueOf(json.getCurrency()), createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
         return uriBuilder.buildResponse(uriInfo, DirectPaymentResource.class, "getDirectPayment", payment.getId());
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultDirectPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultDirectPaymentApi.java
index 4f79e19..8edb561 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultDirectPaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultDirectPaymentApi.java
@@ -37,8 +37,6 @@ import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.Pagination;
 
-import com.google.common.base.Preconditions;
-
 public class DefaultDirectPaymentApi implements DirectPaymentApi {
 
     private static final boolean SHOULD_LOCK_ACCOUNT = true;
@@ -56,18 +54,17 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
-
     @Override
     public DirectPayment createAuthorization(final Account account, final UUID paymentMethodId, @Nullable final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String directPaymentExternalKey, final String directPaymentTransactionExternalKey,
                                              final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
 
-        checkNullParameter(account, "account");
-        checkNullParameter(paymentMethodId, "paymentMethodId");
-        checkNullParameter(amount, "amount");
-        checkNullParameter(currency, "currency");
-        checkNullParameter(directPaymentExternalKey, "paymentExternalKey");
-        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
-        checkNullParameter(properties, "plugin properties");
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentMethodId, "paymentMethodId");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(directPaymentExternalKey, "paymentExternalKey");
+        checkNotNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
         checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
@@ -79,12 +76,11 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     public DirectPayment createCapture(final Account account, final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String directPaymentTransactionExternalKey,
                                        final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
 
-
-        checkNullParameter(account, "account");
-        checkNullParameter(directPaymentId, "paymentId");
-        checkNullParameter(currency, "currency");
-        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
-        checkNullParameter(properties, "plugin properties");
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(directPaymentId, "paymentId");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
         checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
@@ -96,13 +92,13 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     public DirectPayment createPurchase(final Account account, final UUID paymentMethodId, @Nullable final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String directPaymentExternalKey, final String directPaymentTransactionExternalKey,
                                         final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
 
-        checkNullParameter(account, "account");
-        checkNullParameter(paymentMethodId, "paymentMethodId");
-        checkNullParameter(amount, "amount");
-        checkNullParameter(currency, "currency");
-        checkNullParameter(directPaymentExternalKey, "paymentExternalKey");
-        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
-        checkNullParameter(properties, "plugin properties");
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentMethodId, "paymentMethodId");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(directPaymentExternalKey, "paymentExternalKey");
+        checkNotNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
         checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
@@ -114,13 +110,12 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     public DirectPayment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String directPaymentExternalKey, final String directPaymentTransactionExternalKey,
                                                           final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
 
-
-        checkNullParameter(account, "account");
-        checkNullParameter(amount, "amount");
-        checkNullParameter(currency, "currency");
-        checkNullParameter(directPaymentExternalKey, "paymentExternalKey");
-        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
-        checkNullParameter(properties, "plugin properties");
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(directPaymentExternalKey, "paymentExternalKey");
+        checkNotNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
         checkPositiveAmount(amount);
 
         if (paymentMethodId == null && !paymentOptions.isExternalPayment()) {
@@ -142,10 +137,10 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     public DirectPayment createVoid(final Account account, final UUID directPaymentId, final String directPaymentTransactionExternalKey, final Iterable<PluginProperty> properties,
                                     final CallContext callContext) throws PaymentApiException {
 
-        checkNullParameter(account, "account");
-        checkNullParameter(directPaymentId, "paymentId");
-        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
-        checkNullParameter(properties, "plugin properties");
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(directPaymentId, "paymentId");
+        checkNotNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return directPaymentProcessor.createVoid(account, directPaymentId, directPaymentTransactionExternalKey,
@@ -157,12 +152,12 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     public DirectPayment createRefund(final Account account, final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String directPaymentTransactionExternalKey, final Iterable<PluginProperty> properties,
                                       final CallContext callContext) throws PaymentApiException {
 
-        checkNullParameter(account, "account");
-        checkNullParameter(amount, "amount");
-        checkNullParameter(currency, "currency");
-        checkNullParameter(directPaymentId, "paymentId");
-        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
-        checkNullParameter(properties, "plugin properties");
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(directPaymentId, "paymentId");
+        checkNotNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
         checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
@@ -174,17 +169,15 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     public DirectPayment createRefundWithPaymentControl(final Account account, final UUID directPaymentId, @Nullable final BigDecimal amount, final Currency currency, final String directPaymentTransactionExternalKey, final Iterable<PluginProperty> properties,
                                                         final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
 
-
-        checkNullParameter(account, "account");
-        checkNullParameter(currency, "currency");
-        checkNullParameter(directPaymentId, "paymentId");
-        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
-        checkNullParameter(properties, "plugin properties");
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(directPaymentId, "paymentId");
+        checkNotNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
         if (amount != null) {
             checkPositiveAmount(amount);
         }
 
-
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return pluginControlledPaymentProcessor.createRefund(true, account, directPaymentId, amount, currency, directPaymentTransactionExternalKey,
                                                              properties, paymentOptions.getPaymentControlPluginName(), callContext, internalCallContext);
@@ -196,15 +189,14 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
                                       final String directPaymentExternalKey, final String directPaymentTransactionExternalKey,
                                       final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
 
-
-        checkNullParameter(account, "account");
-        checkNullParameter(paymentMethodId, "paymentMethodId");
-        checkNullParameter(amount, "amount");
-        checkNullParameter(currency, "currency");
-        checkNullParameter(directPaymentId, "paymentId");
-        checkNullParameter(directPaymentExternalKey, "paymentExternalKey");
-        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
-        checkNullParameter(properties, "plugin properties");
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentMethodId, "paymentMethodId");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(directPaymentId, "paymentId");
+        checkNotNullParameter(directPaymentExternalKey, "paymentExternalKey");
+        checkNotNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
         checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
@@ -216,29 +208,51 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     @Override
     public void notifyPendingTransactionOfStateChanged(final Account account, final UUID directPaymentTransactionId, final boolean isSuccess, final CallContext callContext) throws PaymentApiException {
 
-        checkNullParameter(account, "account");
-        checkNullParameter(directPaymentTransactionId, "paymentTransactionId");
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(directPaymentTransactionId, "paymentTransactionId");
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         directPaymentProcessor.notifyPendingPaymentOfStateChanged(account, directPaymentTransactionId, isSuccess, callContext, internalCallContext);
     }
 
     @Override
-    public DirectPayment notifyChargeback(final Account account, final UUID directPaymentTransactionId, final String chargebackTransactionExternalKey, final BigDecimal amount, final Currency currency, final CallContext callContext) throws PaymentApiException {
+    public void notifyPendingTransactionOfStateChangedWithPaymentControl(Account account, UUID directPaymentTransactionId, boolean isSuccess, PaymentOptions paymentOptions, CallContext context) throws PaymentApiException {
+
+    }
+
+
+    @Override
+    public DirectPayment notifyChargeback(final Account account, final UUID directPaymentTransactionId, final String chargebackTransactionExternalKey, final BigDecimal amount, final Currency currency,
+                                          final CallContext callContext) throws PaymentApiException {
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(directPaymentTransactionId, "paymentTransactionId");
+        checkNotNullParameter(chargebackTransactionExternalKey, "transactionExternalKey");
+        checkPositiveAmount(amount);
 
-        checkNullParameter(account, "account");
-        checkNullParameter(amount, "amount");
-        checkNullParameter(currency, "currency");
-        checkNullParameter(directPaymentTransactionId, "paymentTransactionId");
-        checkNullParameter(chargebackTransactionExternalKey, "transactionExternalKey");
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return directPaymentProcessor.notifyChargeback(account, null, directPaymentTransactionId, chargebackTransactionExternalKey, amount, currency, true,
+                                                       callContext, internalCallContext);
+    }
+
+    @Override
+    public DirectPayment notifyChargebackWithPaymentControl(Account account, UUID directPaymentTransactionId, String chargebackTransactionExternalKey, BigDecimal amount, Currency currency, final PaymentOptions paymentOptions, CallContext callContext) throws PaymentApiException {
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(directPaymentTransactionId, "paymentTransactionId");
+        checkNotNullParameter(chargebackTransactionExternalKey, "transactionExternalKey");
         checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
-        return directPaymentProcessor.notifyPaymentPaymentOfChargeback(account, directPaymentTransactionId, chargebackTransactionExternalKey, amount, currency, callContext, internalCallContext);
+        return pluginControlledPaymentProcessor.notifyPaymentPaymentOfChargeback(account, directPaymentTransactionId, chargebackTransactionExternalKey, amount, currency,
+                                                                                 paymentOptions.getPaymentControlPluginName(), callContext, internalCallContext);
     }
 
     @Override
-    public List<DirectPayment> getAccountPayments(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext)  throws PaymentApiException {
+    public List<DirectPayment> getAccountPayments(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentApiException {
         return directPaymentProcessor.getAccountPayments(accountId, internalCallContextFactory.createInternalTenantContext(accountId, tenantContext));
     }
 
@@ -278,7 +292,6 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
         return directPaymentProcessor.searchPayments(searchKey, offset, limit, pluginName, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
-
     @Override
     public UUID addPaymentMethod(String paymentMethodExternalKey,
                                  final Account account, final String pluginName,
@@ -358,7 +371,7 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
         return paymentMethods;
     }
 
-    private void checkNullParameter(final Object parameter, final String parameterName) throws PaymentApiException {
+    private void checkNotNullParameter(final Object parameter, final String parameterName) throws PaymentApiException {
         if (parameter == null) {
             throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null");
         }
diff --git a/payment/src/main/java/org/killbill/billing/payment/control/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/control/InvoicePaymentControlPluginApi.java
index 73357a6..8874dcc 100644
--- a/payment/src/main/java/org/killbill/billing/payment/control/InvoicePaymentControlPluginApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/control/InvoicePaymentControlPluginApi.java
@@ -113,13 +113,19 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
 
         final TransactionType transactionType = paymentControlContext.getTransactionType();
         Preconditions.checkArgument(transactionType == TransactionType.PURCHASE ||
-                                    transactionType == TransactionType.REFUND);
+                                    transactionType == TransactionType.REFUND ||
+                                    transactionType == TransactionType.CHARGEBACK);
 
         final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
-        if (transactionType == TransactionType.PURCHASE) {
-            return getPluginPurchaseResult(paymentControlContext, internalContext);
-        } else /* TransactionType.REFUND */ {
-            return getPluginRefundResult(paymentControlContext, internalContext);
+        switch (transactionType) {
+            case PURCHASE:
+                return getPluginPurchaseResult(paymentControlContext, internalContext);
+            case REFUND:
+                return getPluginRefundResult(paymentControlContext, internalContext);
+            case CHARGEBACK:
+                return new DefaultPriorPaymentControlResult(false, paymentControlContext.getAmount());
+            default:
+                throw new IllegalStateException("Unexpected transactionType " + transactionType);
         }
     }
 
@@ -128,43 +134,60 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
 
         final TransactionType transactionType = paymentControlContext.getTransactionType();
         Preconditions.checkArgument(transactionType == TransactionType.PURCHASE ||
-                                    transactionType == TransactionType.REFUND);
+                                    transactionType == TransactionType.REFUND ||
+                                    transactionType == TransactionType.CHARGEBACK);
 
         final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
         try {
-            if (transactionType == TransactionType.PURCHASE) {
-                final UUID invoiceId = getInvoiceId(paymentControlContext);
-                invoiceApi.notifyOfPayment(invoiceId,
-                                           paymentControlContext.getAmount(),
-                                           paymentControlContext.getCurrency(),
-                                           paymentControlContext.getProcessedCurrency(),
-                                           paymentControlContext.getPaymentId(),
-                                           paymentControlContext.getCreatedDate(),
-                                           internalContext);
-            } else /* TransactionType.REFUND */ {
-                final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(paymentControlContext.getPluginProperties());
-                final PluginProperty prop = getPluginProperty(paymentControlContext.getPluginProperties(), PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
-                final boolean isAdjusted = prop != null ? Boolean.valueOf((String) prop.getValue()) : false;
-                invoiceApi.createRefund(paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), isAdjusted , idWithAmount, paymentControlContext.getTransactionExternalKey(), internalContext);
+            switch (transactionType) {
+                case PURCHASE:
+                    final UUID invoiceId = getInvoiceId(paymentControlContext);
+                    invoiceApi.notifyOfPayment(invoiceId,
+                                               paymentControlContext.getAmount(),
+                                               paymentControlContext.getCurrency(),
+                                               paymentControlContext.getProcessedCurrency(),
+                                               paymentControlContext.getPaymentId(),
+                                               paymentControlContext.getCreatedDate(),
+                                               internalContext);
+                    break;
+
+                case REFUND:
+                    final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(paymentControlContext.getPluginProperties());
+                    final PluginProperty prop = getPluginProperty(paymentControlContext.getPluginProperties(), PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
+                    final boolean isAdjusted = prop != null ? Boolean.valueOf((String) prop.getValue()) : false;
+                    invoiceApi.createRefund(paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), isAdjusted, idWithAmount, paymentControlContext.getTransactionExternalKey(), internalContext);
+                    break;
+
+                case CHARGEBACK:
+                    invoiceApi.createChargeback(paymentControlContext.getPaymentId(), paymentControlContext.getProcessedAmount(), paymentControlContext.getProcessedCurrency(), internalContext);
+                    break;
+
+                default:
+                    throw new IllegalStateException("Unexpected transactionType " + transactionType);
             }
-
         } catch (InvoiceApiException e) {
+            // STEPH need to add some state machine logic in the plugin itself to handle those cases
             logger.error("Failed to complete call: ", e);
             //throw new PaymentControlApiException(e);
         }
     }
 
     @Override
-    public FailureCallResult onFailureCall(final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
+    public FailureCallResult onFailureCall(final PaymentControlContext paymentControlContext) throws
+                                                                                              PaymentControlApiException {
 
         final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
-        switch (paymentControlContext.getTransactionType()) {
+        final TransactionType transactionType = paymentControlContext.getTransactionType();
+        switch (transactionType) {
             case PURCHASE:
                 final DateTime nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext);
                 return new DefaultFailureCallResult(nextRetryDate);
-            default:
-                // We don't retry  REFUND
+            case REFUND:
+            case CHARGEBACK:
+                // We don't retry  REFUND, CHARGEBACK
                 return new DefaultFailureCallResult(null);
+            default:
+                throw new IllegalStateException("Unexpected transactionType " + transactionType);
         }
     }
 
@@ -176,16 +199,15 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         controlDao.removeAutoPayOffEntry(account.getId());
     }
 
-    private UUID getInvoiceId(final PaymentControlContext paymentControlContext) throws PaymentControlApiException  {
+    private UUID getInvoiceId(final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
         final PluginProperty invoiceProp = getPluginProperty(paymentControlContext.getPluginProperties(), PROP_IPCD_INVOICE_ID);
         if (invoiceProp == null ||
-            ! (invoiceProp.getValue() instanceof String)) {
+            !(invoiceProp.getValue() instanceof String)) {
             throw new PaymentControlApiException("Need to specify a valid invoiceId in property " + PROP_IPCD_INVOICE_ID);
         }
         return UUID.fromString((String) invoiceProp.getValue());
     }
 
-
     private PriorPaymentControlResult getPluginPurchaseResult(final PaymentControlContext paymentControlPluginContext, final InternalCallContext internalContext) throws PaymentControlApiException {
 
         try {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/DirectPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/DirectPaymentProcessor.java
index 26e7a00..e11a89c 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/DirectPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/DirectPaymentProcessor.java
@@ -28,7 +28,6 @@ import java.util.concurrent.ExecutorService;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
-import org.joda.time.DateTime;
 import org.killbill.automaton.State;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
@@ -145,6 +144,15 @@ public class DirectPaymentProcessor extends ProcessorBase {
         return performOperation(TransactionType.CREDIT, account, paymentMethodId, directPaymentId, amount, currency, directPaymentExternalKey, directPaymentTransactionExternalKey, shouldLockAccountAndDispatch, properties, callContext, internalCallContext);
     }
 
+    public DirectPayment notifyChargeback(final Account account, @Nullable final UUID paymentId, @Nullable final UUID transactionId, final String directPaymentTransactionExternalKey, final BigDecimal amount, final Currency currency, final boolean shouldLockAccountAndDispatch,
+                                          final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        // We need to have something...
+        Preconditions.checkState(paymentId != null || transactionId != null);
+        final UUID nonNullPaymentId = retrieveNonNullPaymentIdFromArguments(paymentId, transactionId, internalCallContext);
+        return performOperation(TransactionType.CHARGEBACK, account, null, nonNullPaymentId, amount, currency, null, directPaymentTransactionExternalKey, shouldLockAccountAndDispatch, ImmutableList.<PluginProperty>of(), callContext, internalCallContext);
+    }
+
     public List<DirectPayment> getAccountPayments(final UUID accountId, final InternalTenantContext tenantContext) throws PaymentApiException {
         final List<PaymentModelDao> paymentsModelDao = paymentDao.getDirectPaymentsForAccount(accountId, tenantContext);
         final List<PaymentTransactionModelDao> transactionsModelDao = paymentDao.getDirectTransactionsForAccount(accountId, tenantContext);
@@ -178,28 +186,6 @@ public class DirectPaymentProcessor extends ProcessorBase {
                                                                  transactionModelDao.getGatewayErrorCode(), transactionModelDao.getGatewayErrorMsg(), internalCallContext);
     }
 
-    public DirectPayment notifyPaymentPaymentOfChargeback(final Account account, final UUID transactionId, final String chargebackTransactionExternalKey, final BigDecimal amount, final Currency currency, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
-
-        validateUniqueTransactionExternalKey(chargebackTransactionExternalKey, internalCallContext);
-
-        final PaymentTransactionModelDao transactionModelDao = paymentDao.getDirectPaymentTransaction(transactionId, internalCallContext);
-        Preconditions.checkState(transactionModelDao != null);
-
-        final DateTime utcNow = clock.getUTCNow();
-        final PaymentTransactionModelDao chargebackTransaction = new PaymentTransactionModelDao(utcNow, utcNow, chargebackTransactionExternalKey, transactionModelDao.getPaymentId(),
-
-                                                                                                TransactionType.CHARGEBACK, utcNow, TransactionStatus.SUCCESS, amount, currency, null, null);
-        final State currentPaymentState = directPaymentAutomatonRunner.fetchNextState("CHARGEBACK_INIT", true);
-
-        // TODO STEPH we could create a DAO operation to do both steps at once
-        paymentDao.updateDirectPaymentWithNewTransaction(transactionModelDao.getPaymentId(), chargebackTransaction, internalCallContext);
-        paymentDao.updateDirectPaymentAndTransactionOnCompletion(transactionModelDao.getPaymentId(), currentPaymentState.getName(), chargebackTransaction.getId(), TransactionStatus.SUCCESS,
-                                                                 chargebackTransaction.getAmount(), chargebackTransaction.getCurrency(),
-                                                                 chargebackTransaction.getGatewayErrorCode(), chargebackTransaction.getGatewayErrorMsg(), internalCallContext);
-
-        return getPayment(transactionModelDao.getPaymentId(), false, ImmutableList.<PluginProperty>of(), callContext, internalCallContext);
-    }
-
     public DirectPayment getPayment(final UUID directPaymentId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
         final PaymentModelDao paymentModelDao = paymentDao.getDirectPayment(directPaymentId, internalTenantContext);
         if (paymentModelDao == null) {
@@ -359,19 +345,6 @@ public class DirectPaymentProcessor extends ProcessorBase {
         }
     }
 
-    private void validateUniqueTransactionExternalKey(final String transactionExternalKey, final InternalTenantContext tenantContext) throws PaymentApiException {
-        final List<PaymentTransactionModelDao> transactions = paymentDao.getDirectPaymentTransactionsByExternalKey(transactionExternalKey, tenantContext);
-        final PaymentTransactionModelDao transactionAlreadyExists = Iterables.tryFind(transactions, new Predicate<PaymentTransactionModelDao>() {
-            @Override
-            public boolean apply(final PaymentTransactionModelDao input) {
-                return input.getTransactionStatus() == TransactionStatus.SUCCESS;
-            }
-        }).orNull();
-        if (transactionAlreadyExists != null) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS, transactionExternalKey);
-        }
-    }
-
     private DirectPayment getDirectPayment(final PaymentModelDao paymentModelDao, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context, final InternalTenantContext tenantContext) throws PaymentApiException {
         final InternalTenantContext tenantContextWithAccountRecordId;
         if (tenantContext.getAccountRecordId() == null) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java
index 8f53346..230dd4a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java
@@ -24,6 +24,7 @@ import java.util.concurrent.ExecutorService;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
+import org.joda.time.DateTime;
 import org.killbill.automaton.State;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
@@ -36,6 +37,7 @@ import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.DirectPayment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.sm.PluginControlledDirectPaymentAutomatonRunner;
 import org.killbill.billing.payment.dao.PaymentModelDao;
@@ -52,6 +54,7 @@ import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 
 import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.inject.name.Named;
@@ -102,7 +105,9 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
         return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
                                                          TransactionType.CAPTURE,
                                                          account,
+                                                         null,
                                                          directPaymentId,
+                                                         null,
                                                          transactionExternalKey,
                                                          amount,
                                                          currency,
@@ -133,8 +138,12 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
         return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
                                                          TransactionType.VOID,
                                                          account,
+                                                         null,
                                                          directPaymentId,
+                                                         null,
                                                          transactionExternalKey,
+                                                         null,
+                                                         null,
                                                          properties,
                                                          null,
                                                          callContext, internalCallContext);
@@ -145,7 +154,9 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
         return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
                                                          TransactionType.REFUND,
                                                          account,
+                                                         null,
                                                          directPaymentId,
+                                                         null,
                                                          transactionExternalKey,
                                                          amount,
                                                          currency,
@@ -158,19 +169,38 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
                                       final String transactionExternalKey, final Iterable<PluginProperty> properties, final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
 
         return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
-                                                         TransactionType.CREDIT,
-                                                         account,
-                                                         paymentMethodId,
-                                                         directPaymentId,
-                                                         paymentExternalKey,
-                                                         transactionExternalKey,
-                                                         amount,
-                                                         currency,
-                                                         properties,
-                                                         paymentControlPluginName,
-                                                         callContext, internalCallContext);
+                                                                TransactionType.CREDIT,
+                                                                account,
+                                                                paymentMethodId,
+                                                                directPaymentId,
+                                                                paymentExternalKey,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
     }
 
+    public DirectPayment notifyPaymentPaymentOfChargeback(final Account account, final UUID transactionId, final String transactionExternalKey, final BigDecimal amount, final Currency currency,
+                                                          final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        // We need to have something...
+        final UUID nonNullPaymentId = retrieveNonNullPaymentIdFromArguments(null, transactionId, internalCallContext);
+        return pluginControlledDirectPaymentAutomatonRunner.run(true,
+                                                                TransactionType.CHARGEBACK,
+                                                                account,
+                                                                null,
+                                                                nonNullPaymentId,
+                                                                null,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                ImmutableList.<PluginProperty>of(),
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
+    }
+
+
     public void retryPaymentTransaction(final UUID attemptId, final String pluginName, final InternalCallContext internalCallContext) {
         try {
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
index eb0f9f3..f0689da 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
@@ -39,8 +39,10 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentMethodModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.api.TagApiException;
@@ -60,7 +62,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
 
 public abstract class ProcessorBase {
 
@@ -167,10 +171,38 @@ public abstract class ProcessorBase {
         }
     }
 
+    protected UUID retrieveNonNullPaymentIdFromArguments(@Nullable final UUID paymentId, @Nullable final UUID transactionId, final InternalCallContext internalCallContext) throws PaymentApiException {
+        final UUID nonNullPaymentId;
+        if (paymentId == null) {
+            final PaymentTransactionModelDao transactionModelDao = paymentDao.getDirectPaymentTransaction(transactionId, internalCallContext);
+            if (transactionModelDao == null) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, transactionId, "need to specify a valid transactionId");
+            }
+            nonNullPaymentId = transactionModelDao.getPaymentId();
+        } else {
+            nonNullPaymentId = paymentId;
+        }
+        return nonNullPaymentId;
+    }
+
     protected TenantContext buildTenantContext(final InternalTenantContext context) {
         return context.toTenantContext(nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT));
     }
 
+    protected void validateUniqueTransactionExternalKey(final String transactionExternalKey, final InternalTenantContext tenantContext) throws PaymentApiException {
+        final List<PaymentTransactionModelDao> transactions = paymentDao.getDirectPaymentTransactionsByExternalKey(transactionExternalKey, tenantContext);
+        final PaymentTransactionModelDao transactionAlreadyExists = Iterables.tryFind(transactions, new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao input) {
+                return input.getTransactionStatus() == TransactionStatus.SUCCESS;
+            }
+        }).orNull();
+        if (transactionAlreadyExists != null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS, transactionExternalKey);
+        }
+    }
+
+
     // TODO Rename - there is no lock!
     public interface WithAccountLockCallback<ReturnType, ExceptionType extends Exception> {
         public ReturnType doOperation() throws ExceptionType;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackCompleted.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackCompleted.java
new file mode 100644
index 0000000..3ef5c93
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackCompleted.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.core.sm;
+
+import org.killbill.billing.payment.api.PaymentApiException;
+
+public class ChargebackCompleted extends DirectPaymentEnteringStateCallback {
+
+    public ChargebackCompleted(final DirectPaymentAutomatonDAOHelper daoHelper, final DirectPaymentStateContext directPaymentStateContext) throws PaymentApiException {
+        super(daoHelper, directPaymentStateContext);
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackInitiated.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackInitiated.java
new file mode 100644
index 0000000..9b2c36a
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackInitiated.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.core.sm;
+
+import org.killbill.billing.payment.api.PaymentApiException;
+
+public class ChargebackInitiated extends DirectPaymentLeavingStateCallback {
+
+    public ChargebackInitiated(final DirectPaymentAutomatonDAOHelper daoHelper, final DirectPaymentStateContext directPaymentStateContext) throws PaymentApiException {
+        super(daoHelper);
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java
new file mode 100644
index 0000000..c3d0235
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ChargebackOperation extends DirectPaymentOperation {
+
+    private final Logger logger = LoggerFactory.getLogger(ChargebackOperation.class);
+
+    public ChargebackOperation(final DirectPaymentAutomatonDAOHelper daoHelper,
+                               final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                               final DirectPaymentStateContext directPaymentStateContext) throws PaymentApiException {
+        super(daoHelper, locker, paymentPluginDispatcher, directPaymentStateContext);
+    }
+
+    @Override
+    protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+        logger.debug("Starting CHARGEBACK for payment {} ({} {})", directPaymentStateContext.getDirectPaymentId(), directPaymentStateContext.getAmount(), directPaymentStateContext.getCurrency());
+        return new DefaultNoOpPaymentInfoPlugin( directPaymentStateContext.getDirectPaymentId(),
+                                                 directPaymentStateContext.getTransactionPaymentId(),
+                                                 TransactionType.CHARGEBACK,
+                                                 directPaymentStateContext.getAmount(),
+                                                 directPaymentStateContext.getCurrency(),
+                                                 null,
+                                                 null,
+                                                 PaymentPluginStatus.PROCESSED,
+                                                 null);
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentAutomatonRunner.java
index 0e4b6a5..6d99e34 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentAutomatonRunner.java
@@ -189,6 +189,13 @@ public class DirectPaymentAutomatonRunner {
                 leavingStateCallback = new CreditInitiated(daoHelper, directPaymentStateContext);
                 enteringStateCallback = new CreditCompleted(daoHelper, directPaymentStateContext);
                 break;
+            case CHARGEBACK:
+                operationStateMachineName = "CHARGEBACK";
+                operationName = "OP_CHARGEBACK";
+                operationCallback = new ChargebackOperation(daoHelper, locker, paymentPluginDispatcher, directPaymentStateContext);
+                leavingStateCallback = new ChargebackInitiated(daoHelper, directPaymentStateContext);
+                enteringStateCallback = new ChargebackCompleted(daoHelper, directPaymentStateContext);
+                break;
             default:
                 throw new IllegalStateException("Unsupported transaction type " + transactionType);
         }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlledDirectPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlledDirectPaymentAutomatonRunner.java
index f3af2a9..c48a3ca 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlledDirectPaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlledDirectPaymentAutomatonRunner.java
@@ -89,29 +89,12 @@ public class PluginControlledDirectPaymentAutomatonRunner extends DirectPaymentA
     public DirectPayment run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
                              @Nullable final UUID directPaymentId, @Nullable final String directPaymentExternalKey, final String directPaymentTransactionExternalKey,
                              @Nullable final BigDecimal amount, @Nullable final Currency currency,
-                             final Iterable<PluginProperty> properties,
-                             @Nullable final String pluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+                             final Iterable<PluginProperty> properties, @Nullable final String pluginName,
+                             final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return run(initialState, isApiPayment, transactionType, account, paymentMethodId, directPaymentId, directPaymentExternalKey, directPaymentTransactionExternalKey,
                    amount, currency, properties, pluginName, callContext, internalCallContext);
     }
 
-    public DirectPayment run(final boolean isApiPayment, final TransactionType transactionType, final Account account,
-                             @Nullable final UUID directPaymentId, final String directPaymentTransactionExternalKey,
-                             @Nullable final BigDecimal amount, @Nullable final Currency currency,
-                             final Iterable<PluginProperty> properties,
-                             @Nullable final String pluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
-        return run(initialState, isApiPayment, transactionType, account, null, directPaymentId, null, directPaymentTransactionExternalKey,
-                   amount, currency, properties, pluginName, callContext, internalCallContext);
-    }
-
-    public DirectPayment run(final boolean isApiPayment, final TransactionType transactionType, final Account account,
-                             @Nullable final UUID directPaymentId, final String directPaymentTransactionExternalKey,
-                             final Iterable<PluginProperty> properties,
-                             @Nullable final String pluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
-        return run(initialState, isApiPayment, transactionType, account, null, directPaymentId, null, directPaymentTransactionExternalKey,
-                   null, null, properties, pluginName, callContext, internalCallContext);
-    }
-
     public DirectPayment run(final State state, final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
                              @Nullable final UUID directPaymentId, @Nullable final String directPaymentExternalKey, final String directPaymentTransactionExternalKey,
                              @Nullable final BigDecimal amount, @Nullable final Currency currency,
@@ -193,6 +176,9 @@ public class PluginControlledDirectPaymentAutomatonRunner extends DirectPaymentA
             case REFUND:
                 callback = new RetryRefundOperationCallback(locker, paymentPluginDispatcher, directPaymentStateContext, directPaymentProcessor, paymentControlPluginRegistry);
                 break;
+            case CHARGEBACK:
+                callback = new RetryChargebackOperationCallback(locker, paymentPluginDispatcher, directPaymentStateContext, directPaymentProcessor, paymentControlPluginRegistry);
+                break;
             default:
                 throw new IllegalStateException("Unsupported transaction type " + transactionType);
         }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryableDirectPaymentStateContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryableDirectPaymentStateContext.java
index 2b70ba6..e085666 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryableDirectPaymentStateContext.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryableDirectPaymentStateContext.java
@@ -43,7 +43,8 @@ public class RetryableDirectPaymentStateContext extends DirectPaymentStateContex
     private String pluginName;
     private DirectPayment result;
 
-    public RetryableDirectPaymentStateContext(@Nullable final String pluginName, final boolean isApiPayment, @Nullable final UUID directPaymentId, final String directPaymentExternalKey, @Nullable final String directPaymentTransactionExternalKey, final TransactionType transactionType,
+    public RetryableDirectPaymentStateContext(@Nullable final String pluginName, final boolean isApiPayment, @Nullable final UUID directPaymentId, final String directPaymentExternalKey,
+                                              @Nullable final String directPaymentTransactionExternalKey, final TransactionType transactionType,
                                               final Account account, @Nullable final UUID paymentMethodId, final BigDecimal amount, final Currency currency,
                                               final Iterable<PluginProperty> properties, final InternalCallContext internalCallContext, final CallContext callContext) {
         super(directPaymentId, directPaymentExternalKey, directPaymentTransactionExternalKey, transactionType, account, paymentMethodId, amount, currency, true, properties, internalCallContext, callContext);
@@ -97,14 +98,5 @@ public class RetryableDirectPaymentStateContext extends DirectPaymentStateContex
             return null;
         }
         return result.getTransactions().get(result.getTransactions().size() -1);
-/*
-STEPH
-        Iterables.filter(result.getTransactions(), new Predicate<DirectPaymentTransaction>() {
-            @Override
-            public boolean apply(final DirectPaymentTransaction input) {
-                return input.getExternalKey().equals(directPaymentTransactionExternalKey);
-            }
-        })
-        */
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryChargebackOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryChargebackOperationCallback.java
new file mode 100644
index 0000000..1b3bd88
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryChargebackOperationCallback.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.DirectPayment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.DirectPaymentProcessor;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class RetryChargebackOperationCallback extends RetryOperationCallback {
+
+    public RetryChargebackOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryableDirectPaymentStateContext directPaymentStateContext, final DirectPaymentProcessor directPaymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
+        super(locker, paymentPluginDispatcher, directPaymentStateContext, directPaymentProcessor, paymentControlPluginRegistry);
+    }
+
+    @Override
+    protected DirectPayment doCallSpecificOperationCallback() throws PaymentApiException {
+        return directPaymentProcessor.notifyChargeback(directPaymentStateContext.account, directPaymentStateContext.getDirectPaymentId(), null, directPaymentStateContext.directPaymentTransactionExternalKey, directPaymentStateContext.getAmount(),
+                                                       directPaymentStateContext.getCurrency(), false,
+                                                       directPaymentStateContext.callContext, directPaymentStateContext.internalCallContext);
+
+    }
+}
diff --git a/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
index 26eee32..990920d 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
+++ b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
@@ -290,6 +290,12 @@
         </linkStateMachine>
         <linkStateMachine>
             <initialStateMachine>CAPTURE</initialStateMachine>
+            <initialState>CAPTURE_SUCCESS</initialState>
+            <finalStateMachine>CHARGEBACK</finalStateMachine>
+            <finalState>CHARGEBACK_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>CAPTURE</initialStateMachine>
             <initialState>CAPTURE_FAILED</initialState>
             <finalStateMachine>CAPTURE</finalStateMachine>
             <finalState>CAPTURE_INIT</finalState>
@@ -318,6 +324,11 @@
             <finalStateMachine>REFUND</finalStateMachine>
             <finalState>REFUND_INIT</finalState>
         </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>PURCHASE</initialStateMachine>
+            <initialState>PURCHASE_SUCCESS</initialState>
+            <finalStateMachine>CHARGEBACK</finalStateMachine>
+            <finalState>CHARGEBACK_INIT</finalState>
+        </linkStateMachine>
     </linkStateMachines>
-
 </stateMachineConfig>