killbill-uncached

Remove plugin property table and add blob into payment_attempts

7/7/2014 10:03:11 PM

Changes

payment/pom.xml 14(+13 -1)

payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertyModelDao.java 194(+0 -194)

payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySqlDao.java 52(+0 -52)

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java
index 323394c..2097a57 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java
@@ -37,7 +37,6 @@ import org.killbill.billing.payment.api.DirectPaymentTransaction;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionType;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -68,7 +67,6 @@ public class RefundChecker {
 
     public DirectPaymentTransaction checkRefund(final UUID paymentId, final CallContext context, ExpectedRefundCheck expected) throws PaymentApiException {
 
-
         final DirectPayment payment = paymentApi.getPayment(paymentId, false, ImmutableList.<PluginProperty>of(), context);
         final DirectPaymentTransaction refund = Iterables.tryFind(payment.getTransactions(), new Predicate<DirectPaymentTransaction>() {
             @Override

payment/pom.xml 14(+13 -1)

diff --git a/payment/pom.xml b/payment/pom.xml
index 343a324..a84a534 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -27,6 +27,15 @@
     <name>killbill-payment</name>
     <dependencies>
         <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
             <scope>provided</scope>
@@ -52,6 +61,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>com.ning</groupId>
+            <artifactId>compress-lzf</artifactId>
+        </dependency>
+        <dependency>
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
@@ -168,7 +181,6 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
-
         <!--  TEST SCOPE -->
         <dependency>
             <groupId>org.slf4j</groupId>
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 2fd7dd6..b9920c0 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
@@ -29,17 +29,10 @@ import javax.inject.Inject;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.PreparedBatch;
-import org.skife.jdbi.v2.PreparedBatchPart;
-import org.skife.jdbi.v2.TransactionCallback;
-import org.skife.jdbi.v2.TransactionStatus;
 import org.skife.jdbi.v2.tweak.HandleCallback;
 
-import com.google.common.base.Objects;
-
 public class InvoicePaymentControlDao {
 
     private final IDBI dbi;
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 7c58873..c733015 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
@@ -17,14 +17,12 @@
 package org.killbill.billing.payment.core;
 
 import java.math.BigDecimal;
-import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
-import org.joda.time.DateTime;
 import org.killbill.automaton.State;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
@@ -37,14 +35,13 @@ import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.DirectPayment;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
-import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.sm.PluginControlledDirectPaymentAutomatonRunner;
-import org.killbill.billing.payment.dao.PaymentModelDao;
-import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.dao.PaymentDao;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.callcontext.CallContext;
@@ -53,10 +50,7 @@ import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
 
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.inject.name.Named;
 
 import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
@@ -85,17 +79,17 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
     public DirectPayment createAuthorization(final boolean isApiPayment, final Account account, final UUID paymentMethodId, @Nullable final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey, final String transactionExternalKey,
                                              final Iterable<PluginProperty> properties, final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
-                                                         TransactionType.AUTHORIZE,
-                                                         account,
-                                                         paymentMethodId,
-                                                         directPaymentId,
-                                                         paymentExternalKey,
-                                                         transactionExternalKey,
-                                                         amount,
-                                                         currency,
-                                                         properties,
-                                                         paymentControlPluginName,
-                                                         callContext, internalCallContext);
+                                                                TransactionType.AUTHORIZE,
+                                                                account,
+                                                                paymentMethodId,
+                                                                directPaymentId,
+                                                                paymentExternalKey,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
     }
 
     public DirectPayment createCapture(final boolean isApiPayment, final Account account, final UUID directPaymentId, final BigDecimal amount, final Currency currency,
@@ -103,66 +97,66 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
                                        final Iterable<PluginProperty> properties, final String paymentControlPluginName,
                                        final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
-                                                         TransactionType.CAPTURE,
-                                                         account,
-                                                         null,
-                                                         directPaymentId,
-                                                         null,
-                                                         transactionExternalKey,
-                                                         amount,
-                                                         currency,
-                                                         properties,
-                                                         paymentControlPluginName,
-                                                         callContext, internalCallContext);
+                                                                TransactionType.CAPTURE,
+                                                                account,
+                                                                null,
+                                                                directPaymentId,
+                                                                null,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
     }
 
     public DirectPayment createPurchase(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID directPaymentId, final BigDecimal amount, final Currency currency,
                                         final String paymentExternalKey, final String transactionExternalKey, final Iterable<PluginProperty> properties,
                                         final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
-                                                         TransactionType.PURCHASE,
-                                                         account,
-                                                         paymentMethodId,
-                                                         directPaymentId,
-                                                         paymentExternalKey,
-                                                         transactionExternalKey,
-                                                         amount,
-                                                         currency,
-                                                         properties,
-                                                         paymentControlPluginName,
-                                                         callContext, internalCallContext);
+                                                                TransactionType.PURCHASE,
+                                                                account,
+                                                                paymentMethodId,
+                                                                directPaymentId,
+                                                                paymentExternalKey,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
     }
 
     public DirectPayment createVoid(final boolean isApiPayment, final Account account, final UUID directPaymentId, final String transactionExternalKey,
                                     final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
-                                                         TransactionType.VOID,
-                                                         account,
-                                                         null,
-                                                         directPaymentId,
-                                                         null,
-                                                         transactionExternalKey,
-                                                         null,
-                                                         null,
-                                                         properties,
-                                                         null,
-                                                         callContext, internalCallContext);
+                                                                TransactionType.VOID,
+                                                                account,
+                                                                null,
+                                                                directPaymentId,
+                                                                null,
+                                                                transactionExternalKey,
+                                                                null,
+                                                                null,
+                                                                properties,
+                                                                null,
+                                                                callContext, internalCallContext);
     }
 
     public DirectPayment createRefund(final boolean isApiPayment, final Account account, final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String transactionExternalKey,
                                       final Iterable<PluginProperty> properties, final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
         return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
-                                                         TransactionType.REFUND,
-                                                         account,
-                                                         null,
-                                                         directPaymentId,
-                                                         null,
-                                                         transactionExternalKey,
-                                                         amount,
-                                                         currency,
-                                                         properties,
-                                                         paymentControlPluginName,
-                                                         callContext, internalCallContext);
+                                                                TransactionType.REFUND,
+                                                                account,
+                                                                null,
+                                                                directPaymentId,
+                                                                null,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
     }
 
     public DirectPayment createCredit(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey,
@@ -198,7 +192,6 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
                                                                 callContext, internalCallContext);
     }
 
