killbill-memoizeit

Added PaymentDao

1/10/2012 6:33:57 PM

Details

diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
index fb07745..c37ee01 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -20,6 +20,8 @@ import java.util.List;
 
 import javax.annotation.Nullable;
 
+import com.ning.billing.account.api.Account;
+
 public interface PaymentApi {
     Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId);
 
@@ -34,6 +36,7 @@ public interface PaymentApi {
     Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
 
     List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds);
+    List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds);
 
     Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
 
@@ -41,5 +44,6 @@ public interface PaymentApi {
 
     Either<PaymentError, PaymentProviderAccount> updatePaymentProviderAccount(PaymentProviderAccount account);
 
+    PaymentAttempt getPaymentAttemptForPaymentId(String id);
 
 }
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
index c4b6bfd..58016e0 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
@@ -25,6 +25,7 @@ import com.ning.billing.invoice.api.Invoice;
 
 public class PaymentAttempt {
     private final UUID paymentAttemptId;
+    private final String paymentId;
     private final DateTime paymentAttemptDate;
     private final Invoice invoice;
 
@@ -32,12 +33,28 @@ public class PaymentAttempt {
         this.paymentAttemptId = paymentAttemptId;
         this.paymentAttemptDate = new DateTime(DateTimeZone.UTC);
         this.invoice = invoice;
+        this.paymentId = null;
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice, String paymentId) {
+        this(paymentAttemptId, invoice, paymentId, null);
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice, String paymentId, DateTime paymentAttemptDate) {
+        this.paymentAttemptId = paymentAttemptId;
+        this.paymentAttemptDate = paymentAttemptDate == null ? new DateTime(DateTimeZone.UTC) : paymentAttemptDate;
+        this.invoice = invoice;
+        this.paymentId = paymentId;
     }
 
     public UUID getPaymentAttemptId() {
         return paymentAttemptId;
     }
 
+    public String getPaymentId() {
+        return paymentId;
+    }
+
     public DateTime getPaymentAttemptDate() {
         return paymentAttemptDate;
     }
@@ -48,7 +65,7 @@ public class PaymentAttempt {
 
     @Override
     public String toString() {
-        return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", paymentAttemptDate=" + paymentAttemptDate + ", invoice=" + invoice + "]";
+        return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", paymentId=" + paymentId + ", paymentAttemptDate=" + paymentAttemptDate + ", invoice=" + invoice + "]";
     }
 
 }
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
index 897ba89..1ccd255 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
@@ -71,13 +71,13 @@ public class PaymentInfo implements EventBusNotification {
         this.amount = amount;
         this.appliedCreditBalanceAmount = appliedCreditBalanceAmount;
         this.bankIdentificationNumber = bankIdentificationNumber;
-        this.createdDate = createdDate;
         this.effectiveDate = effectiveDate;
         this.paymentNumber = paymentNumber;
         this.referenceId = referenceId;
         this.refundAmount = refundAmount;
         this.status = status;
         this.type = type;
+        this.createdDate = createdDate;
         this.updatedDate = updatedDate;
     }
 
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index 33e963d..943d04f 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.payment.api;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
@@ -27,6 +28,7 @@ import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.PaymentProviderPlugin;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
 
@@ -34,14 +36,17 @@ public class DefaultPaymentApi implements PaymentApi {
     private final PaymentProviderPluginRegistry pluginRegistry;
     private final AccountUserApi accountUserApi;
     private final InvoicePaymentApi invoicePaymentApi;
+    private final PaymentDao paymentDao;
 
     @Inject
     public DefaultPaymentApi(PaymentProviderPluginRegistry pluginRegistry,
                              AccountUserApi accountUserApi,
-                             InvoicePaymentApi invoicePaymentApi) {
+                             InvoicePaymentApi invoicePaymentApi,
+                             PaymentDao paymentDao) {
         this.pluginRegistry = pluginRegistry;
         this.accountUserApi = accountUserApi;
         this.invoicePaymentApi = invoicePaymentApi;
+        this.paymentDao = paymentDao;
     }
 
     @Override
@@ -55,9 +60,17 @@ public class DefaultPaymentApi implements PaymentApi {
 
         if (accountKey != null) {
             final Account account = accountUserApi.getAccountByKey(accountKey);
-            if (account != null) {
-                paymentProviderName = account.getPaymentProviderName();
-            }
+            return getPaymentProviderPlugin(account);
+        }
+
+        return pluginRegistry.getPlugin(paymentProviderName);
+    }
+
+    private PaymentProviderPlugin getPaymentProviderPlugin(Account account) {
+        String paymentProviderName = null;
+
+        if (account != null) {
+            paymentProviderName = account.getPaymentProviderName();
         }
 
         return pluginRegistry.getPlugin(paymentProviderName);
@@ -101,14 +114,45 @@ public class DefaultPaymentApi implements PaymentApi {
 
     @Override
     public List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds) {
-        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
         final Account account = accountUserApi.getAccountByKey(accountKey);
+        return createPayment(account, invoiceIds);
+    }
+
+    @Override
+    public List<Either<PaymentError, PaymentInfo>> createPayment(Account account, List<String> invoiceIds) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(account);
+
         List<Either<PaymentError, PaymentInfo>> processedPaymentsOrErrors = new ArrayList<Either<PaymentError, PaymentInfo>>(invoiceIds.size());
 
         for (String invoiceId : invoiceIds) {
             Invoice invoice = invoicePaymentApi.getInvoice(UUID.fromString(invoiceId));
-            Either<PaymentError, PaymentInfo> paymentOrError = plugin.processInvoice(account, invoice);
-            processedPaymentsOrErrors.add(paymentOrError);
+
+            if (invoice.getAmountOutstanding().compareTo(BigDecimal.ZERO) == 0 ) {
+            // TODO: send a notification that invoice was ignored ?
+            }
+            else {
+                PaymentAttempt paymentAttempt = paymentDao.createPaymentAttempt(invoice);
+                Either<PaymentError, PaymentInfo> paymentOrError = plugin.processInvoice(account, invoice);
+                processedPaymentsOrErrors.add(paymentOrError);
+
+                if (paymentOrError.isRight()) {
+                    PaymentInfo info = paymentOrError.getRight();
+
+                    paymentDao.savePaymentInfo(info);
+                    paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getPaymentAttemptId(), info.getId());
+
+                    invoicePaymentApi.paymentSuccessful(invoice.getId(),
+                                                        invoice.getAmountOutstanding(),
+                                                        invoice.getCurrency(),
+                                                        paymentAttempt.getPaymentAttemptId(),
+                                                        paymentAttempt.getPaymentAttemptDate());
+                }
+                else {
+                    invoicePaymentApi.paymentFailed(invoice.getId(),
+                                                    paymentAttempt.getPaymentAttemptId(),
+                                                    paymentAttempt.getPaymentAttemptDate());
+                }
+            }
         }
 
         return processedPaymentsOrErrors;
@@ -116,7 +160,7 @@ public class DefaultPaymentApi implements PaymentApi {
 
     @Override
     public Either<PaymentError, PaymentProviderAccount> createPaymentProviderAccount(PaymentProviderAccount account) {
-        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(null);
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin((Account)null);
         return plugin.createPaymentProviderAccount(account);
     }
 
@@ -125,4 +169,10 @@ public class DefaultPaymentApi implements PaymentApi {
         // TODO Auto-generated method stub
         return null;
     }
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForPaymentId(String id) {
+        return paymentDao.getPaymentAttemptForPaymentId(id);
+    }
+
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
new file mode 100644
index 0000000..f14dee1
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.payment.dao;
+
+import java.util.UUID;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public class DefaultPaymentDao implements PaymentDao {
+    private final PaymentSqlDao sqlDao;
+
+    @Inject
+    public DefaultPaymentDao(IDBI dbi) {
+        this.sqlDao = dbi.onDemand(PaymentSqlDao.class);
+    }
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
+        return sqlDao.getPaymentAttemptForPaymentId(paymentId);
+    }
+
+    @Override
+    public PaymentAttempt createPaymentAttempt(Invoice invoice) {
+        final PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+
+        sqlDao.insertPaymentAttempt(paymentAttempt);
+        return paymentAttempt;
+    }
+
+    @Override
+    public void savePaymentInfo(PaymentInfo info) {
+        sqlDao.insertPaymentInfo(info);
+    }
+
+    @Override
+    public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String id) {
+        sqlDao.updatePaymentAttemptWithPaymentId(paymentAttemptId.toString(), id);
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
index f16adb0..db8e011 100644
--- a/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -16,6 +16,20 @@
 
 package com.ning.billing.payment.dao;
 
-public class PaymentDao {
+import java.util.UUID;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public interface PaymentDao {
+
+    PaymentAttempt createPaymentAttempt(Invoice invoice);
+
+    void savePaymentInfo(PaymentInfo right);
+
+    PaymentAttempt getPaymentAttemptForPaymentId(String paymentId);
+
+    void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId);
 
 }
diff --git a/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
new file mode 100644
index 0000000..8f38958
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.payment.dao;
+
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.skife.jdbi.v2.SQLStatement;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.Binder;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+import org.skife.jdbi.v2.tweak.ResultSetMapper;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+@ExternalizedSqlViaStringTemplate3()
+public interface PaymentSqlDao extends Transactional<PaymentSqlDao>, CloseMe, Transmogrifier {
+    @SqlUpdate
+    void insertPaymentAttempt(@Bind(binder = PaymentAttemptBinder.class) PaymentAttempt paymentAttempt);
+
+    @SqlQuery
+    @Mapper(PaymentAttemptMapper.class)
+    PaymentAttempt getPaymentAttemptForPaymentId(@Bind("paymentId") String paymentId);
+
+    @SqlUpdate
+    void updatePaymentAttemptWithPaymentId(String paymentAttemptId, String id);
+
+    @SqlUpdate
+    void insertPaymentInfo(@Bind(binder = PaymentInfoBinder.class) PaymentInfo paymentInfo);
+
+    public static final class PaymentAttemptBinder implements Binder<Bind, PaymentAttempt> {
+
+        private Date getDate(DateTime dateTime) {
+            return dateTime == null ? null : dateTime.toDate();
+        }
+
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentAttempt paymentAttempt) {
+            Invoice invoice = paymentAttempt.getInvoice();
+
+            stmt.bind("id", paymentAttempt.getPaymentAttemptId().toString());
+            stmt.bind("payment_id", paymentAttempt.getPaymentId());
+            stmt.bind("payment_attempt_dt", getDate(paymentAttempt.getPaymentAttemptDate()));
+            stmt.bind("invoice_id", invoice.getId().toString());
+            stmt.bind("account_id", invoice.getAccountId().toString());
+            stmt.bind("invoice_dt", getDate(invoice.getInvoiceDate()));
+            stmt.bind("target_dt", getDate(invoice.getTargetDate()));
+            stmt.bind("amount_paid", invoice.getAmountPaid());
+            stmt.bind("amount_outstanding", invoice.getAmountOutstanding());
+            stmt.bind("last_payment_attempt", getDate(invoice.getLastPaymentAttempt()));
+            stmt.bind("currency", invoice.getCurrency().toString());
+        }
+    }
+
+    public static class PaymentAttemptMapper implements ResultSetMapper<PaymentAttempt> {
+
+        private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+            final Timestamp resultStamp = rs.getTimestamp(fieldName);
+            return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+        }
+
+        @Override
+        public PaymentAttempt map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+
+            UUID paymentAttemptId = UUID.fromString(rs.getString("id"));
+            String paymentId = rs.getString("payment_id");
+            DateTime paymentAttemptDate = getDate(rs, "payment_attempt_dt");
+
+            UUID invoiceId = UUID.fromString(rs.getString("invoice_id"));
+            UUID accountId = UUID.fromString(rs.getString("account_id"));
+            DateTime invoiceDate = getDate(rs, "invoice_dt");
+            DateTime targetDate = getDate(rs, "target_dt");
+            Currency currency = Currency.valueOf(rs.getString("currency"));
+            DateTime lastPaymentAttempt = getDate(rs, "last_payment_attempt");
+            BigDecimal amountPaid = rs.getBigDecimal("amount_paid");
+
+            Invoice invoice = new DefaultInvoice(invoiceId, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid);
+
+            return new PaymentAttempt(paymentAttemptId, invoice, paymentId, paymentAttemptDate);
+        }
+    }
+
+    public static final class PaymentInfoBinder implements Binder<Bind, PaymentInfo> {
+
+        private Date getDate(DateTime dateTime) {
+            return dateTime == null ? null : dateTime.toDate();
+        }
+
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, PaymentInfo paymentInfo) {
+            stmt.bind("id", paymentInfo.getId().toString());
+            stmt.bind("amount", paymentInfo.getAmount());
+            stmt.bind("refund_amount", paymentInfo.getRefundAmount());
+            stmt.bind("applied_credit_balance_amount", paymentInfo.getAppliedCreditBalanceAmount());
+            stmt.bind("payment_number", paymentInfo.getPaymentNumber());
+            stmt.bind("bank_identification_number", paymentInfo.getBankIdentificationNumber());
+            stmt.bind("status", paymentInfo.getStatus());
+            stmt.bind("type", paymentInfo.getType());
+            stmt.bind("reference_id", paymentInfo.getReferenceId());
+            stmt.bind("effective_dt", getDate(paymentInfo.getEffectiveDate()));
+            stmt.bind("created_dt", getDate(paymentInfo.getCreatedDate()));
+            stmt.bind("updated_dt", getDate(paymentInfo.getUpdatedDate()));
+        }
+    }
+
+    public static class PaymentInfoMapper implements ResultSetMapper<PaymentInfo> {
+
+        private DateTime getDate(ResultSet rs, String fieldName) throws SQLException {
+            final Timestamp resultStamp = rs.getTimestamp(fieldName);
+            return rs.wasNull() ? null : new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+        }
+
+        @Override
+        public PaymentInfo map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+
+            String id = rs.getString("id");
+            BigDecimal amount = rs.getBigDecimal("amount");
+            BigDecimal refundAmount = rs.getBigDecimal("refund_amount");
+            BigDecimal appliedCreditBalanceAmount = rs.getBigDecimal("applied_credit_balance_amount");
+            String paymentNumber = rs.getString("payment_number");
+            String bankIdentificationNumber = rs.getString("bank_identification_number");
+            String status = rs.getString("status");
+            String type = rs.getString("type");
+            String referenceId = rs.getString("reference_id");
+            DateTime effectiveDate = getDate(rs, "effective_dt");
+            DateTime createdDate = getDate(rs, "created_dt");
+            DateTime updatedDate = getDate(rs, "updated_dt");
+
+            return new PaymentInfo(id,
+                                   amount,
+                                   appliedCreditBalanceAmount,
+                                   bankIdentificationNumber,
+                                   paymentNumber,
+                                   refundAmount,
+                                   status,
+                                   type,
+                                   referenceId,
+                                   effectiveDate,
+                                   createdDate,
+                                   updatedDate);
+        }
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
index 5b671bc..39a3d67 100644
--- a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
@@ -16,18 +16,16 @@
 
 package com.ning.billing.payment;
 
