killbill-aplcache

Rework payment search (attempts/payments/transactions..)

6/26/2014 12:13:30 AM

Changes

Details

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 e12f232..864aa3b 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
@@ -380,6 +380,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                                 @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                 @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
         final TenantContext tenantContext = context.createContext(request);
+
         final List<DirectPayment> payments = null; // STEPH paymentApi.getInvoicePayments(UUID.fromString(invoiceId), tenantContext);
         final List<PaymentJson> result = new ArrayList<PaymentJson>(payments.size());
         if (payments.size() == 0) {
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 8aad7cb..949decd 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
@@ -61,8 +61,14 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     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 {
 
-        checkNotNullArgumentList(account, paymentMethodId, amount, currency, directPaymentExternalKey, directPaymentTransactionExternalKey, properties, callContext);
-        Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "Amount should be positive");
+        checkNullParameter(account, "account");
+        checkNullParameter(paymentMethodId, "paymentMethodId");
+        checkNullParameter(amount, "amount");
+        checkNullParameter(currency, "currency");
+        checkNullParameter(directPaymentExternalKey, "paymentExternalKey");
+        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return directPaymentProcessor.createAuthorization(account, paymentMethodId, directPaymentId, amount, currency, directPaymentExternalKey, directPaymentTransactionExternalKey,
@@ -73,8 +79,13 @@ 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 {
 
-        checkNotNullArgumentList(account, directPaymentId,  amount, currency, directPaymentTransactionExternalKey, properties, callContext);
-        Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "Amount should be positive");
+
+        checkNullParameter(account, "account");
+        checkNullParameter(directPaymentId, "paymentId");
+        checkNullParameter(currency, "currency");
+        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return directPaymentProcessor.createCapture(account, directPaymentId, amount, currency, directPaymentTransactionExternalKey,
@@ -85,8 +96,14 @@ 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 {
 
-        checkNotNullArgumentList(account, paymentMethodId, amount, currency, directPaymentExternalKey, directPaymentTransactionExternalKey, properties, callContext);
-        Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "Amount should be positive");
+        checkNullParameter(account, "account");
+        checkNullParameter(paymentMethodId, "paymentMethodId");
+        checkNullParameter(amount, "amount");
+        checkNullParameter(currency, "currency");
+        checkNullParameter(directPaymentExternalKey, "paymentExternalKey");
+        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return directPaymentProcessor.createPurchase(account, paymentMethodId, directPaymentId, amount, currency, directPaymentExternalKey, directPaymentTransactionExternalKey,
@@ -97,9 +114,18 @@ 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 {
 
-        checkNotNullArgumentList(account, amount, currency, directPaymentExternalKey, directPaymentTransactionExternalKey, properties, callContext);
-        Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "Amount should be positive");
-        Preconditions.checkArgument(paymentMethodId != null || paymentOptions.isExternalPayment(), "Payment methodId should be specified for non regular payments");
+
+        checkNullParameter(account, "account");
+        checkNullParameter(amount, "amount");
+        checkNullParameter(currency, "currency");
+        checkNullParameter(directPaymentExternalKey, "paymentExternalKey");
+        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
+
+        if (paymentMethodId == null && paymentOptions.isExternalPayment()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "paymentMethodId", "should not be null");
+        }
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
 
@@ -116,7 +142,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 {
 
-        checkNotNullArgumentList(account, directPaymentId, directPaymentTransactionExternalKey, properties, callContext);
+        checkNullParameter(account, "account");
+        checkNullParameter(directPaymentId, "paymentId");
+        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNullParameter(properties, "plugin properties");
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return directPaymentProcessor.createVoid(account, directPaymentId, directPaymentTransactionExternalKey,
@@ -128,8 +157,13 @@ 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 {
 
-        checkNotNullArgumentList(account, directPaymentId, directPaymentTransactionExternalKey, amount, currency, properties, callContext);
-        Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "Amount should be positive");
+        checkNullParameter(account, "account");
+        checkNullParameter(amount, "amount");
+        checkNullParameter(currency, "currency");
+        checkNullParameter(directPaymentId, "paymentId");
+        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return directPaymentProcessor.createRefund(account, directPaymentId, amount, currency, directPaymentTransactionExternalKey,
@@ -137,11 +171,19 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     }
 
     @Override
-    public DirectPayment createRefundWithPaymentControl(final Account account, final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String directPaymentTransactionExternalKey, final Iterable<PluginProperty> properties,
+    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 {
 
-        checkNotNullArgumentList(account, directPaymentId, directPaymentTransactionExternalKey, amount, currency, properties, callContext);
-        Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "Amount should be positive");
+
+        checkNullParameter(account, "account");
+        checkNullParameter(currency, "currency");
+        checkNullParameter(directPaymentId, "paymentId");
+        checkNullParameter(directPaymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNullParameter(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,
@@ -154,8 +196,16 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
                                       final String directPaymentExternalKey, final String directPaymentTransactionExternalKey,
                                       final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
 
-        checkNotNullArgumentList(account, paymentMethodId, directPaymentId, directPaymentExternalKey, directPaymentTransactionExternalKey, amount, currency, properties, callContext);
-        Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "Amount should be positive");
+
+        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");
+        checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         return directPaymentProcessor.createCredit(account, paymentMethodId, directPaymentId, amount, currency, directPaymentExternalKey, directPaymentTransactionExternalKey,
@@ -166,7 +216,8 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     @Override
     public void notifyPendingTransactionOfStateChanged(final Account account, final UUID directPaymentTransactionId, final boolean isSuccess, final CallContext callContext) throws PaymentApiException {
 
-        checkNotNullArgumentList(account, directPaymentTransactionId, callContext);
+        checkNullParameter(account, "account");
+        checkNullParameter(directPaymentTransactionId, "paymentTransactionId");
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         directPaymentProcessor.notifyPendingPaymentOfStateChanged(account, directPaymentTransactionId, isSuccess, callContext, internalCallContext);
@@ -175,8 +226,12 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
     @Override
     public void notifyChargeback(final Account account, final UUID directPaymentTransactionId, final String chargebackTransactionExternalKey, final BigDecimal amount, final Currency currency, final CallContext callContext) throws PaymentApiException {
 
-        checkNotNullArgumentList(account, directPaymentTransactionId, chargebackTransactionExternalKey, amount, currency, callContext);
-        Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "Amount should be positive");
+        checkNullParameter(account, "account");
+        checkNullParameter(amount, "amount");
+        checkNullParameter(currency, "currency");
+        checkNullParameter(directPaymentTransactionId, "paymentTransactionId");
+        checkNullParameter(chargebackTransactionExternalKey, "transactionExternalKey");
+        checkPositiveAmount(amount);
 
         final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
         directPaymentProcessor.notifyPaymentPaymentOfChargeback(account, directPaymentTransactionId, chargebackTransactionExternalKey, amount, currency, callContext, internalCallContext);
@@ -297,9 +352,15 @@ public class DefaultDirectPaymentApi implements DirectPaymentApi {
         return paymentMethods;
     }
 
-    private void checkNotNullArgumentList(Object... nonNullArguments) {
-        for (Object cur : nonNullArguments) {
-            Preconditions.checkNotNull(cur);
+    private void checkNullParameter(final Object parameter, final String parameterName) throws PaymentApiException {
+        if (parameter == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null");
+        }
+    }
+
+    private void checkPositiveAmount(final BigDecimal amount) throws PaymentApiException {
+        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "amount", "should be greater than 0");
         }
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java b/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java
index 63db20b..2fd7dd6 100644
--- a/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java
@@ -53,12 +53,12 @@ public class InvoicePaymentControlDao {
         dbi.withHandle(new HandleCallback<Void>() {
             @Override
             public Void withHandle(final Handle handle) throws Exception {
-                final String paymentId = Objects.firstNonNull(data.getPaymentId(), "").toString();
-                final String paymentMethodId = Objects.firstNonNull(data.getPaymentMethodId(), "").toString();
+                final String paymentId = data.getPaymentId() != null ? data.getPaymentId().toString() : null;
+                final String paymentMethodId = data.getPaymentMethodId() != null ? data.getPaymentMethodId().toString() : null;
                 handle.execute("insert into _invoice_payment_control_plugin_auto_pay_off " +
-                               "(payment_external_key, transaction_external_key, account_id, plugin_name, payment_id, payment_method_id, amount, currency, created_by, created_date) values " +
-                               "(?,?,?,?,?,?,?,?,?,?)",
-                               data.getPaymentExternalKey(), data.getTransactionExternalKey(), data.getAccountId(), data.getPluginName(), paymentId, paymentMethodId,
+                               "(attempt_id, payment_external_key, transaction_external_key, account_id, plugin_name, payment_id, payment_method_id, amount, currency, created_by, created_date) values " +
+                               "(?,?,?,?,?,?,?,?,?,?,?)",
+                               data.getAttemptId().toString(), data.getPaymentExternalKey(), data.getTransactionExternalKey(), data.getAccountId(), data.getPluginName(), paymentId, paymentMethodId,
                                data.getAmount(), data.getCurrency(), data.getCreatedBy(), data.getCreatedDate()
                               );
                 return null;
@@ -75,11 +75,12 @@ public class InvoicePaymentControlDao {
                 for (final Map<String, Object> row : queryResult) {
 
                     final PluginAutoPayOffModelDao entry = new PluginAutoPayOffModelDao(Long.valueOf(row.get("record_id").toString()),
+                                                                                        UUID.fromString((String) row.get("attempt_id")),
                                                                                         (String) row.get("payment_external_key"),
                                                                                         (String) row.get("transaction_external_key"),
                                                                                         UUID.fromString((String) row.get("account_id")),
                                                                                         (String) row.get("plugin_name"),
-                                                                                        row.get("payment_id") != null ? UUID.fromString((String) row.get("payment_method_id")) : null,
+                                                                                        row.get("payment_id") != null ? UUID.fromString((String) row.get("payment_id")) : null,
                                                                                         UUID.fromString((String) row.get("payment_method_id")),
                                                                                         (BigDecimal) row.get("amount"),
                                                                                         Currency.valueOf((String) row.get("currency")),
@@ -93,6 +94,7 @@ public class InvoicePaymentControlDao {
         });
     }
 
+    // STEPH soft delete?
     public void removeAutoPayOffEntry(final UUID accountId) {
         dbi.withHandle(new HandleCallback<Void>() {
             @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/control/dao/PluginAutoPayOffModelDao.java b/payment/src/main/java/org/killbill/billing/payment/control/dao/PluginAutoPayOffModelDao.java
index fb92161..f82b295 100644
--- a/payment/src/main/java/org/killbill/billing/payment/control/dao/PluginAutoPayOffModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/control/dao/PluginAutoPayOffModelDao.java
@@ -25,6 +25,7 @@ import org.killbill.billing.catalog.api.Currency;
 public class PluginAutoPayOffModelDao {
 
     private Long recordId;
+    private UUID attemptId;
     private String paymentExternalKey;
     private String transactionExternalKey;
     private UUID accountId;
@@ -39,14 +40,15 @@ public class PluginAutoPayOffModelDao {
     public PluginAutoPayOffModelDao() { /* For the DAO mapper */
     }
 
-    public PluginAutoPayOffModelDao(final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName,
+    public PluginAutoPayOffModelDao(final UUID attemptId, final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName,
                                     final UUID paymentId, final UUID paymentMethodId, final BigDecimal amount, final Currency currency, final String createdBy, final DateTime createdDate) {
-        this(-1L, paymentExternalKey, transactionExternalKey, accountId, pluginName, paymentId, paymentMethodId, amount, currency, createdBy, createdDate);
+        this(-1L, attemptId, paymentExternalKey, transactionExternalKey, accountId, pluginName, paymentId, paymentMethodId, amount, currency, createdBy, createdDate);
     }
 
-    public PluginAutoPayOffModelDao(final Long recordId, final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName,
+    public PluginAutoPayOffModelDao(final Long recordId, UUID attemptId, final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName,
                                     final UUID paymentId, final UUID paymentMethodId, final BigDecimal amount, final Currency currency, final String createdBy, final DateTime createdDate) {
         this.recordId = recordId;
+        this.attemptId = attemptId;
         this.paymentExternalKey = paymentExternalKey;
         this.transactionExternalKey = transactionExternalKey;
         this.accountId = accountId;
@@ -59,6 +61,14 @@ public class PluginAutoPayOffModelDao {
         this.createdDate = createdDate;
     }
 
+    public UUID getAttemptId() {
+        return attemptId;
+    }
+
+    public void setAttemptId(final UUID attemptId) {
+        this.attemptId = attemptId;
+    }
+
     public Long getRecordId() {
         return recordId;
     }
@@ -158,6 +168,9 @@ public class PluginAutoPayOffModelDao {
 
         final PluginAutoPayOffModelDao that = (PluginAutoPayOffModelDao) o;
 
+        if (attemptId != null ? !attemptId.equals(that.attemptId) : that.attemptId != null) {
+            return false;
+        }
         if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
         }
@@ -197,6 +210,7 @@ public class PluginAutoPayOffModelDao {
     @Override
     public int hashCode() {
         int result = recordId != null ? recordId.hashCode() : 0;
+        result = 31 * result + (attemptId != null ? attemptId.hashCode() : 0);
         result = 31 * result + (paymentExternalKey != null ? paymentExternalKey.hashCode() : 0);
         result = 31 * result + (transactionExternalKey != null ? transactionExternalKey.hashCode() : 0);
         result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
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 afffaa0..253c810 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
@@ -170,7 +170,7 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
     public void process_AUTO_PAY_OFF_removal(final Account account, final InternalCallContext internalCallContext) {
         final List<PluginAutoPayOffModelDao> entries = controlDao.getAutoPayOffEntry(account.getId());
         for (PluginAutoPayOffModelDao cur : entries) {
-            retryServiceScheduler.scheduleRetry(ObjectType.ACCOUNT, account.getId(), cur.getTransactionExternalKey(), PLUGIN_NAME, clock.getUTCNow());
+            retryServiceScheduler.scheduleRetry(ObjectType.ACCOUNT, account.getId(), cur.getAttemptId(), PLUGIN_NAME, clock.getUTCNow());
         }
         controlDao.removeAutoPayOffEntry(account.getId());
     }
@@ -423,8 +423,10 @@ public final class InvoicePaymentControlPluginApi implements PaymentControlPlugi
         if (paymentControlContext.isApiPayment() || !isAccountAutoPayOff(paymentControlContext.getAccountId(), paymentControlContext)) {
             return false;
         }
-        final PluginAutoPayOffModelDao data = new PluginAutoPayOffModelDao(paymentControlContext.getPaymentExternalKey(), paymentControlContext.getTransactionExternalKey(), paymentControlContext.getAccountId(), PLUGIN_NAME,
-                                                                           paymentControlContext.getPaymentId(), paymentControlContext.getPaymentMethodId(), computedAmount, paymentControlContext.getCurrency(), CREATED_BY, clock.getUTCNow());
+        final PluginAutoPayOffModelDao data = new PluginAutoPayOffModelDao(paymentControlContext.getAttemptPaymentId(), paymentControlContext.getPaymentExternalKey(), paymentControlContext.getTransactionExternalKey(),
+                                                                           paymentControlContext.getAccountId(), PLUGIN_NAME,
+                                                                           paymentControlContext.getPaymentId(), paymentControlContext.getPaymentMethodId(),
+                                                                           computedAmount, paymentControlContext.getCurrency(), CREATED_BY, clock.getUTCNow());
         controlDao.insertAutoPayOff(data);
         return true;
     }
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 cc82454..1043979 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
@@ -112,6 +112,9 @@ public class DirectPaymentProcessor extends ProcessorBase {
     public DirectPayment createAuthorization(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID directPaymentId, final BigDecimal amount, final Currency currency,
                                              final String directPaymentExternalKey, final String directPaymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
                                              final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        validateUniqueTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+
         final UUID nonNullDirectPaymentId = directPaymentAutomatonRunner.run(TransactionType.AUTHORIZE,
                                                                              account,
                                                                              paymentMethodId,
@@ -133,6 +136,9 @@ public class DirectPaymentProcessor extends ProcessorBase {
     public DirectPayment createCapture(final Account account, final UUID directPaymentId, final BigDecimal amount, final Currency currency,
                                        final String directPaymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
                                        final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        validateUniqueTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+
         final UUID nonNullDirectPaymentId = directPaymentAutomatonRunner.run(TransactionType.CAPTURE,
                                                                              account,
                                                                              directPaymentId,
@@ -152,6 +158,9 @@ public class DirectPaymentProcessor extends ProcessorBase {
     public DirectPayment createPurchase(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID directPaymentId, final BigDecimal amount, final Currency currency,
                                         final String directPaymentExternalKey, final String directPaymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
                                         final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        validateUniqueTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+
         final UUID nonNullDirectPaymentId = directPaymentAutomatonRunner.run(TransactionType.PURCHASE,
                                                                              account,
                                                                              paymentMethodId,
@@ -172,6 +181,9 @@ public class DirectPaymentProcessor extends ProcessorBase {
 
     public DirectPayment createVoid(final Account account, final UUID directPaymentId, final String directPaymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
                                     final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        validateUniqueTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+
         final UUID nonNullDirectPaymentId = directPaymentAutomatonRunner.run(TransactionType.VOID,
                                                                              account,
                                                                              directPaymentId,
@@ -188,6 +200,9 @@ public class DirectPaymentProcessor extends ProcessorBase {
     public DirectPayment createRefund(final Account account, final UUID directPaymentId, final BigDecimal amount, final Currency currency,
                                       final String directPaymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
                                       final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        validateUniqueTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+
         final UUID nonNullDirectPaymentId = directPaymentAutomatonRunner.run(TransactionType.REFUND,
                                                                              account,
                                                                              directPaymentId,
@@ -206,6 +221,9 @@ public class DirectPaymentProcessor extends ProcessorBase {
     public DirectPayment createCredit(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID directPaymentId, final BigDecimal amount, final Currency currency,
                                       final String directPaymentExternalKey, final String directPaymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
                                       final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        validateUniqueTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+
         final UUID nonNullDirectPaymentId = directPaymentAutomatonRunner.run(TransactionType.CREDIT,
                                                                              account,
                                                                              paymentMethodId,
@@ -257,6 +275,9 @@ public class DirectPaymentProcessor extends ProcessorBase {
     }
 
     public void 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);
 
@@ -402,6 +423,19 @@ public class DirectPaymentProcessor extends ProcessorBase {
         return toDirectPayment(paymentModelDao, transactionsForAccount, pluginTransactions);
     }
 
+    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 55ea1b8..8f53346 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
@@ -171,18 +171,14 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
                                                          callContext, internalCallContext);
     }
 
-    public void retryPaymentTransaction(final String transactionExternalKey, final String pluginName, final InternalCallContext internalCallContext) {
+    public void retryPaymentTransaction(final UUID attemptId, final String pluginName, final InternalCallContext internalCallContext) {
         try {
 
-            final PaymentAttemptModelDao attempt = paymentDao.getPaymentAttemptByExternalKey(transactionExternalKey, internalCallContext);
-            final PaymentTransactionModelDao transaction = paymentDao.getDirectPaymentTransactionByExternalKey(transactionExternalKey, internalCallContext);
-            final PaymentModelDao payment = transaction != null ?
-                                            paymentDao.getDirectPayment(transaction.getPaymentId(), internalCallContext) :
-                                            null;
+            final PaymentAttemptModelDao attempt = paymentDao.getPaymentAttempt(attemptId, internalCallContext);
+            final PaymentModelDao payment = paymentDao.getDirectPaymentByExternalKey(attempt.getPaymentExternalKey(), internalCallContext);
             final UUID paymentId = payment != null ? payment.getId() : null;
 
-            final List<PluginPropertyModelDao> properties = paymentDao.getProperties(transactionExternalKey, internalCallContext);
-
+            final List<PluginPropertyModelDao> properties = paymentDao.getProperties(attempt.getId(), internalCallContext);
             final List<PluginProperty> pluginProperties = properties == null ?
                                                           ImmutableList.<PluginProperty>of() :
                                                           ImmutableList.<PluginProperty>copyOf(Iterables.transform(properties, new Function<PluginPropertyModelDao, PluginProperty>() {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentOperation.java
index a9a8db3..97c0b59 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentOperation.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentOperation.java
@@ -62,7 +62,7 @@ public abstract class DirectPaymentOperation extends OperationCallbackBase imple
     @Override
     protected OperationException rewrapExecutionException(final DirectPaymentStateContext directPaymentStateContext, final ExecutionException e) {
         final Throwable realException = Objects.firstNonNull(e.getCause(), e);
-        if (e.getCause() instanceof PaymentPluginApiException) {
+        if (e.getCause() instanceof PaymentApiException) {
             logger.warn("Unsuccessful plugin call for account {}", directPaymentStateContext.getAccount().getExternalKey(), realException);
             return new OperationException(realException, OperationResult.FAILURE);
         } else if (e.getCause() instanceof LockFailedException) {
@@ -70,6 +70,8 @@ public abstract class DirectPaymentOperation extends OperationCallbackBase imple
             logger.error(String.format(format), e);
             return new OperationException(realException, OperationResult.FAILURE);
         } else /* if (e instanceof RuntimeException) */ {
+
+     // STEPH: should we ever return an OperationResult.EXCEPTION at this layer -- since there is transtion back to init and there cannot be retried?
             logger.warn("Plugin call threw an exception for account {}", directPaymentStateContext.getAccount().getExternalKey(), e);
             return new OperationException(e, OperationResult.EXCEPTION);
         }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentStateContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentStateContext.java
index 3c303e7..d8019dc 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentStateContext.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/DirectPaymentStateContext.java
@@ -108,11 +108,7 @@ public class DirectPaymentStateContext {
     }
 
     public UUID getTransactionPaymentId() {
-        return transactionPaymentId;
-    }
-
-    public void setTransactionPaymentId(final UUID transactionPaymentId) {
-        this.transactionPaymentId = transactionPaymentId;
+        return transactionPaymentId != null ? transactionPaymentId : (directPaymentTransactionModelDao != null ? directPaymentTransactionModelDao.getId() : null);
     }
 
     public String getDirectPaymentExternalKey() {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java
index fba08cf..c9a5c31 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java
@@ -50,7 +50,7 @@ public class RetryEnteringStateCallback implements EnteringStateCallback {
         retryableDirectPaymentAutomatonRunner.paymentDao.updatePaymentAttempt(attempt.getId(), transactionId, state.getName(), directPaymentStateContext.internalCallContext);
 
         if ("RETRIED".equals(state.getName())) {
-            retryServiceScheduler.scheduleRetry(ObjectType.PAYMENT_ATTEMPT, attempt.getId(), directPaymentStateContext.directPaymentTransactionExternalKey,
+            retryServiceScheduler.scheduleRetry(ObjectType.PAYMENT_ATTEMPT, attempt.getId(), attempt.getId(),
                                                 directPaymentStateContext.getPluginName(), directPaymentStateContext.getRetryDate());
         }
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
index 8076b73..8445482 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
@@ -68,22 +68,23 @@ public class RetryLeavingStateCallback implements LeavingStateCallback {
 
         if (state.getName().equals(initialState.getName()) ||
             state.getName().equals(retriedState.getName())) {
+
+            final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(stateContext.getAccount().getId(), stateContext.getPaymentMethodId(),
+                                                                              utcNow, utcNow, stateContext.getDirectPaymentExternalKey(), null,
+                                                                              stateContext.directPaymentTransactionExternalKey, transactionType, state.getName(),
+                                                                              stateContext.getAmount(), stateContext.getCurrency(),
+                                                                              stateContext.getPluginName());
+
             final List<PluginPropertyModelDao> properties = ImmutableList.copyOf(Iterables.transform(stateContext.getProperties(), new Function<PluginProperty, PluginPropertyModelDao>() {
                 @Override
                 public PluginPropertyModelDao apply(final PluginProperty input) {
                     // STEPH how to serialize more complex values such as item adjustments. json ?
                     final String value = (input.getValue() instanceof String) ? (String) input.getValue() : "TODO: could not serialize";
-                    return new PluginPropertyModelDao(stateContext.getDirectPaymentExternalKey(), stateContext.directPaymentTransactionExternalKey, stateContext.getAccount().getId(),
+                    return new PluginPropertyModelDao(attempt.getId(), stateContext.getDirectPaymentExternalKey(), stateContext.directPaymentTransactionExternalKey, stateContext.getAccount().getId(),
                                                       stateContext.getPluginName(), input.getKey(), value, stateContext.getCallContext().getUserName(), stateContext.getCallContext().getCreatedDate());
                 }
             }));
-            final PaymentAttemptModelDao attempt = retryableDirectPaymentAutomatonRunner.paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(stateContext.getAccount().getId(), stateContext.getPaymentMethodId(),
-                                                                                                        utcNow, utcNow, stateContext.getDirectPaymentExternalKey(), null,
-                                                                                                                           stateContext.directPaymentTransactionExternalKey, transactionType, state.getName(),
-                                                                                                                            stateContext.getAmount(), stateContext.getCurrency(),
-                                                                                                                           stateContext.getPluginName()),
-                                                                                                properties, stateContext.internalCallContext);
-
+            retryableDirectPaymentAutomatonRunner.paymentDao.insertPaymentAttemptWithProperties(attempt, properties, stateContext.internalCallContext);
             stateContext.setAttemptId(attempt.getId());
         }
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
index 95d1b55..5f44768 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
@@ -77,15 +77,16 @@ public abstract class RetryOperationCallback extends OperationCallbackBase imple
             public OperationResult doOperation() throws OperationException {
 
                 final RetryableDirectPaymentStateContext retryableDirectPaymentStateContext = (RetryableDirectPaymentStateContext) directPaymentStateContext;
-                final PaymentControlContext paymentControlContext = new DefaultPaymentControlContext(directPaymentStateContext.account,
-                                                                                                     directPaymentStateContext.paymentMethodId,
-                                                                                                     directPaymentStateContext.directPaymentId,
-                                                                                                     directPaymentStateContext.directPaymentExternalKey,
-                                                                                                     directPaymentStateContext.directPaymentTransactionExternalKey,
-                                                                                                     directPaymentStateContext.transactionType,
-                                                                                                     directPaymentStateContext.amount,
-                                                                                                     directPaymentStateContext.currency,
-                                                                                                     directPaymentStateContext.properties,
+                final PaymentControlContext paymentControlContext = new DefaultPaymentControlContext(directPaymentStateContext.getAccount(),
+                                                                                                     directPaymentStateContext.getPaymentMethodId(),
+                                                                                                     retryableDirectPaymentStateContext.getAttemptId(),
+                                                                                                     directPaymentStateContext.getDirectPaymentId(),
+                                                                                                     directPaymentStateContext.getDirectPaymentExternalKey(),
+                                                                                                     directPaymentStateContext.getDirectPaymentTransactionExternalKey(),
+                                                                                                     directPaymentStateContext.getTransactionType(),
+                                                                                                     directPaymentStateContext.getAmount(),
+                                                                                                     directPaymentStateContext.getCurrency(),
+                                                                                                     directPaymentStateContext.getProperties(),
                                                                                                      retryableDirectPaymentStateContext.isApiPayment(),
                                                                                                      directPaymentStateContext.callContext);
 
@@ -117,11 +118,12 @@ public abstract class RetryOperationCallback extends OperationCallbackBase imple
                     if (success) {
                         final PaymentControlContext updatedPaymentControlContext = new DefaultPaymentControlContext(directPaymentStateContext.account,
                                                                                                                     directPaymentStateContext.paymentMethodId,
+                                                                                                                    retryableDirectPaymentStateContext.getAttemptId(),
                                                                                                                     result.getId(),
                                                                                                                     result.getExternalKey(),
                                                                                                                     transaction.getId(),
-                                                                                                                    directPaymentStateContext.directPaymentTransactionExternalKey,
-                                                                                                                    directPaymentStateContext.transactionType,
+                                                                                                                    directPaymentStateContext.getDirectPaymentTransactionExternalKey(),
+                                                                                                                    directPaymentStateContext.getTransactionType(),
                                                                                                                     transaction.getAmount(),
                                                                                                                     transaction.getCurrency(),
                                                                                                                     transaction.getProcessedAmount(),
@@ -224,8 +226,9 @@ public abstract class RetryOperationCallback extends OperationCallbackBase imple
     public class DefaultPaymentControlContext extends DefaultCallContext implements PaymentControlContext {
 
         private final Account account;
-        private final UUID paymentId;
         private final UUID paymentMethodId;
+        private final UUID attemptId;
+        private final UUID paymentId;
         private final String paymentExternalKey;
         private final UUID transactionId;
         private final String transactionExternalKey;
@@ -237,17 +240,18 @@ public abstract class RetryOperationCallback extends OperationCallbackBase imple
         private final boolean isApiPayment;
         private final Iterable<PluginProperty> properties;
 
-        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final String paymentExternalKey, final String transactionExternalKey, final TransactionType transactionType, final BigDecimal amount, final Currency currency,
+        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, final String transactionExternalKey, final TransactionType transactionType, final BigDecimal amount, final Currency currency,
                                             final Iterable<PluginProperty> properties, final boolean isApiPayment, final CallContext callContext) {
-            this(account, paymentMethodId, paymentId, paymentExternalKey, null, transactionExternalKey, transactionType, amount, currency, null, null, properties, isApiPayment, callContext);
+            this(account, paymentMethodId, attemptId, paymentId, paymentExternalKey, null, transactionExternalKey, transactionType, amount, currency, null, null, properties, isApiPayment, callContext);
         }
 
-        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final String paymentExternalKey, @Nullable final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType,
+        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, @Nullable final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType,
                                             final BigDecimal amount, final Currency currency, @Nullable final BigDecimal processedAmount, @Nullable final Currency processedCurrency, final Iterable<PluginProperty> properties, final boolean isApiPayment, final CallContext callContext) {
             super(callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(), callContext.getComments(), callContext.getUserToken(), callContext.getCreatedDate(), callContext.getUpdatedDate());
             this.account = account;
-            this.paymentId = paymentId;
             this.paymentMethodId = paymentMethodId;
+            this.attemptId = attemptId;
+            this.paymentId = paymentId;
             this.paymentExternalKey = paymentExternalKey;
             this.transactionId = transactionId;
             this.transactionExternalKey = transactionExternalKey;
@@ -301,6 +305,11 @@ public abstract class RetryOperationCallback extends OperationCallbackBase imple
         }
 
         @Override
+        public UUID getAttemptPaymentId() {
+            return attemptId;
+        }
+
+        @Override
         public BigDecimal getProcessedAmount() {
             return processedAmount;
         }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryPurchaseOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryPurchaseOperationCallback.java
index 6127cef..7876e9e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryPurchaseOperationCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryPurchaseOperationCallback.java
@@ -33,6 +33,8 @@ public class RetryPurchaseOperationCallback extends RetryOperationCallback {
 
     @Override
     protected DirectPayment doCallSpecificOperationCallback() throws PaymentApiException {
-        return directPaymentProcessor.createPurchase(directPaymentStateContext.account, directPaymentStateContext.paymentMethodId, directPaymentStateContext.directPaymentId, directPaymentStateContext.getAmount(), directPaymentStateContext.getCurrency(), directPaymentStateContext.directPaymentExternalKey, directPaymentStateContext.directPaymentTransactionExternalKey, false, directPaymentStateContext.getProperties(), directPaymentStateContext.callContext, directPaymentStateContext.internalCallContext);
+        return directPaymentProcessor.createPurchase(directPaymentStateContext.account, directPaymentStateContext.paymentMethodId, directPaymentStateContext.directPaymentId, directPaymentStateContext.getAmount(),
+                                                     directPaymentStateContext.getCurrency(), directPaymentStateContext.directPaymentExternalKey, directPaymentStateContext.directPaymentTransactionExternalKey, false,
+                                                     directPaymentStateContext.getProperties(), directPaymentStateContext.callContext, directPaymentStateContext.internalCallContext);
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
index 68d5721..04caa3c 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
@@ -72,12 +72,12 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    public List<PluginPropertyModelDao> getProperties(final String transactionExternalKey, final InternalCallContext context) {
+    public List<PluginPropertyModelDao> getProperties(final UUID attemptId, final InternalCallContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PluginPropertyModelDao>>() {
             @Override
             public List<PluginPropertyModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
                 final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
-                return transactional.become(PluginPropertySqlDao.class).getPluginProperties(transactionExternalKey);
+                return transactional.become(PluginPropertySqlDao.class).getPluginProperties(attemptId.toString());
             }
         });
     }
@@ -127,11 +127,11 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    public PaymentAttemptModelDao getPaymentAttemptByExternalKey(final String externalKey, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentAttemptModelDao>() {
+    public List<PaymentAttemptModelDao> getPaymentAttemptByTransactionExternalKey(final String externalKey, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentAttemptModelDao>>() {
 
             @Override
-            public PaymentAttemptModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+            public List<PaymentAttemptModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
                 final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
                 return transactional.getByTransactionExternalKey(externalKey, context);
             }
@@ -139,11 +139,11 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    public PaymentTransactionModelDao getDirectPaymentTransactionByExternalKey(final String transactionExternalKey, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentTransactionModelDao>() {
+    public List<PaymentTransactionModelDao> getDirectPaymentTransactionsByExternalKey(final String transactionExternalKey, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentTransactionModelDao>>() {
             @Override
-            public PaymentTransactionModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).getPaymentTransactionByExternalKey(transactionExternalKey, context);
+            public List<PaymentTransactionModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).getPaymentTransactionsByExternalKey(transactionExternalKey, context);
             }
         });
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java
index 1e58d1b..d66a717 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java
@@ -41,7 +41,7 @@ public interface PaymentAttemptSqlDao extends EntitySqlDao<PaymentAttemptModelDa
                        @BindBean final InternalCallContext context);
 
     @SqlQuery
-    PaymentAttemptModelDao getByTransactionExternalKey(@Bind("transactionExternalKey") final String transactionExternalKey,
+    List<PaymentAttemptModelDao> getByTransactionExternalKey(@Bind("transactionExternalKey") final String transactionExternalKey,
                                                        @BindBean final InternalTenantContext context);
 
     @SqlQuery
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
index 8cbbd2e..6327b14 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
@@ -29,7 +29,7 @@ import org.killbill.billing.util.entity.Pagination;
 public interface PaymentDao {
 
 
-    public List<PluginPropertyModelDao> getProperties(String transactionExternalKey, InternalCallContext context);
+    public List<PluginPropertyModelDao> getProperties(UUID attenptId, InternalCallContext context);
 
     public PaymentAttemptModelDao insertPaymentAttemptWithProperties(PaymentAttemptModelDao attempt, List<PluginPropertyModelDao> properties, InternalCallContext context);
 
@@ -37,9 +37,9 @@ public interface PaymentDao {
 
     public List<PaymentAttemptModelDao> getPaymentAttempts(String paymentExternalKey, InternalTenantContext context);
 
-    public PaymentAttemptModelDao getPaymentAttemptByExternalKey(String externalKey, InternalTenantContext context);
+    public List<PaymentAttemptModelDao> getPaymentAttemptByTransactionExternalKey(String externalKey, InternalTenantContext context);
 
-    public PaymentTransactionModelDao getDirectPaymentTransactionByExternalKey(String transactionExternalKey, InternalTenantContext context);
+    public List<PaymentTransactionModelDao> getDirectPaymentTransactionsByExternalKey(String transactionExternalKey, InternalTenantContext context);
 
     public PaymentModelDao getDirectPaymentByExternalKey(String externalKey, InternalTenantContext context);
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertyModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertyModelDao.java
index 28855b2..5e0913e 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertyModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertyModelDao.java
@@ -23,6 +23,7 @@ import org.joda.time.DateTime;
 public class PluginPropertyModelDao {
 
     private Long recordId;
+    private UUID attemptId;
     private String paymentExternalKey;
     private String transactionExternalKey;
     private UUID accountId;
@@ -35,12 +36,13 @@ public class PluginPropertyModelDao {
     public PluginPropertyModelDao() { /* For the DAO mapper */
     }
 
-    public PluginPropertyModelDao(final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName, final String propKey, final String propValue, final String createdBy, final DateTime createdDate) {
-        this(-1L, paymentExternalKey, transactionExternalKey, accountId, pluginName, propKey, propValue, createdBy, createdDate);
+    public PluginPropertyModelDao(final UUID attemptId, final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName, final String propKey, final String propValue, final String createdBy, final DateTime createdDate) {
+        this(-1L, attemptId, paymentExternalKey, transactionExternalKey, accountId, pluginName, propKey, propValue, createdBy, createdDate);
     }
 
-    public PluginPropertyModelDao(final Long recordId, final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName, final String propKey, final String propValue, final String createdBy, final DateTime createdDate) {
+    public PluginPropertyModelDao(final Long recordId, final UUID attemptId, final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName, final String propKey, final String propValue, final String createdBy, final DateTime createdDate) {
         this.recordId = recordId;
+        this.attemptId = attemptId;
         this.paymentExternalKey = paymentExternalKey;
         this.transactionExternalKey = transactionExternalKey;
         this.accountId = accountId;
@@ -51,6 +53,14 @@ public class PluginPropertyModelDao {
         this.createdDate = createdDate;
     }
 
+    public UUID getAttemptId() {
+        return attemptId;
+    }
+
+    public void setAttemptId(final UUID attemptId) {
+        this.attemptId = attemptId;
+    }
+
     public Long getRecordId() {
         return recordId;
     }
@@ -134,6 +144,9 @@ public class PluginPropertyModelDao {
 
         final PluginPropertyModelDao that = (PluginPropertyModelDao) o;
 
+        if (attemptId != null ? !attemptId.equals(that.attemptId) : that.attemptId != null) {
+            return false;
+        }
         if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
         }
@@ -169,6 +182,7 @@ public class PluginPropertyModelDao {
         int result = recordId != null ? recordId.hashCode() : 0;
         result = 31 * result + (paymentExternalKey != null ? paymentExternalKey.hashCode() : 0);
         result = 31 * result + (transactionExternalKey != null ? transactionExternalKey.hashCode() : 0);
+        result = 31 * result + (attemptId != null ? attemptId.hashCode() : 0);
         result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
         result = 31 * result + (pluginName != null ? pluginName.hashCode() : 0);
         result = 31 * result + (propKey != null ? propKey.hashCode() : 0);
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySqlDao.java
index 76591f7..b61b13a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySqlDao.java
@@ -42,7 +42,7 @@ public interface PluginPropertySqlDao extends Transactional<PluginPropertySqlDao
 
     @RegisterMapper(PluginPropertySqlDaoMapper.class)
     @SqlQuery
-    List<PluginPropertyModelDao> getPluginProperties(@Bind("transactionExternalKey") final String transactionExternalkey);
+    List<PluginPropertyModelDao> getPluginProperties(@Bind("attemptId") final String attemptId);
 
     @SqlBatch(transactional = false)
     void batchCreateFromTransaction(@BindBean List<PluginPropertyModelDao> dataEntries);
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java
index b997f7b..e27dcdf 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/TransactionSqlDao.java
@@ -46,8 +46,8 @@ public interface TransactionSqlDao extends EntitySqlDao<PaymentTransactionModelD
                                  @BindBean final InternalCallContext context);
 
     @SqlQuery
-    PaymentTransactionModelDao getPaymentTransactionByExternalKey(@Bind("transactionExternalKey") final String transactionExternalKey,
-                                                                        @BindBean final InternalTenantContext context);
+    List<PaymentTransactionModelDao> getPaymentTransactionsByExternalKey(@Bind("transactionExternalKey") final String transactionExternalKey,
+                                                                         @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public List<PaymentTransactionModelDao> getByPaymentId(@Bind("paymentId") final UUID paymentId,
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java b/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
index 972ff68..b72d4c7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
@@ -70,7 +70,7 @@ public abstract class BaseRetryService implements RetryService {
                                                                               }
                                                                               final PaymentRetryNotificationKey key = (PaymentRetryNotificationKey) notificationKey;
                                                                               final InternalCallContext callContext = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, PAYMENT_RETRY_SERVICE, CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
-                                                                              retryPaymentTransaction(key.getTransactionExternalKey(), key.getPluginName(), callContext);
+                                                                              retryPaymentTransaction(key.getAttemptId(), key.getPluginName(), callContext);
                                                                           }
                                                                       }
                                                                      );
@@ -104,17 +104,17 @@ public abstract class BaseRetryService implements RetryService {
             this.internalCallContextFactory = internalCallContextFactory;
         }
 
-        public boolean scheduleRetry(final ObjectType objectType, final UUID objectId, final String transactionExternalKey, final String pluginName, final DateTime timeOfRetry) {
-            return scheduleRetryInternal(objectType, objectId, transactionExternalKey, pluginName, timeOfRetry, null);
+        public boolean scheduleRetry(final ObjectType objectType, final UUID objectId, final UUID attemptId, final String pluginName, final DateTime timeOfRetry) {
+            return scheduleRetryInternal(objectType, objectId, attemptId, pluginName, timeOfRetry, null);
         }
 
 
-        private boolean scheduleRetryInternal(final ObjectType objectType, final UUID objectId, final String transactionExternalKey, final String pluginName, final DateTime timeOfRetry, final EntitySqlDaoWrapperFactory<EntitySqlDao> transactionalDao) {
+        private boolean scheduleRetryInternal(final ObjectType objectType, final UUID objectId, final UUID attemptId, final String pluginName, final DateTime timeOfRetry, final EntitySqlDaoWrapperFactory<EntitySqlDao> transactionalDao) {
             final InternalCallContext context = createCallContextFromPaymentId(objectType, objectId);
 
             try {
                 final NotificationQueue retryQueue = notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, getQueueName());
-                final NotificationEvent key = new PaymentRetryNotificationKey(transactionExternalKey, pluginName);
+                final NotificationEvent key = new PaymentRetryNotificationKey(attemptId, pluginName);
                 if (retryQueue != null) {
                     if (transactionalDao == null) {
                         retryQueue.recordFutureNotification(timeOfRetry, key, context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultRetryService.java b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultRetryService.java
index 83e7555..d120a29 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/DefaultRetryService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/DefaultRetryService.java
@@ -49,8 +49,8 @@ public class DefaultRetryService extends BaseRetryService implements RetryServic
     }
 
     @Override
-    public void retryPaymentTransaction(final String transactionExternalKey, final String pluginName, final InternalCallContext context) {
-        processor.retryPaymentTransaction(transactionExternalKey, pluginName, context);
+    public void retryPaymentTransaction(final UUID attemptId, final String pluginName, final InternalCallContext context) {
+        processor.retryPaymentTransaction(attemptId, pluginName, context);
     }
 
     public static class DefaultRetryServiceScheduler extends RetryServiceScheduler {
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java b/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java
index c410f68..50a3155 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java
@@ -16,6 +16,8 @@
 
 package org.killbill.billing.payment.retry;
 
+import java.util.UUID;
+
 import org.killbill.notificationq.api.NotificationEvent;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
@@ -23,18 +25,18 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class PaymentRetryNotificationKey implements NotificationEvent {
 
-    private final String transactionExternalKey;
+    private final UUID attemptId;
     private final String pluginName;
 
     @JsonCreator
-    public PaymentRetryNotificationKey(@JsonProperty("transactionExternalKey") String transactionExternalKey,
+    public PaymentRetryNotificationKey(@JsonProperty("attemptId") UUID attemptId,
                                        @JsonProperty("pluginName") String pluginName) {
-        this.transactionExternalKey = transactionExternalKey;
+        this.attemptId = attemptId;
         this.pluginName = pluginName;
     }
 
-    public String getTransactionExternalKey() {
-        return transactionExternalKey;
+    public UUID getAttemptId() {
+        return attemptId;
     }
 
     public String getPluginName() {
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/RetryService.java b/payment/src/main/java/org/killbill/billing/payment/retry/RetryService.java
index f18eb48..934fd03 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/RetryService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/RetryService.java
@@ -38,5 +38,5 @@ public interface RetryService {
     // STEPH_RETRY API disappear
     public void retry(UUID paymentId, final Iterable<PluginProperty> properties, final InternalCallContext context);
 
-    public void retryPaymentTransaction(final String transactionExternalKey, String pluginName, final InternalCallContext context);
+    public void retryPaymentTransaction(final UUID attemptId, String pluginName, final InternalCallContext context);
 }
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
index 9ba054d..3060330 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
@@ -47,6 +47,7 @@ from <tableName()>
 where transaction_external_key = :transactionExternalKey
 <andCheckSoftDeletionWithComma("")>
 <AND_CHECK_TENANT("")>
+<defaultOrderBy()>
 ;
 >>
 
@@ -56,7 +57,8 @@ select
 from <tableName()>
 where payment_external_key = :paymentExternalKey
 <andCheckSoftDeletionWithComma("")>
-<AND_CHECK_TENANT("")>
+<AND_CHECK_TENANT()>
+<defaultOrderBy()>
 ;
 >>
 
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PluginPropertySqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PluginPropertySqlDao.sql.stg
index b2a3534..a0d0b05 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PluginPropertySqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PluginPropertySqlDao.sql.stg
@@ -9,7 +9,8 @@ recordIdField(prefix) ::= <<
 recordIdValue() ::= ":recordId"
 
 tableFields(prefix) ::= <<
-  <prefix>payment_external_key
+  <prefix>attempt_id
+, <prefix>payment_external_key
 , <prefix>transaction_external_key
 , <prefix>account_id
 , <prefix>plugin_name
@@ -20,7 +21,8 @@ tableFields(prefix) ::= <<
 >>
 
 tableValues() ::= <<
-  :paymentExternalKey
+  :attemptId
+, :paymentExternalKey
 , :transactionExternalKey
 , :accountId
 , :pluginName
@@ -65,7 +67,7 @@ getPluginProperties() ::= <<
 select
 <allTableFields("")>
 from <tableName()>
-where transaction_external_key = :transactionExternalKey
+where attempt_id = :attemptId
 order by record_id asc
 ;
 >>
\ No newline at end of file
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
index 92766eb..f42c7fb 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
@@ -44,11 +44,13 @@ tableValues() ::= <<
 , :updatedDate
 >>
 
-getPaymentTransactionByExternalKey() ::= <<
+getPaymentTransactionsByExternalKey() ::= <<
 select
 <allTableFields("")>
 from <tableName()>
 where transaction_external_key = :transactionExternalKey
+<AND_CHECK_TENANT()>
+<defaultOrderBy()>
 ;
 >>
 
diff --git a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
index 47592a4..d874f08 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
+++ b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
@@ -202,6 +202,7 @@ CREATE INDEX transaction_history_tenant_account_record_id ON transaction_history
 DROP TABLE IF EXISTS payment_plugin_properties;
 CREATE TABLE payment_plugin_properties (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    attempt_id char(36) NOT NULL,
     payment_external_key varchar(255),
     transaction_external_key varchar(255),
     account_id char(36) NOT NULL,
@@ -212,13 +213,14 @@ CREATE TABLE payment_plugin_properties (
     created_date datetime NOT NULL,
     PRIMARY KEY (record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE INDEX payment_plugin_properties_ext ON payment_plugin_properties(transaction_external_key);
+CREATE INDEX payment_plugin_properties_attempt_id ON payment_plugin_properties(attempt_id);
 
 
 /*  PaymentControlPlugin lives  here until this becomes a first class citizen plugin */
 DROP TABLE IF EXISTS _invoice_payment_control_plugin_auto_pay_off;
 CREATE TABLE _invoice_payment_control_plugin_auto_pay_off (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    attempt_id char(36) NOT NULL,
     payment_external_key varchar(255) NOT NULL,
     transaction_external_key varchar(255) NOT NULL,
     account_id char(36) NOT NULL,
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index 5027058..a1557e5 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -214,6 +214,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         assertNull(payment4.getTransactions().get(3).getGatewayErrorCode());
     }
 
+
     @Test(groups = "slow")
     public void testCreateSuccessPurchaseWithPaymentControl() throws PaymentApiException, InvoiceApiException, EventBusException {
 
@@ -238,7 +239,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
                                                             Currency.USD));
 
         final DirectPayment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
-                                                                                  ImmutableList.<PluginProperty>of(), INVOICE_PAYMENT, callContext);
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
 
         assertEquals(payment.getExternalKey(), paymentExternalKey);
         assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
@@ -292,7 +293,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
         try {
             paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
-                                                        ImmutableList.<PluginProperty>of(), INVOICE_PAYMENT, callContext);
+                                                        createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
             Assert.fail("Unexpected success");
         } catch (PaymentApiException e) {
             assertTrue(e.getCause() instanceof PaymentControlApiException);
@@ -325,7 +326,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         invoice.addInvoiceItem(invoiceItem);
 
         final DirectPayment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
-                                                                                  ImmutableList.<PluginProperty>of(), INVOICE_PAYMENT, callContext);
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
 
         final List<PluginProperty> refundProperties = ImmutableList.<PluginProperty>of();
         final DirectPayment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), requestedAmount, Currency.USD, transactionExternalKey2,
@@ -368,7 +369,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         invoice.addInvoiceItem(invoiceItem);
 
         final DirectPayment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
-                                                                                  ImmutableList.<PluginProperty>of(), INVOICE_PAYMENT, callContext);
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
 
         final List<PluginProperty> refundProperties = ImmutableList.<PluginProperty>of();
 
@@ -406,7 +407,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         invoice.addInvoiceItem(invoiceItem);
 
         final DirectPayment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
-                                                                                  ImmutableList.<PluginProperty>of(), INVOICE_PAYMENT, callContext);
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
 
         final List<PluginProperty> refundProperties = new ArrayList<PluginProperty>();
         final HashMap<UUID, BigDecimal> uuidBigDecimalHashMap = new HashMap<UUID, BigDecimal>();
@@ -439,10 +440,9 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         final DirectPayment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
                                                                 ImmutableList.<PluginProperty>of(), callContext);
 
-        paymentApi.notifyChargeback(account, payment.getTransactions().get(0).getId(), transactionExternalKey2, requestedAmount, Currency.AED,  callContext);
+        paymentApi.notifyChargeback(account, payment.getTransactions().get(0).getId(), transactionExternalKey2, requestedAmount, Currency.AED, callContext);
         final DirectPayment payment2 = paymentApi.getPayment(payment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
 
-
         assertEquals(payment2.getExternalKey(), paymentExternalKey);
         assertEquals(payment2.getPaymentMethodId(), account.getPaymentMethodId());
         assertEquals(payment2.getAccountId(), account.getId());
@@ -476,8 +476,8 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         }
     }
 
-
-    @Test(groups = "slow")
+    // STEPH fix precondition to actually throw a PaymentApiException
+    @Test(groups = "slow", enabled = false)
     public void testCreatePaymentWithNoDefaultPaymentMethod() throws Exception {
         final LocalDate now = clock.getUTCToday();
         final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
@@ -505,4 +505,10 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
             assertEquals(e.getCode(), ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD.getCode());
         }
     }
+
+    private List<PluginProperty> createPropertiesForInvoice(final Invoice invoice) {
+        final List<PluginProperty> result = new ArrayList<PluginProperty>();
+        result.add(new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false));
+        return result;
+    }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java
index 75cc5da..e5fda6b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java
@@ -19,6 +19,7 @@
 package org.killbill.billing.payment.api;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
@@ -80,7 +81,7 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
     public void testSimplePaymentWithNoAmount() throws Exception {
         final BigDecimal invoiceAmount = new BigDecimal("10.0011");
         final BigDecimal requestedAmount = null;
-        final BigDecimal expectedAmount = invoiceAmount;
+        final BigDecimal expectedAmount = null;
 
         testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
     }
@@ -130,8 +131,13 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
                                                             Currency.USD));
 
         try {
+
+            final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+            final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
+            properties.add(prop1);
+
             final DirectPayment paymentInfo = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(),
-                                                                                          invoice.getId().toString(), UUID.randomUUID().toString(), PLUGIN_PROPERTIES, PAYMENT_OPTIONS, callContext);
+                                                                                          invoice.getId().toString(), UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
             if (expectedAmount == null) {
                 fail("Expected to fail because requested amount > invoice amount");
             }
diff --git a/payment/src/test/java/org/killbill/billing/payment/control/dao/TestInvoicePaymentControlDao.java b/payment/src/test/java/org/killbill/billing/payment/control/dao/TestInvoicePaymentControlDao.java
index 279fa36..27429cc 100644
--- a/payment/src/test/java/org/killbill/billing/payment/control/dao/TestInvoicePaymentControlDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/control/dao/TestInvoicePaymentControlDao.java
@@ -17,15 +17,12 @@
 package org.killbill.billing.payment.control.dao;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
@@ -41,15 +38,16 @@ public class TestInvoicePaymentControlDao extends PaymentTestSuiteWithEmbeddedDB
         dao = new InvoicePaymentControlDao(dbi);
     }
 
-        @Test(groups = "slow")
+    @Test(groups = "slow")
     public void testPluginAutoPayOffSimple() {
 
         UUID accountId = UUID.randomUUID();
+        UUID attemptId = UUID.randomUUID();
         UUID paymentId = UUID.randomUUID();
         UUID methodId = UUID.randomUUID();
         BigDecimal amount = new BigDecimal("13.33");
         DateTime utcNow = clock.getUTCNow();
-        final PluginAutoPayOffModelDao entry1 = new PluginAutoPayOffModelDao("key1", "tkey1", accountId, "XXX", paymentId, methodId, amount, Currency.USD, "lulu", utcNow);
+        final PluginAutoPayOffModelDao entry1 = new PluginAutoPayOffModelDao(attemptId, "key1", "tkey1", accountId, "XXX", paymentId, methodId, amount, Currency.USD, "lulu", utcNow);
         dao.insertAutoPayOff(entry1);
 
         final List<PluginAutoPayOffModelDao> entries = dao.getAutoPayOffEntry(accountId);
@@ -70,15 +68,16 @@ public class TestInvoicePaymentControlDao extends PaymentTestSuiteWithEmbeddedDB
     public void testPluginAutoPayOffMutlitpleEntries() {
 
         UUID accountId = UUID.randomUUID();
+        UUID attemptId = UUID.randomUUID();
         UUID paymentId1 = UUID.randomUUID();
         UUID methodId = UUID.randomUUID();
         BigDecimal amount = new BigDecimal("13.33");
         DateTime utcNow = clock.getUTCNow();
-        final PluginAutoPayOffModelDao entry1 = new PluginAutoPayOffModelDao("key1", "tkey1", accountId, "XXX", paymentId1, methodId, amount, Currency.USD, "lulu", utcNow);
+        final PluginAutoPayOffModelDao entry1 = new PluginAutoPayOffModelDao(attemptId, "key1", "tkey1", accountId, "XXX", paymentId1, methodId, amount, Currency.USD, "lulu", utcNow);
         dao.insertAutoPayOff(entry1);
 
         UUID paymentId2 = UUID.randomUUID();
-        final PluginAutoPayOffModelDao entry2 = new PluginAutoPayOffModelDao("key2", "tkey2", accountId, "XXX", paymentId2, methodId, amount, Currency.USD, "lulu", utcNow);
+        final PluginAutoPayOffModelDao entry2 = new PluginAutoPayOffModelDao(attemptId, "key2", "tkey2", accountId, "XXX", paymentId2, methodId, amount, Currency.USD, "lulu", utcNow);
         dao.insertAutoPayOff(entry2);
 
         final List<PluginAutoPayOffModelDao> entries = dao.getAutoPayOffEntry(accountId);
@@ -90,10 +89,11 @@ public class TestInvoicePaymentControlDao extends PaymentTestSuiteWithEmbeddedDB
 
         UUID accountId = UUID.randomUUID();
         UUID paymentId1 = UUID.randomUUID();
+        UUID attemptId = UUID.randomUUID();
         UUID methodId = UUID.randomUUID();
         BigDecimal amount = new BigDecimal("13.33");
         DateTime utcNow = clock.getUTCNow();
-        final PluginAutoPayOffModelDao entry1 = new PluginAutoPayOffModelDao("key1", "tkey1", accountId, "XXX", paymentId1, methodId, amount, Currency.USD, "lulu", utcNow);
+        final PluginAutoPayOffModelDao entry1 = new PluginAutoPayOffModelDao(attemptId, "key1", "tkey1", accountId, "XXX", paymentId1, methodId, amount, Currency.USD, "lulu", utcNow);
         dao.insertAutoPayOff(entry1);
 
         final List<PluginAutoPayOffModelDao> entries = dao.getAutoPayOffEntry(UUID.randomUUID());
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryableDirectPayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryableDirectPayment.java
index c65752e..f053e8f 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryableDirectPayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryableDirectPayment.java
@@ -17,9 +17,11 @@
 package org.killbill.billing.payment.core.sm;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 
+import javax.annotation.Nullable;
 import javax.inject.Named;
 
 import org.joda.time.DateTime;
@@ -62,12 +64,15 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
 import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
 
 public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
 
@@ -219,7 +224,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
                    callContext,
                    internalCallContext);
 
-        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext).get(0);
         assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
         assertEquals(pa.getStateName(), "ABORTED");
         assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
@@ -253,7 +258,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
                    callContext,
                    internalCallContext);
 
-        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext).get(0);
         assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
         assertEquals(pa.getStateName(), "SUCCESS");
         assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
@@ -286,7 +291,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
                    null,
                    callContext, internalCallContext);
 
-        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext).get(0);
         assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
         assertEquals(pa.getStateName(), "SUCCESS");
         assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
@@ -323,7 +328,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
             Assert.fail("Expected PaymentApiException...");
 
         } catch (final PaymentApiException e) {
-            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext).get(0);
             assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
             assertEquals(pa.getStateName(), "ABORTED");
             assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
@@ -360,7 +365,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
 
             Assert.fail("Expected PaymentApiException...");
         } catch (final PaymentApiException e) {
-            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext).get(0);
             assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
             assertEquals(pa.getStateName(), "RETRIED");
             assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
@@ -397,7 +402,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
 
             Assert.fail("Expected Exception...");
         } catch (final PaymentApiException e) {
-            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext).get(0);
             assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
             assertEquals(pa.getStateName(), "RETRIED");
             assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
@@ -434,7 +439,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
 
             Assert.fail("Expected Exception...");
         } catch (final PaymentApiException e) {
-            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext).get(0);
             assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
             assertEquals(pa.getStateName(), "ABORTED");
             assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
@@ -477,10 +482,16 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
                    callContext,
                    internalCallContext);
 
-        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
-        assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
-        assertEquals(pa.getStateName(), "SUCCESS");
-        assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+        final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+        assertEquals(pas.size(), 2);
+        final PaymentAttemptModelDao successfulAttempt = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+            @Override
+            public boolean apply(final PaymentAttemptModelDao input) {
+                return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                       input.getStateName().equals("SUCCESS");
+            }
+        }).orNull();
+        assertNotNull(successfulAttempt);
     }
 
     @Test(groups = "fast")
@@ -523,7 +534,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
 
             Assert.fail("Expecting paymentApiException...");
         } catch (final PaymentApiException e) {
-            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext).get(0);
             assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
             assertEquals(pa.getStateName(), "RETRIED");
             assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
@@ -570,10 +581,18 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
 
             Assert.fail("Expecting paymentApiException...");
         } catch (final PaymentApiException e) {
-            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
-            assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
-            assertEquals(pa.getStateName(), "ABORTED");
-            assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+
+            final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+            assertEquals(pas.size(), 2);
+
+            final PaymentAttemptModelDao failedAttempts = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+                @Override
+                public boolean apply(final PaymentAttemptModelDao input) {
+                    return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                           input.getStateName().equals("ABORTED");
+                }
+            }).orNull();
+            assertNotNull(failedAttempts);
         }
     }
 
@@ -594,21 +613,29 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
         final State state = runner.fetchState("RETRIED");
         final UUID directTransactionId = UUID.randomUUID();
         final UUID directPaymentId = UUID.randomUUID();
-        paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
-                                                                                 directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
-                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null),
+        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
+                                                                    directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
+                                                                    TransactionType.AUTHORIZE, state.getName(), amount, currency, null);
+        paymentDao.insertPaymentAttemptWithProperties(attempt,
                                                       ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
                                                      );
         paymentDao.insertDirectPaymentWithFirstTransaction(new PaymentModelDao(directPaymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, directPaymentExternalKey),
                                                            new PaymentTransactionModelDao(directTransactionId, directPaymentTransactionExternalKey, utcNow, utcNow, directPaymentId, TransactionType.AUTHORIZE, utcNow, TransactionStatus.PAYMENT_FAILURE, amount, currency, "bla", "foo"),
                                                            internalCallContext);
 
-        processor.retryPaymentTransaction(directPaymentTransactionExternalKey, MockPaymentControlProviderPlugin.PLUGIN_NAME, internalCallContext);
+        processor.retryPaymentTransaction(attempt.getId(), MockPaymentControlProviderPlugin.PLUGIN_NAME, internalCallContext);
 
-        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
-        assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
-        assertEquals(pa.getStateName(), "SUCCESS");
-        assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+        final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+        assertEquals(pas.size(), 2);
+
+        final PaymentAttemptModelDao successfulAttempt = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+            @Override
+            public boolean apply(final PaymentAttemptModelDao input) {
+                return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                       input.getStateName().equals("SUCCESS");
+            }
+        }).orNull();
+        assertNotNull(successfulAttempt);
     }
 
     @Test(groups = "fast")
@@ -628,9 +655,10 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
         final State state = runner.fetchState("RETRIED");
         final UUID directTransactionId = UUID.randomUUID();
         final UUID directPaymentId = UUID.randomUUID();
-        paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
-                                                                                 directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
-                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null),
+        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
+                                                                    directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
+                                                                    TransactionType.AUTHORIZE, state.getName(), amount, currency, null);
+        paymentDao.insertPaymentAttemptWithProperties(attempt,
                                                       ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
                                                      );
         paymentDao.insertDirectPaymentWithFirstTransaction(new PaymentModelDao(directPaymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, directPaymentExternalKey),
@@ -639,12 +667,18 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
                                                            internalCallContext
                                                           );
 
-        processor.retryPaymentTransaction(directPaymentTransactionExternalKey, MockPaymentControlProviderPlugin.PLUGIN_NAME, internalCallContext);
+        processor.retryPaymentTransaction(attempt.getId(), MockPaymentControlProviderPlugin.PLUGIN_NAME, internalCallContext);
 
-        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
-        assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
-        assertEquals(pa.getStateName(), "ABORTED");
-        assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+        final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+        assertEquals(pas.size(), 2);
+        final PaymentAttemptModelDao failedAttempt = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+            @Override
+            public boolean apply(final PaymentAttemptModelDao input) {
+                return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                       input.getStateName().equals("ABORTED");
+            }
+        }).orNull();
+        assertNotNull(failedAttempt);
     }
 
     @Test(groups = "fast")
@@ -669,9 +703,10 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
             final State state = runner.fetchState("RETRIED");
             final UUID directTransactionId = UUID.randomUUID();
             final UUID directPaymentId = UUID.randomUUID();
-            paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
-                                                                                     directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
-                                                                                     TransactionType.AUTHORIZE, state.getName(), amount, currency, null),
+            final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
+                                                                        directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
+                                                                        TransactionType.AUTHORIZE, state.getName(), amount, currency, null);
+            paymentDao.insertPaymentAttemptWithProperties(attempt,
                                                           ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
                                                          );
             paymentDao.insertDirectPaymentWithFirstTransaction(new PaymentModelDao(directPaymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, directPaymentExternalKey),
@@ -680,12 +715,18 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
                                                                internalCallContext
                                                               );
 
-            processor.retryPaymentTransaction(directPaymentTransactionExternalKey, MockPaymentControlProviderPlugin.PLUGIN_NAME, internalCallContext);
-
-            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByExternalKey(directPaymentTransactionExternalKey, internalCallContext);
-            assertEquals(pa.getTransactionExternalKey(), directPaymentTransactionExternalKey);
-            assertEquals(pa.getStateName(), "ABORTED");
-            assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+            processor.retryPaymentTransaction(attempt.getId(), MockPaymentControlProviderPlugin.PLUGIN_NAME, internalCallContext);
+
+            final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(directPaymentTransactionExternalKey, internalCallContext);
+            assertEquals(pas.size(), 2);
+            final PaymentAttemptModelDao failedAttempt = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+                @Override
+                public boolean apply(final PaymentAttemptModelDao input) {
+                    return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                           input.getStateName().equals("ABORTED");
+                }
+            }).orNull();
+            assertNotNull(failedAttempt);
         } finally {
             if (lock != null) {
                 lock.release();
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
index d6005e5..52d05b7 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
@@ -39,23 +39,28 @@ public class MockPaymentDao implements PaymentDao {
 
     private final Map<UUID, PaymentModelDao> payments = new HashMap<UUID, PaymentModelDao>();
     private final Map<UUID, PaymentTransactionModelDao> transactions = new HashMap<UUID, PaymentTransactionModelDao>();
-    private final Map<String, PaymentAttemptModelDao> attempts = new HashMap<String, PaymentAttemptModelDao>();
+    private final Map<UUID, PaymentAttemptModelDao> attempts = new HashMap<UUID, PaymentAttemptModelDao>();
+    private final List<PluginPropertyModelDao> properties = new ArrayList<PluginPropertyModelDao>();
 
     public void reset() {
-        payments.clear();
-        transactions.clear();
-        attempts.clear();
+        synchronized (this) {
+            payments.clear();
+            transactions.clear();
+            attempts.clear();
+            properties.clear();
+        }
     }
 
     @Override
-    public List<PluginPropertyModelDao> getProperties(final String transactionExternalKey, final InternalCallContext context) {
-        return null;
+    public List<PluginPropertyModelDao> getProperties(final UUID attemptId, final InternalCallContext context) {
+        return properties;
     }
 
     @Override
     public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final List<PluginPropertyModelDao> properties, final InternalCallContext context) {
         synchronized (this) {
-            attempts.put(attempt.getTransactionExternalKey(), attempt);
+            attempts.put(attempt.getId(), attempt);
+            this.properties.addAll(properties);
             return attempt;
         }
     }
@@ -80,32 +85,41 @@ public class MockPaymentDao implements PaymentDao {
 
     @Override
     public List<PaymentAttemptModelDao> getPaymentAttempts(final String paymentExternalKey, final InternalTenantContext context) {
-        final List<PaymentAttemptModelDao> result = new ArrayList<PaymentAttemptModelDao>();
-        for (PaymentAttemptModelDao cur : attempts.values()) {
-            if (cur.getPaymentExternalKey().equals(paymentExternalKey)) {
-                result.add(cur);
+        synchronized (this) {
+            final List<PaymentAttemptModelDao> result = new ArrayList<PaymentAttemptModelDao>();
+            for (PaymentAttemptModelDao cur : attempts.values()) {
+                if (cur.getPaymentExternalKey().equals(paymentExternalKey)) {
+                    result.add(cur);
+                }
             }
+            return result;
         }
-        return result;
     }
 
     @Override
-    public PaymentAttemptModelDao getPaymentAttemptByExternalKey(final String transactionExternalKey, final InternalTenantContext context) {
+    public List<PaymentAttemptModelDao> getPaymentAttemptByTransactionExternalKey(final String transactionExternalKey, final InternalTenantContext context) {
         synchronized (this) {
-            return attempts.get(transactionExternalKey);
+            final List<PaymentAttemptModelDao> result = new ArrayList<PaymentAttemptModelDao>();
+            for (PaymentAttemptModelDao cur : attempts.values()) {
+                if (cur.getTransactionExternalKey().equals(transactionExternalKey)) {
+                    result.add(cur);
+                }
+            }
+            return result;
         }
     }
 
     @Override
-    public PaymentTransactionModelDao getDirectPaymentTransactionByExternalKey(final String transactionExternalKey, final InternalTenantContext context) {
+    public List<PaymentTransactionModelDao> getDirectPaymentTransactionsByExternalKey(final String transactionExternalKey, final InternalTenantContext context) {
+        final List<PaymentTransactionModelDao> result = new ArrayList<PaymentTransactionModelDao>();
         synchronized (this) {
             for (PaymentTransactionModelDao cur : transactions.values()) {
                 if (cur.getTransactionExternalKey().equals(transactionExternalKey)) {
-                    return cur;
+                    result.add(cur);
                 }
             }
         }
-        return null;
+        return result;
     }
 
     @Override
@@ -217,36 +231,49 @@ public class MockPaymentDao implements PaymentDao {
 
     @Override
     public PaymentAttemptModelDao getPaymentAttempt(final UUID attemptId, final InternalTenantContext context) {
-        return attempts.get(attemptId);
+        synchronized (this) {
+            return Iterables.tryFind(attempts.values(), new Predicate<PaymentAttemptModelDao>() {
+                @Override
+                public boolean apply(final PaymentAttemptModelDao input) {
+                    return input.getId().equals(attemptId);
+                }
+            }).orNull();
+        }
     }
 
     private final List<PaymentMethodModelDao> paymentMethods = new LinkedList<PaymentMethodModelDao>();
 
     @Override
     public PaymentMethodModelDao insertPaymentMethod(final PaymentMethodModelDao paymentMethod, final InternalCallContext context) {
-        paymentMethods.add(paymentMethod);
-        return paymentMethod;
+        synchronized (this) {
+            paymentMethods.add(paymentMethod);
+            return paymentMethod;
+        }
     }
 
     @Override
     public PaymentMethodModelDao getPaymentMethod(final UUID paymentMethodId, final InternalTenantContext context) {
-        for (final PaymentMethodModelDao cur : paymentMethods) {
-            if (cur.getId().equals(paymentMethodId)) {
-                return cur;
+        synchronized (this) {
+            for (final PaymentMethodModelDao cur : paymentMethods) {
+                if (cur.getId().equals(paymentMethodId)) {
+                    return cur;
+                }
             }
+            return null;
         }
-        return null;
     }
 
     @Override
     public List<PaymentMethodModelDao> getPaymentMethods(final UUID accountId, final InternalTenantContext context) {
-        final List<PaymentMethodModelDao> result = new ArrayList<PaymentMethodModelDao>();
-        for (final PaymentMethodModelDao cur : paymentMethods) {
-            if (cur.getAccountId().equals(accountId)) {
-                result.add(cur);
+        synchronized (this) {
+            final List<PaymentMethodModelDao> result = new ArrayList<PaymentMethodModelDao>();
+            for (final PaymentMethodModelDao cur : paymentMethods) {
+                if (cur.getAccountId().equals(accountId)) {
+                    result.add(cur);
+                }
             }
+            return result;
         }
-        return result;
     }
 
     @Override
@@ -256,12 +283,14 @@ public class MockPaymentDao implements PaymentDao {
 
     @Override
     public void deletedPaymentMethod(final UUID paymentMethodId, final InternalCallContext context) {
-        final Iterator<PaymentMethodModelDao> it = paymentMethods.iterator();
-        while (it.hasNext()) {
-            final PaymentMethodModelDao cur = it.next();
-            if (cur.getId().equals(paymentMethodId)) {
-                it.remove();
-                break;
+        synchronized (this) {
+            final Iterator<PaymentMethodModelDao> it = paymentMethods.iterator();
+            while (it.hasNext()) {
+                final PaymentMethodModelDao cur = it.next();
+                if (cur.getId().equals(paymentMethodId)) {
+                    it.remove();
+                    break;
+                }
             }
         }
     }
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index efafe53..839e65a 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -37,7 +37,6 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testPaymentAttempt() {
-        final UUID paymentId = UUID.randomUUID();
         final UUID directTransactionId = UUID.randomUUID();
         final String paymentExternalKey = "vraiment?";
         final String transactionExternalKey = "tduteuqweq";
@@ -46,24 +45,28 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
         final String pluginName = "superPlugin";
 
         final UUID accountId = UUID.randomUUID();
-        final PluginPropertyModelDao prop1 = new PluginPropertyModelDao("foo", transactionExternalKey, accountId, "PLUGIN", "key1", "value1", "yo", clock.getUTCNow());
-        final PluginPropertyModelDao prop2 = new PluginPropertyModelDao("foo2", transactionExternalKey, accountId, "PLUGIN", "key2", "value2", "yo", clock.getUTCNow());
-        final PluginPropertyModelDao prop3 = new PluginPropertyModelDao("foo3", "other", UUID.randomUUID(), "PLUGIN", "key2", "value2", "yo", clock.getUTCNow());
+
+        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(UUID.randomUUID(), UUID.randomUUID(), clock.getUTCNow(), clock.getUTCNow(),
+                                                                          paymentExternalKey, directTransactionId, transactionExternalKey, transactionType, stateName,
+                                                                          BigDecimal.ZERO, Currency.ALL, pluginName);
+
+
+        final PluginPropertyModelDao prop1 = new PluginPropertyModelDao(attempt.getId(), "foo", transactionExternalKey, accountId, "PLUGIN", "key1", "value1", "yo", clock.getUTCNow());
+        final PluginPropertyModelDao prop2 = new PluginPropertyModelDao(attempt.getId(), "foo2", transactionExternalKey, accountId, "PLUGIN", "key2", "value2", "yo", clock.getUTCNow());
+        final PluginPropertyModelDao prop3 = new PluginPropertyModelDao(UUID.randomUUID()   , "foo3", "other", UUID.randomUUID(), "PLUGIN", "key2", "value2", "yo", clock.getUTCNow());
         final List<PluginPropertyModelDao> props = new ArrayList<PluginPropertyModelDao>();
         props.add(prop1);
         props.add(prop2);
         props.add(prop3);
 
-        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(UUID.randomUUID(), UUID.randomUUID(), clock.getUTCNow(), clock.getUTCNow(),
-                                                                          paymentExternalKey, directTransactionId, transactionExternalKey, transactionType, stateName,
-                                                                          BigDecimal.ZERO, Currency.ALL, pluginName);
+
         PaymentAttemptModelDao savedAttempt = paymentDao.insertPaymentAttemptWithProperties(attempt, props, internalCallContext);
         assertEquals(savedAttempt.getTransactionExternalKey(), transactionExternalKey);
         assertEquals(savedAttempt.getTransactionType(), transactionType);
         assertEquals(savedAttempt.getStateName(), stateName);
         assertEquals(savedAttempt.getPluginName(), pluginName);
 
-        final List<PluginPropertyModelDao> retrievedProperties = paymentDao.getProperties(transactionExternalKey, internalCallContext);
+        final List<PluginPropertyModelDao> retrievedProperties = paymentDao.getProperties(attempt.getId(), internalCallContext);
         assertEquals(retrievedProperties.size(), 2);
         assertEquals(retrievedProperties.get(0).getAccountId(), accountId);
         assertEquals(retrievedProperties.get(0).getTransactionExternalKey(), transactionExternalKey);
@@ -87,11 +90,12 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(retrievedAttempt1.getStateName(), stateName);
         assertEquals(retrievedAttempt1.getPluginName(), pluginName);
 
-        final PaymentAttemptModelDao retrievedAttempt2 = paymentDao.getPaymentAttemptByExternalKey(transactionExternalKey, internalCallContext);
-        assertEquals(retrievedAttempt2.getTransactionExternalKey(), transactionExternalKey);
-        assertEquals(retrievedAttempt2.getTransactionType(), transactionType);
-        assertEquals(retrievedAttempt2.getStateName(), stateName);
-        assertEquals(retrievedAttempt2.getPluginName(), pluginName);
+        final List<PaymentAttemptModelDao> retrievedAttempts = paymentDao.getPaymentAttemptByTransactionExternalKey(transactionExternalKey, internalCallContext);
+        assertEquals(retrievedAttempts.size(), 1);
+        assertEquals(retrievedAttempts.get(0).getTransactionExternalKey(), transactionExternalKey);
+        assertEquals(retrievedAttempts.get(0).getTransactionType(), transactionType);
+        assertEquals(retrievedAttempts.get(0).getStateName(), stateName);
+        assertEquals(retrievedAttempts.get(0).getPluginName(), pluginName);
     }
 
     @Test(groups = "slow")
@@ -140,7 +144,9 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(savedTransaction.getAmount().compareTo(BigDecimal.TEN), 0);
         assertEquals(savedTransaction.getCurrency(), Currency.AED);
 
-        final PaymentTransactionModelDao savedTransaction2 = paymentDao.getDirectPaymentTransactionByExternalKey(transactionExternalKey, internalCallContext);
+        final List<PaymentTransactionModelDao> savedTransactions = paymentDao.getDirectPaymentTransactionsByExternalKey(transactionExternalKey, internalCallContext);
+        assertEquals(savedTransactions.size(), 1);
+        final PaymentTransactionModelDao savedTransaction2 = savedTransactions.get(0);
         assertEquals(savedTransaction2.getTransactionExternalKey(), transactionExternalKey);
         assertEquals(savedTransaction2.getPaymentId(), paymentModelDao.getId());
         assertEquals(savedTransaction2.getTransactionType(), TransactionType.AUTHORIZE);
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
index 26c7e29..686f48b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -338,18 +338,15 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
 
     private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType type, final BigDecimal amount, final Currency currency) throws PaymentPluginApiException {
 
-        boolean prev = makeNextInvoiceFailWithException.get();
-
-
         if (makeNextInvoiceFailWithException.getAndSet(false)) {
-            System.out.println("################## (STEPH) MockPaymentProviderPlugin getPaymentTransactionInfoPluginResult makeNextInvoiceFailWithException (prev) = " + prev + " => THROW");
+            System.out.println("################## (STEPH) MockPaymentProviderPlugin getPaymentTransactionInfoPluginResult makeNextInvoiceFailWithException  => THROW");
             throw new PaymentPluginApiException("", "test error");
         }
 
 
         final PaymentPluginStatus status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED;
 
-        System.out.println("################## (STEPH) MockPaymentProviderPlugin getPaymentTransactionInfoPluginResult makeNextInvoiceFailWithException (prev) = " + prev + " => status = " + status);
+        System.out.println("################## (STEPH) MockPaymentProviderPlugin getPaymentTransactionInfoPluginResult makeNextInvoiceFailWithError => status = " + status);
 
         InternalPaymentInfo info = payments.get(kbPaymentId.toString());
         if (info == null) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
index 08d3cd6..9bfcdae 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
@@ -17,6 +17,7 @@
 package org.killbill.billing.payment;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -24,6 +25,8 @@ import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeoutException;
 
+import javax.annotation.Nullable;
+
 import org.joda.time.LocalDate;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.api.Currency;
@@ -32,6 +35,7 @@ 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.control.InvoicePaymentControlPluginApi;
+import org.killbill.billing.payment.dao.MockPaymentDao;
 import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.glue.DefaultPaymentService;
@@ -40,9 +44,14 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.jayway.awaitility.Awaitility;
+import com.jayway.awaitility.Duration;
 
 import static com.jayway.awaitility.Awaitility.await;
+import static com.jayway.awaitility.Awaitility.setDefaultPollInterval;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
@@ -57,10 +66,15 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
     public void beforeMethod() throws Exception {
         super.beforeMethod();
 
+        setDefaultPollInterval(Duration.ONE_HUNDRED_MILLISECONDS);
+        Awaitility.setDefaultPollDelay(Duration.SAME_AS_POLL_INTERVAL);
+
+        ((MockPaymentDao) paymentDao).reset();
         mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME);
         mockPaymentProviderPlugin.clear();
         retryService.initialize(DefaultPaymentService.SERVICE_NAME);
         retryService.start();
+
     }
 
     @Override
@@ -70,10 +84,8 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
         retryService.stop();
     }
 
-    private DirectPayment getPaymentForInvoice(final UUID invoiceId) throws PaymentApiException {
-        final String paymentExternalKey = invoiceId.toString();
-        final DirectPayment payment = paymentProcessor.getPaymentByExternalKey(paymentExternalKey, false, ImmutableList.<PluginProperty>of(), callContext, internalCallContext);
-        assertEquals(payment.getExternalKey(), paymentExternalKey);
+    private DirectPayment getPaymentForExternalKey(final String externalKey) throws PaymentApiException {
+        final DirectPayment payment = paymentProcessor.getPaymentByExternalKey(externalKey, false, ImmutableList.<PluginProperty>of(), callContext, internalCallContext);
         return payment;
     }
 
@@ -130,17 +142,18 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
         setPaymentFailure(failureType);
 
         boolean failed = false;
+        final String paymentExternalKey = UUID.randomUUID().toString();
         final String transactionExternalKey = UUID.randomUUID().toString();
         try {
-            pluginControlledPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, invoice.getId().toString(), transactionExternalKey,
-                                                            ImmutableList.<PluginProperty>of(), InvoicePaymentControlPluginApi.PLUGIN_NAME, callContext, internalCallContext);
+            pluginControlledPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                            createPropertiesForInvoice(invoice), InvoicePaymentControlPluginApi.PLUGIN_NAME, callContext, internalCallContext);
         } catch (final PaymentApiException e) {
             failed = true;
         }
         assertTrue(failed);
 
-        DirectPayment payment = getPaymentForInvoice(invoice.getId());
-        List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(payment.getExternalKey(), internalCallContext);
+        DirectPayment payment = getPaymentForExternalKey(paymentExternalKey);
+        List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
         assertEquals(attempts.size(), 1);
 
         final List<PaymentTransactionModelDao> transactions = paymentDao.getDirectTransactionsForDirectPayment(payment.getId(), internalCallContext);
@@ -225,4 +238,11 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
             return paymentConfig.getPluginFailureRetryMaxAttempts();
         }
     }
+
+    private List<PluginProperty> createPropertiesForInvoice(final Invoice invoice) {
+        final List<PluginProperty> result = new ArrayList<PluginProperty>();
+        result.add(new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false));
+        return result;
+    }
+
 }