-
     public void retryPaymentTransaction(final UUID attemptId, final String pluginName, final InternalCallContext internalCallContext) {
         try {
 
@@ -206,44 +199,33 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
             final PaymentModelDao payment = paymentDao.getDirectPaymentByExternalKey(attempt.getPaymentExternalKey(), internalCallContext);
             final UUID paymentId = payment != null ? payment.getId() : null;
 
-            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>() {
-                @Nullable
-                @Override
-                public PluginProperty apply(final PluginPropertyModelDao input) {
-                    return new PluginProperty(input.getPropKey(), input.getPropValue(), false);
-                }
-            }));
-
+            final Iterable<PluginProperty> pluginProperties = PluginPropertySerializer.deserialize(attempt.getPluginProperties());
             final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), internalCallContext);
             final UUID tenantId = nonEntityDao.retrieveIdFromObject(internalCallContext.getTenantRecordId(), ObjectType.TENANT);
             final CallContext callContext = internalCallContext.toCallContext(tenantId);
 
-
             final State state = pluginControlledDirectPaymentAutomatonRunner.fetchState(attempt.getStateName());
             pluginControlledDirectPaymentAutomatonRunner.run(state,
-                                                      false,
-                                                      attempt.getTransactionType(),
-                                                      account,
-                                                      attempt.getPaymentMethodId(),
-                                                      paymentId,
-                                                      attempt.getPaymentExternalKey(),
-                                                      attempt.getTransactionExternalKey(),
-                                                      attempt.getAmount(),
-                                                      attempt.getCurrency(),
-                                                      pluginProperties,
-                                                      pluginName,
-                                                      callContext,
-                                                      internalCallContext);
+                                                             false,
+                                                             attempt.getTransactionType(),
+                                                             account,
+                                                             attempt.getPaymentMethodId(),
+                                                             paymentId,
+                                                             attempt.getPaymentExternalKey(),
+                                                             attempt.getTransactionExternalKey(),
+                                                             attempt.getAmount(),
+                                                             attempt.getCurrency(),
+                                                             pluginProperties,
+                                                             pluginName,
+                                                             callContext,
+                                                             internalCallContext);
 
         } catch (AccountApiException e) {
-            e.printStackTrace();
+            log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
         } catch (PaymentApiException e) {
-            e.printStackTrace();
+            log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
+        } catch (PluginPropertySerializerException e) {
+            log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
         }