-import java.math.BigDecimal;
-import java.util.UUID;
+import java.util.Arrays;
+import java.util.List;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceCreationNotification;
-import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.payment.api.Either;
-import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentError;
 import com.ning.billing.payment.api.PaymentInfo;
 import com.ning.billing.payment.provider.PaymentProviderPlugin;
@@ -37,18 +35,18 @@ import com.ning.billing.util.eventbus.EventBus.EventBusException;
 
 public class RequestProcessor {
     public static final String PAYMENT_PROVIDER_KEY = "paymentProvider";
-    private final InvoicePaymentApi invoiceApi;
     private final AccountUserApi accountUserApi;
+    private final PaymentApi paymentApi;
     private final PaymentProviderPluginRegistry pluginRegistry;
     private final EventBus eventBus;
 
     @Inject
     public RequestProcessor(AccountUserApi accountUserApi,
-                            InvoicePaymentApi invoiceApi,
+                            PaymentApi paymentApi,
                             PaymentProviderPluginRegistry pluginRegistry,
                             EventBus eventBus) {
-        this.invoiceApi = invoiceApi;
         this.accountUserApi = accountUserApi;
+        this.paymentApi = paymentApi;
         this.pluginRegistry = pluginRegistry;
         this.eventBus = eventBus;
     }
@@ -56,44 +54,20 @@ public class RequestProcessor {
     @Subscribe
     public void receiveInvoice(InvoiceCreationNotification event) {
         try {
-            final Invoice invoice = invoiceApi.getInvoice(event.getInvoiceId());
-            if (invoice == null) {
+            final Account account = accountUserApi.getAccountById(event.getAccountId());
+
+            if (account == null) {
                 // TODO: log a warning
             }
             else {
-                PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+                List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account, Arrays.asList(event.getInvoiceId().toString()));
 
-                if (invoice.getAmountOutstanding().compareTo(BigDecimal.ZERO) == 0 ) {
-                // TODO: send a notification that invoice was ignored ?
+                if (results.isEmpty()) {
+                    eventBus.post(new PaymentError("unknown", "No payment processed"));
                 }
                 else {
-                    final Account account = accountUserApi.getAccountById(event.getAccountId());
-
-                    if (account == null) {
-                        // TODO: log a warning
-                    }
-                    else {
-                        final BigDecimal paymentAmount = invoice.getAmountOutstanding();
-                        final String paymentProviderName = account.getPaymentProviderName();
-                        final PaymentProviderPlugin plugin = pluginRegistry.getPlugin(paymentProviderName);
-
-                        Either<PaymentError, PaymentInfo> result = plugin.processInvoice(account, invoice);
-
-                        if (result.isLeft()) {
-                            invoiceApi.paymentFailed(invoice.getId(),
-                                                     paymentAttempt.getPaymentAttemptId(),
-                                                     paymentAttempt.getPaymentAttemptDate());
-                        }
-                        else {
-                            updatePaymentAttemptWithPaymentInfoId(result.getRight().getId(), plugin);
-                            invoiceApi.paymentSuccessful(invoice.getId(),
-                                                         paymentAmount,
-                                                         invoice.getCurrency(),
-                                                         paymentAttempt.getPaymentAttemptId(),
-                                                         paymentAttempt.getPaymentAttemptDate());
-                        }
-                        eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
-                    }
+                    Either<PaymentError, PaymentInfo> result = results.get(0);
+                    eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
                 }
             }
         }
@@ -102,10 +76,6 @@ public class RequestProcessor {
         }
     }
 
-    private void updatePaymentAttemptWithPaymentInfoId(String id, PaymentProviderPlugin plugin) {
-        // TODO update PaymentAttempt with paymentId
-    }
-
     @Subscribe
     public void receivePaymentInfoRequest(PaymentInfoRequest paymentInfoRequest) throws EventBusException {
         final Account account = accountUserApi.getAccountById(paymentInfoRequest.getAccountId());
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
index 9c81c77..8b40409 100644
--- a/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.java
@@ -23,6 +23,8 @@ import org.skife.config.ConfigurationObjectFactory;
 import com.google.inject.AbstractModule;
 import com.ning.billing.payment.api.DefaultPaymentApi;
 import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
 
 public class PaymentModule extends AbstractModule {
@@ -42,11 +44,13 @@ public class PaymentModule extends AbstractModule {
     @Override
     protected void configure() {
         final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(props);
-        final PaymentConfig config = factory.build(PaymentConfig.class);
+        final PaymentConfig paymentConfig = factory.build(PaymentConfig.class);
 
-        bind(PaymentConfig.class).toInstance(config);
+        bind(PaymentConfig.class).toInstance(paymentConfig);
         bind(PaymentProviderPluginRegistry.class).asEagerSingleton();
-        bind(PaymentApi.class).to(DefaultPaymentApi.class);
-        installPaymentProviderPlugins(config);
+        bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
+        bind(MockPaymentDao.class).asEagerSingleton();
+        bind(PaymentDao.class).to(MockPaymentDao.class);
+        installPaymentProviderPlugins(paymentConfig);
     }
 }
diff --git a/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
new file mode 100644
index 0000000..553877e
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -0,0 +1,2 @@
+group PaymentSqlDao;
+
diff --git a/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
new file mode 100644
index 0000000..ca46ca3
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.payment.dao;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public class MockPaymentDao implements PaymentDao {
+    private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+    private final Map<UUID, PaymentAttempt> paymentAttempts = new ConcurrentHashMap<UUID, PaymentAttempt>();
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForPaymentId(String paymentId) {
+        for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
+            if (paymentId.equals(paymentAttempt.getPaymentId())) {
+                return paymentAttempt;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public PaymentAttempt createPaymentAttempt(Invoice invoice) {
+        PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+        paymentAttempts.put(paymentAttempt.getPaymentAttemptId(), paymentAttempt);
+        return paymentAttempt;
+    }
+
+    @Override
+    public void savePaymentInfo(PaymentInfo paymentInfo) {
+        payments.put(paymentInfo.getId(), paymentInfo);
+    }
+
+    @Override
+    public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId) {
+        PaymentAttempt existingPaymentAttempt = paymentAttempts.get(paymentAttemptId);
+        if (existingPaymentAttempt != null) {
+            paymentAttempts.put(existingPaymentAttempt.getPaymentAttemptId(),
+                                new PaymentAttempt(existingPaymentAttempt.getPaymentAttemptId(),
+                                                   existingPaymentAttempt.getInvoice(),
+                                                   paymentId));
+        }
+    }
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
index a02dfed..23ff1d3 100644
--- a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -71,7 +71,7 @@ public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
             PaymentProviderAccount paymentProviderAccount = accounts.put(account.getAccountName(),
                                                                          new PaymentProviderAccount.Builder().setAccountName(account.getAccountName())
                                                                                                              .setAccountNumber(account.getAccountName())
-                                                                                                             .setId(account.getId().toString())
+                                                                                                             .setId(account.getId())
                                                                                                              .build());
 
             return Either.right(paymentProviderAccount);
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
new file mode 100644
index 0000000..39a080c
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithEmbeddedDb.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.payment.setup;
+
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.collect.ImmutableMap;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.MemoryEventBus;
+
+public class PaymentTestModuleWithEmbeddedDb extends PaymentModule {
+    public PaymentTestModuleWithEmbeddedDb() {
+        super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock")));
+    }
+
+    @Override
+    protected void installPaymentProviderPlugins(PaymentConfig config) {
+        install(new MockPaymentProviderPluginModule("my-mock"));
+    }
+
+    @Override
+    protected void configure() {
+        super.configure();
+        bind(EventBus.class).to(MemoryEventBus.class).asEagerSingleton();
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
new file mode 100644
index 0000000..8ccfefd
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.payment;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.user.AccountBuilder;
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.glue.AccountModule;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.glue.InvoiceModule;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.setup.PaymentTestModuleWithEmbeddedDb;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.EventBus.EventBusException;
+
+public class TestPaymentInvoiceIntegration {
+    // create payment for received invoice and save it -- positive and negative
+    // check that notification for payment attempt is created
+    // check that invoice-payment is saved
+    @Inject
+    private EventBus eventBus;
+    @Inject
+    private RequestProcessor invoiceProcessor;
+    @Inject
+    protected AccountDao accountDao;
+    @Inject
+    protected InvoiceDao invoiceDao;
+    @Inject
+    protected InvoicePaymentApi invoicePaymentApi;
+    @Inject
+    protected PaymentApi paymentApi;
+
+    private MockPaymentInfoReceiver paymentInfoReceiver;
+
+    private IDBI dbi;
+    private MysqlTestingHelper helper;
+
+    @BeforeClass(alwaysRun = true)
+    public void startMysql() throws IOException {
+        final String accountddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+        final String invoiceddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+
+        helper = new MysqlTestingHelper();
+        helper.startMysql();
+        helper.initDb(accountddl + "\n" + invoiceddl);
+        dbi = helper.getDBI();
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void stopMysql() {
+        helper.stopMysql();
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        Injector injector = Guice.createInjector(new PaymentTestModuleWithEmbeddedDb(),
+                                                 new AccountModule(),
+                                                 new InvoiceModule(),
+                                                 new AbstractModule() {
+                                                    @Override
+                                                    protected void configure() {
+                                                        bind(IDBI.class).toInstance(dbi);
+                                                    }
+                                                });
+        injector.injectMembers(this);
+
+        paymentInfoReceiver = new MockPaymentInfoReceiver();
+
+        eventBus.start();
+        eventBus.register(invoiceProcessor);
+        eventBus.register(paymentInfoReceiver);
+
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() {
+        eventBus.stop();
+    }
+
+    @Test(enabled = false)
+    protected Account createTestAccount() {
+        final String name = "First" + RandomStringUtils.random(5) + " " + "Last" + RandomStringUtils.random(5);
+        final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
+                                                                     .firstNameLength(name.length())
+                                                                     .externalKey("12345")
+                                                                     .phone("123-456-7890")
+                                                                     .email("user@example.com")
+                                                                     .currency(Currency.USD)
+                                                                     .billingCycleDay(1)
+                                                                     .build();
+        accountDao.save(account);
+        return account;
+    }
+
+    @Test(enabled = false)
+    protected Invoice createTestInvoice(Account account,
+                                        DateTime targetDate,
+                                        Currency currency,
+                                        InvoiceItem... items) {
+        Invoice invoice = new DefaultInvoice(UUID.randomUUID(), account.getId(), new DateTime(), targetDate, currency, null, new BigDecimal("0"));
+
+        for (InvoiceItem item : items) {
+            invoice.add(new DefaultInvoiceItem(invoice.getId(),
+                                               item.getSubscriptionId(),
+                                               item.getStartDate(),
+                                               item.getEndDate(),
+                                               item.getDescription(),
+                                               item.getAmount(),
+                                               item.getRate(),
+                                               item.getCurrency()));
+        }
+        invoiceDao.save(invoice);
+        return invoice;
+    }
+
+    @Test
+    public void testInvoiceIntegration() throws Exception {
+        final DateTime now = new DateTime();
+        final Account account = createTestAccount();
+        final UUID subscriptionId = UUID.randomUUID();
+        final BigDecimal amount = new BigDecimal("10.00");
+        final InvoiceItem item = new DefaultInvoiceItem(null, subscriptionId, now, now.plusMonths(1), "Test", amount, new BigDecimal("1.0"), Currency.USD);
+        final Invoice invoice = createTestInvoice(account, now, Currency.USD, item);
+
+        await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                List<PaymentInfo> processedPayments = paymentInfoReceiver.getProcessedPayments();
+                List<PaymentError> errors = paymentInfoReceiver.getErrors();
+
+                return processedPayments.size() == 1 || errors.size() == 1;
+            }
+        });
+
+        assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
+        assertTrue(paymentInfoReceiver.getErrors().isEmpty());
+
+        List<PaymentInfo> payments = paymentInfoReceiver.getProcessedPayments();
+        PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(payments.get(0).getId());
+        Assert.assertNotNull(paymentAttempt);
+
+        invoicePaymentApi.getInvoicePayment(invoice.getId(), paymentAttempt.getPaymentAttemptId());
+    }
+}