-
     }
-
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
index 47a8ba0..23d619d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
@@ -80,7 +80,7 @@ public abstract class ProcessorBase {
     protected final TagInternalApi tagInternalApi;
     protected final Clock clock;
 
-    private static final Logger log = LoggerFactory.getLogger(ProcessorBase.class);
+    protected static final Logger log = LoggerFactory.getLogger(ProcessorBase.class);
     protected final InvoiceInternalApi invoiceApi;
 
     public ProcessorBase(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
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 2c1d56b..cb7e6c2 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
@@ -16,22 +16,18 @@
 
 package org.killbill.billing.payment.core.sm;
 
-import java.util.List;
-
 import org.joda.time.DateTime;
+import org.killbill.automaton.OperationException;
 import org.killbill.automaton.State;
 import org.killbill.automaton.State.LeavingStateCallback;
-import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.dao.PaymentModelDao;
 import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.dao.PaymentDao;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
 
-import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 
 public class RetryLeavingStateCallback implements LeavingStateCallback {
 
@@ -53,7 +49,7 @@ public class RetryLeavingStateCallback implements LeavingStateCallback {
     }
 
     @Override
-    public void leavingState(final State state) {
+    public void leavingState(final State state) throws OperationException {
 
         final DateTime utcNow = retryableDirectPaymentAutomatonRunner.clock.getUTCNow();
 
@@ -69,23 +65,24 @@ 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, initialState.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(attempt.getId(), stateContext.getDirectPaymentExternalKey(), stateContext.directPaymentTransactionExternalKey, stateContext.getAccount().getId(),
-                                                      stateContext.getPluginName(), input.getKey(), value, stateContext.getCallContext().getUserName(), stateContext.getCallContext().getCreatedDate());
-                }
-            }));
-            retryableDirectPaymentAutomatonRunner.paymentDao.insertPaymentAttemptWithProperties(attempt, properties, stateContext.internalCallContext);
-            stateContext.setAttemptId(attempt.getId());
+            try {
+                final byte [] serializedProperties = PluginPropertySerializer.serialize(stateContext.getProperties());
+
+
+                final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(stateContext.getAccount().getId(), stateContext.getPaymentMethodId(),
+                                                                                  utcNow, utcNow, stateContext.getDirectPaymentExternalKey(), null,
+                                                                                  stateContext.directPaymentTransactionExternalKey, transactionType, initialState.getName(),
+                                                                                  stateContext.getAmount(), stateContext.getCurrency(),
+                                                                                  stateContext.getPluginName(), serializedProperties);
+
+                retryableDirectPaymentAutomatonRunner.paymentDao.insertPaymentAttemptWithProperties(attempt, stateContext.internalCallContext);
+                stateContext.setAttemptId(attempt.getId());
+
+            } catch (PluginPropertySerializerException e) {
+                // STEPH
+                throw new OperationException(e);
+            }
+
         }
     }
 }
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 8c65ac1..9197bb5 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,18 +72,7 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    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(attemptId.toString());
-            }
-        });
-    }
-
-    @Override
-    public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final List<PluginPropertyModelDao> properties, final InternalCallContext context) {
+    public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final InternalCallContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentAttemptModelDao>() {
 
             @Override
@@ -91,10 +80,6 @@ public class DefaultPaymentDao implements PaymentDao {
                 final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
                 transactional.create(attempt, context);
                 final PaymentAttemptModelDao result = transactional.getById(attempt.getId().toString(), context);
-
-                // Those calls are not part of history and audit on purpose, this is just to implement a temporary property cache cache
-                transactional.become(PluginPropertySqlDao.class).batchCreateFromTransaction(properties);
-
                 return result;
             }
         });
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
index 82bba44..815a6d2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
@@ -17,6 +17,7 @@
 package org.killbill.billing.payment.dao;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -41,12 +42,13 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
     private BigDecimal amount;
     private Currency currency;
     private String pluginName;
+    private byte [] pluginProperties;
 
     public PaymentAttemptModelDao() { /* For the DAO mapper */ }
 
     public PaymentAttemptModelDao(final UUID accountId, final UUID paymentMethodId, final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
                                   final String paymentExternalKey, final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType,
-                                  final String stateName, final BigDecimal amount, final Currency currency, final String pluginName) {
+                                  final String stateName, final BigDecimal amount, final Currency currency, final String pluginName, final byte [] pluginProperties) {
         super(id, createdDate, updatedDate);
         this.accountId = accountId;
         this.paymentMethodId = paymentMethodId;
@@ -58,13 +60,14 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
         this.amount = amount;
         this.currency = currency;
         this.pluginName = pluginName;
+        this.pluginProperties = pluginProperties;
     }
 
     public PaymentAttemptModelDao(final UUID accountId, final UUID paymentMethodId, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
                                   final String paymentExternalKey, final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType, final String stateName,
-                                  final BigDecimal amount, final Currency currency, final String pluginName) {
+                                  final BigDecimal amount, final Currency currency, final String pluginName,  final byte [] pluginProperties) {
         this(accountId, paymentMethodId, UUID.randomUUID(), createdDate, updatedDate, paymentExternalKey, transactionId, transactionExternalKey, transactionType, stateName,
-             amount, currency, pluginName);
+             amount, currency, pluginName, pluginProperties);
     }
 
     public String getPaymentExternalKey() {
@@ -107,6 +110,14 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
         this.pluginName = pluginName;
     }
 
+    public byte [] getPluginProperties() {
+        return pluginProperties;
+    }
+
+    public void setPluginProperties(final byte [] pluginProperties) {
+        this.pluginProperties = pluginProperties;
+    }
+
     public UUID getAccountId() {
         return accountId;
     }
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 cf0a59e..a41d336 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
@@ -28,10 +28,7 @@ import org.killbill.billing.util.entity.Pagination;
 
 public interface PaymentDao {
 
-
-    public List<PluginPropertyModelDao> getProperties(UUID attenptId, InternalCallContext context);
-
-    public PaymentAttemptModelDao insertPaymentAttemptWithProperties(PaymentAttemptModelDao attempt, List<PluginPropertyModelDao> properties, InternalCallContext context);
+    public PaymentAttemptModelDao insertPaymentAttemptWithProperties(PaymentAttemptModelDao attempt, InternalCallContext context);
 
     public void updatePaymentAttempt(UUID paymentAttemptId, UUID transactionId, String state, InternalCallContext context);
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySerializer.java b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySerializer.java
new file mode 100644
index 0000000..6ddd3bc
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySerializer.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.dao;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.killbill.billing.payment.api.PluginProperty;
+
+import com.ning.compress.lzf.LZFDecoder;
+import com.ning.compress.lzf.LZFEncoder;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class PluginPropertySerializer {
+
+    private static final int MAX_SIZE_PROPERTIES_BYTES = (8 * 1024); // As defined in payment_attempt ddl
+
+    private static final JsonFactory jsonFactory = new JsonFactory();
+    private static ObjectMapper mapper = new ObjectMapper(jsonFactory);
+
+    static {
+        mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
+    }
+
+    public static byte[] serialize(final Iterable<PluginProperty> input) throws PluginPropertySerializerException {
+
+        final ByteArrayOutputStream out = new ByteArrayOutputStream(MAX_SIZE_PROPERTIES_BYTES);
+        try {
+            final JsonGenerator jsonGenerator = jsonFactory.createGenerator(out, JsonEncoding.UTF8);
+            jsonGenerator.writeStartArray();
+            for (final PluginProperty cur : input) {
+                final String key = cur.getKey();
+                final Object value = cur.getValue();
+                jsonGenerator.writeStartObject();
+                jsonGenerator.writeFieldName(key);
+                mapper.writeValue(jsonGenerator, value);
+                jsonGenerator.writeEndObject();
+            }
+            jsonGenerator.writeEndArray();
+            jsonGenerator.close();
+            final byte[] data = out.toByteArray();
+            return LZFEncoder.encode(data);
+        } catch (final IOException e) {
+            throw new PluginPropertySerializerException(e);
+        }
+    }
+
+    public static Iterable<PluginProperty> deserialize(final byte[] input) throws PluginPropertySerializerException {
+
+        final List<PluginProperty> result = new ArrayList<PluginProperty>();
+        try {
+            final byte[] uncompressed = LZFDecoder.decode(input);
+            final InputStream in = new ByteArrayInputStream(uncompressed);
+            final JsonParser jsonParser = jsonFactory.createParser(in);
+
+            PluginProperty prop = null;
+            String key = null;
+            JsonToken nextToken = jsonParser.nextToken();
+            while (nextToken != null && nextToken != JsonToken.END_ARRAY) {
+                if (nextToken != JsonToken.START_ARRAY) {
+                    if (nextToken == JsonToken.FIELD_NAME && key == null) {
+                        key = jsonParser.getText();
+                    } else if (key != null) {
+                        final Object value = mapper.readValue(jsonParser, Object.class);
+                        prop = new PluginProperty(key, value, false);
+                        key = null;
+                    } else if (nextToken == JsonToken.END_OBJECT) {
+                        result.add(prop);
+                        prop = null;
+                    }
+                }
+                nextToken = jsonParser.nextToken();
+            }
+            jsonParser.close();
+            return result;
+        } catch (final UnsupportedEncodingException e) {
+            throw new PluginPropertySerializerException(e);
+        } catch (final JsonParseException e) {
+            throw new PluginPropertySerializerException(e);
+        } catch (final IOException e) {
+            throw new PluginPropertySerializerException(e);
+        }
+    }
+
+    public static class PluginPropertySerializerException extends Exception {
+
+        public PluginPropertySerializerException() {
+        }
+
+        public PluginPropertySerializerException(final String message) {
+            super(message);
+        }
+
+        public PluginPropertySerializerException(final Throwable cause) {
+            super(cause);
+        }
+
+        public PluginPropertySerializerException(final String message, final Throwable cause) {
+            super(message, cause);
+        }
+    }
+}
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 3060330..1c4083b 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
@@ -17,6 +17,7 @@ tableFields(prefix) ::= <<
 , <prefix>amount
 , <prefix>currency
 , <prefix>plugin_name
+, <prefix>plugin_properties
 , <prefix>created_by
 , <prefix>created_date
 , <prefix>updated_by
@@ -34,6 +35,7 @@ tableValues() ::= <<
 , :amount
 , :currency
 , :pluginName
+, :pluginProperties
 , :createdBy
 , :createdDate
 , :updatedBy
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 afa8c33..0d6a797 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
+++ b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
@@ -15,6 +15,7 @@ CREATE TABLE payment_attempts (
     amount numeric(15,9),
     currency char(3),
     plugin_name varchar(50) NOT NULL,
+    plugin_properties blob(8194),
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
@@ -44,6 +45,7 @@ CREATE TABLE payment_attempt_history (
     amount numeric(15,9),
     currency char(3),
     plugin_name varchar(50) NOT NULL,
+    plugin_properties blob(8194),
     change_type char(6) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
@@ -201,22 +203,6 @@ CREATE TABLE transaction_history (
 CREATE INDEX transaction_history_target_record_id ON transaction_history(target_record_id);
 CREATE INDEX transaction_history_tenant_account_record_id ON transaction_history(tenant_record_id, account_record_id);
 
-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,
-    plugin_name varchar(50) DEFAULT NULL,
-    prop_key varchar(255),
-    prop_value varchar(255),
-    created_by varchar(50) NOT NULL,
-    created_date datetime NOT NULL,
-    PRIMARY KEY (record_id)
-) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-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;
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 f053e8f..3c9d8d1 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
@@ -21,7 +21,6 @@ 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;
@@ -46,7 +45,7 @@ import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
 import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
 import org.killbill.billing.payment.glue.PaymentModule;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.provider.MockPaymentControlProviderPlugin;
@@ -114,6 +113,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
     private final ImmutableList<PluginProperty> emptyProperties = ImmutableList.of();
     private final MockPaymentControlProviderPlugin mockRetryProviderPlugin = new MockPaymentControlProviderPlugin();
 
+    private byte [] EMPTY_PROPERTIES;
     private MockRetryableDirectPaymentAutomatonRunner runner;
     private RetryableDirectPaymentStateContext directPaymentStateContext;
     private MockRetryAuthorizeOperationCallback mockRetryAuthorizeOperationCallback;
@@ -136,6 +136,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
                 return MockPaymentControlProviderPlugin.PLUGIN_NAME;
             }
         }, mockRetryProviderPlugin);
+        EMPTY_PROPERTIES = PluginPropertySerializer.serialize(ImmutableList.<PluginProperty>of());
     }
 
     @BeforeMethod(groups = "fast")
@@ -464,8 +465,8 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
         final UUID directTransactionId = UUID.randomUUID();
         paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
                                                                                  directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
-                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null),
-                                                      ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES),
+                                                      internalCallContext
                                                      );
         runner.run(state,
                    false,
@@ -512,8 +513,8 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
         final UUID directTransactionId = UUID.randomUUID();
         paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
                                                                                  directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
-                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null),
-                                                      ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES),
+                                                      internalCallContext
                                                      );
 
         try {
@@ -559,8 +560,8 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
         final UUID directTransactionId = UUID.randomUUID();
         paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
                                                                                  directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
-                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null),
-                                                      ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES),
+                                                      internalCallContext
                                                      );
 
         try {
@@ -615,9 +616,9 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
         final UUID directPaymentId = UUID.randomUUID();
         final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
                                                                     directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
-                                                                    TransactionType.AUTHORIZE, state.getName(), amount, currency, null);
+                                                                    TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES);
         paymentDao.insertPaymentAttemptWithProperties(attempt,
-                                                      ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+                                                      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"),
@@ -657,9 +658,9 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
         final UUID directPaymentId = UUID.randomUUID();
         final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
                                                                     directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
-                                                                    TransactionType.AUTHORIZE, state.getName(), amount, currency, null);
+                                                                    TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES);
         paymentDao.insertPaymentAttemptWithProperties(attempt,
-                                                      ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+                                                      internalCallContext
                                                      );
         paymentDao.insertDirectPaymentWithFirstTransaction(new PaymentModelDao(directPaymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, directPaymentExternalKey),
                                                            new PaymentTransactionModelDao(directTransactionId, directPaymentTransactionExternalKey, utcNow, utcNow, directPaymentId, TransactionType.AUTHORIZE, utcNow,
@@ -705,9 +706,9 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
             final UUID directPaymentId = UUID.randomUUID();
             final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
                                                                         directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
-                                                                        TransactionType.AUTHORIZE, state.getName(), amount, currency, null);
+                                                                        TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES);
             paymentDao.insertPaymentAttemptWithProperties(attempt,
-                                                          ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+                                                          internalCallContext
                                                          );
             paymentDao.insertDirectPaymentWithFirstTransaction(new PaymentModelDao(directPaymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, directPaymentExternalKey),
                                                                new PaymentTransactionModelDao(directTransactionId, directPaymentTransactionExternalKey, utcNow, utcNow, directPaymentId, TransactionType.AUTHORIZE, utcNow,
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 ba4f6cd..ae583ad 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
@@ -40,27 +40,19 @@ 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<UUID, PaymentAttemptModelDao> attempts = new HashMap<UUID, PaymentAttemptModelDao>();
-    private final List<PluginPropertyModelDao> properties = new ArrayList<PluginPropertyModelDao>();
 
     public void reset() {
         synchronized (this) {
             payments.clear();
             transactions.clear();
             attempts.clear();
-            properties.clear();
         }
     }
 
     @Override
-    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) {
+    public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final InternalCallContext context) {
         synchronized (this) {
             attempts.put(attempt.getId(), attempt);
-            this.properties.addAll(properties);
             return attempt;
         }
     }
@@ -157,7 +149,7 @@ public class MockPaymentDao implements PaymentDao {
     }
 
     @Override
-    public void updateDirectPaymentAndTransactionOnCompletion(final UUID directPaymentId, final String currentPaymentStateName,  final String lastSuccessPaymentStateName, final UUID directTransactionId, final TransactionStatus paymentStatus, final BigDecimal processedAmount, final Currency processedCurrency, final String gatewayErrorCode, final String gatewayErrorMsg, final InternalCallContext context) {
+    public void updateDirectPaymentAndTransactionOnCompletion(final UUID directPaymentId, final String currentPaymentStateName, final String lastSuccessPaymentStateName, final UUID directTransactionId, final TransactionStatus paymentStatus, final BigDecimal processedAmount, final Currency processedCurrency, final String gatewayErrorCode, final String gatewayErrorMsg, final InternalCallContext context) {
         synchronized (this) {
             final PaymentModelDao payment = payments.get(directPaymentId);
             if (payment != null) {
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 c3a87e6..8077815 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
@@ -24,8 +24,11 @@ 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.api.PluginProperty;
 import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
@@ -36,7 +39,7 @@ import static org.testng.Assert.assertNull;
 public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
-    public void testPaymentAttempt() {
+    public void testPaymentAttempt() throws PluginPropertySerializerException {
         final UUID directTransactionId = UUID.randomUUID();
         final String paymentExternalKey = "vraiment?";
         final String transactionExternalKey = "tduteuqweq";
@@ -46,43 +49,29 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
 
         final UUID accountId = UUID.randomUUID();
 
-        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 List<PluginProperty> properties = new ArrayList<PluginProperty>();
+        properties.add(new PluginProperty("key1", "value1", false));
+        properties.add(new PluginProperty("key2", "value2", false));
+
+        final byte [] serialized = PluginPropertySerializer.serialize(properties);
+        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(UUID.randomUUID(), UUID.randomUUID(), clock.getUTCNow(), clock.getUTCNow(),
+                                                                          paymentExternalKey, directTransactionId, transactionExternalKey, transactionType, stateName,
+                                                                          BigDecimal.ZERO, Currency.ALL, pluginName, serialized);
 
 
-        PaymentAttemptModelDao savedAttempt = paymentDao.insertPaymentAttemptWithProperties(attempt, props, internalCallContext);
+        PaymentAttemptModelDao savedAttempt = paymentDao.insertPaymentAttemptWithProperties(attempt, internalCallContext);
         assertEquals(savedAttempt.getTransactionExternalKey(), transactionExternalKey);
         assertEquals(savedAttempt.getTransactionType(), transactionType);
         assertEquals(savedAttempt.getStateName(), stateName);
         assertEquals(savedAttempt.getPluginName(), pluginName);
 
-        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);
-        assertEquals(retrievedProperties.get(0).getPluginName(), "PLUGIN");
-        assertEquals(retrievedProperties.get(0).getPaymentExternalKey(), "foo");
-        assertEquals(retrievedProperties.get(0).getPropKey(), "key1");
-        assertEquals(retrievedProperties.get(0).getPropValue(), "value1");
-        assertEquals(retrievedProperties.get(0).getCreatedBy(), "yo");
-
-        assertEquals(retrievedProperties.get(1).getAccountId(), accountId);
-        assertEquals(retrievedProperties.get(1).getTransactionExternalKey(), transactionExternalKey);
-        assertEquals(retrievedProperties.get(1).getPluginName(), "PLUGIN");
-        assertEquals(retrievedProperties.get(1).getPaymentExternalKey(), "foo2");
-        assertEquals(retrievedProperties.get(1).getPropKey(), "key2");
-        assertEquals(retrievedProperties.get(1).getPropValue(), "value2");
-        assertEquals(retrievedProperties.get(1).getCreatedBy(), "yo");
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(savedAttempt.getPluginProperties());
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            Assert.assertEquals(cur, properties.get(i++));
+        }
 
         final PaymentAttemptModelDao retrievedAttempt1 = paymentDao.getPaymentAttempt(attempt.getId(), internalCallContext);
         assertEquals(retrievedAttempt1.getTransactionExternalKey(), transactionExternalKey);
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPluginPropertySerializer.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPluginPropertySerializer.java
new file mode 100644
index 0000000..096936e
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPluginPropertySerializer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.dao;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestPluginPropertySerializer {
+
+    @Test(groups = "fast")
+    public void testNoPluginProperty() throws PluginPropertySerializerException {
+        final List<PluginProperty> input = new ArrayList<PluginProperty>();
+
+        final byte[] serialized = PluginPropertySerializer.serialize(input);
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            Assert.assertEquals(cur, input.get(i++));
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testSimplePluginProperty() throws PluginPropertySerializerException {
+        final List<PluginProperty> input = new ArrayList<PluginProperty>();
+        input.add(new PluginProperty("foo", "bar", false));
+
+        final byte[] serialized = PluginPropertySerializer.serialize(input);
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            Assert.assertEquals(cur, input.get(i++));
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testLotsPluginProperty() throws PluginPropertySerializerException {
+        final List<PluginProperty> input = new ArrayList<PluginProperty>();
+        for (int i = 0; i < 100; i++) {
+            input.add(new PluginProperty("foo-" + i, "bar-" + i, false));
+        }
+
+        final byte[] serialized = PluginPropertySerializer.serialize(input);
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            Assert.assertEquals(cur, input.get(i++));
+        }
+    }
+
+    @Test(groups = "fast", enabled = true)
+    public void testPluginPropertyWithComplexValue() throws PluginPropertySerializerException {
+        final HashMap<String, BigDecimal> something = new HashMap<String, BigDecimal>();
+        something.put("yoyo", new BigDecimal("0.0"));
+        something.put("what", new BigDecimal("10.0"));
+        final List<PluginProperty> input = new ArrayList<PluginProperty>();
+        input.add(new PluginProperty("prev", "simple", false));
+        input.add(new PluginProperty("foo", something, false));
+        input.add(new PluginProperty("next", "easy", false));
+
+        final byte[] serialized = PluginPropertySerializer.serialize(input);
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            if (i == 0 || i == 2) {
+                Assert.assertEquals(cur, input.get(i));
+            } else {
+                Assert.assertEquals(cur.getKey(), "foo");
+                Assert.assertTrue(cur.getValue() instanceof Map);
+                final Map<String, BigDecimal> mappedValue = (Map<String, BigDecimal>) cur.getValue();
+                Assert.assertTrue(mappedValue.containsKey("yoyo"));
+                Assert.assertTrue(mappedValue.containsKey("what"));
+                Assert.assertTrue(mappedValue.get("yoyo").compareTo(BigDecimal.ZERO) == 0);
+                Assert.assertTrue(mappedValue.get("what").compareTo(BigDecimal.TEN) == 0);
+            }
+            i++;
+        }
+
+    }
+}