killbill-aplcache

Merged with master

1/20/2012 4:22:21 PM

Changes

api/pom.xml 8(+8 -0)

payment/pom.xml 98(+96 -2)

pom.xml 82(+73 -9)

Details

diff --git a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
index 345e2a1..f6e51f4 100644
--- a/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
+++ b/account/src/main/java/com/ning/billing/account/glue/AccountModule.java
@@ -16,15 +16,15 @@
 
 package com.ning.billing.account.glue;
 
+import org.skife.config.ConfigurationObjectFactory;
+
 import com.google.inject.AbstractModule;
 import com.ning.billing.account.api.AccountService;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.account.api.DefaultAccountService;
 import com.ning.billing.account.api.user.DefaultAccountUserApi;
 import com.ning.billing.account.dao.AccountDao;
-import com.ning.billing.account.dao.AccountSqlDao;
 import com.ning.billing.account.dao.DefaultAccountDao;
-import org.skife.config.ConfigurationObjectFactory;
 
 public class AccountModule extends AbstractModule {
 
@@ -33,11 +33,11 @@ public class AccountModule extends AbstractModule {
         bind(AccountConfig.class).toInstance(config);
     }
 
-    private void installAccountDao() {
+    protected void installAccountDao() {
         bind(AccountDao.class).to(DefaultAccountDao.class).asEagerSingleton();
     }
 
-    private void installAccountUserApi() {
+    protected void installAccountUserApi() {
         bind(AccountUserApi.class).to(DefaultAccountUserApi.class).asEagerSingleton();
     }
 
diff --git a/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
new file mode 100644
index 0000000..335c529
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/api/MockAccountUserApi.java
@@ -0,0 +1,108 @@
+/*
+ * 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.account.api;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.Tag;
+
+public class MockAccountUserApi implements AccountUserApi {
+    private final CopyOnWriteArrayList<Account> accounts = new CopyOnWriteArrayList<Account>();
+
+    public Account createAccount(UUID id,
+                                 String externalKey,
+                                 String email,
+                                 String name,
+                                 int firstNameLength,
+                                 Currency currency,
+                                 int billCycleDay,
+                                 String paymentProviderName,
+                                 BigDecimal balance,
+                                 final DateTimeZone timeZone, 
+                                 final String locale,
+                                 final String address1, 
+                                 final String address2, 
+                                 final String companyName,
+                                 final String city,
+                                 final String stateOrProvince, 
+                                 final String country, 
+                                 final String postalCode, 
+                                 final String phone) {
+
+		Account result = new DefaultAccount(id, externalKey, email, name,
+				firstNameLength, currency, billCycleDay, paymentProviderName,
+				timeZone, locale, address1, address2, companyName, city,
+				stateOrProvince, country, postalCode, phone);
+		accounts.add(result);
+		return result;
+	}
+
+    @Override
+    public Account createAccount(AccountData data, List<CustomField> fields, List<Tag> tags) throws AccountApiException {
+        Account result = new DefaultAccount(data);
+        accounts.add(result);
+        return result;
+    }
+
+    @Override
+    public Account getAccountByKey(String key) {
+        for (Account account : accounts) {
+            if (key.equals(account.getExternalKey())) {
+                return account;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Account getAccountById(UUID uid) {
+        for (Account account : accounts) {
+            if (uid.equals(account.getId())) {
+                return account;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<Account> getAccounts() {
+        return new ArrayList<Account>(accounts);
+    }
+
+    @Override
+    public UUID getIdFromKey(String externalKey) {
+        for (Account account : accounts) {
+            if (externalKey.equals(account.getExternalKey())) {
+                return account.getId();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void updateAccount(Account account) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
index b18a41b..0a1c642 100644
--- a/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
+++ b/account/src/test/java/com/ning/billing/account/dao/AccountDaoTestBase.java
@@ -16,23 +16,24 @@
 
 package com.ning.billing.account.dao;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
-import com.ning.billing.account.glue.AccountModuleMock;
-import com.ning.billing.util.eventbus.DefaultEventBusService;
-import com.ning.billing.util.eventbus.EventBusService;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+
 import org.apache.commons.io.IOUtils;
 import org.skife.jdbi.v2.IDBI;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 
-import java.io.IOException;
-
-import static org.testng.Assert.fail;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+import com.ning.billing.account.glue.AccountModuleWithEmbeddedDb;
+import com.ning.billing.util.eventbus.DefaultEventBusService;
+import com.ning.billing.util.eventbus.EventBusService;
 
 public abstract class AccountDaoTestBase {
-    protected AccountModuleMock module;
+    protected AccountModuleWithEmbeddedDb module;
     protected AccountDao accountDao;
     protected IDBI dbi;
 
@@ -40,14 +41,12 @@ public abstract class AccountDaoTestBase {
     protected void setup() throws IOException {
         // Health check test to make sure MySQL is setup properly
         try {
-            module = new AccountModuleMock();
+            module = new AccountModuleWithEmbeddedDb();
             final String accountDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
-            final String invoiceDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
             final String utilDdl = IOUtils.toString(AccountSqlDao.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
 
             module.startDb();
             module.initDb(accountDdl);
-            module.initDb(invoiceDdl);
             module.initDb(utilDdl);
 
             final Injector injector = Guice.createInjector(Stage.DEVELOPMENT, module);
diff --git a/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
new file mode 100644
index 0000000..69efaa1
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/dao/MockAccountDao.java
@@ -0,0 +1,98 @@
+/*
+ * 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.account.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountChangeNotification;
+import com.ning.billing.account.api.user.DefaultAccountChangeNotification;
+import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.EventBus.EventBusException;
+
+public class MockAccountDao implements AccountDao {
+    private final EventBus eventBus;
+    private final Map<String, Account> accounts = new ConcurrentHashMap<String, Account>();
+
+    @Inject
+    public MockAccountDao(EventBus eventBus) {
+        this.eventBus = eventBus;
+    }
+
+    @Override
+    public void create(Account account) {
+        accounts.put(account.getId().toString(), account);
+
+        try {
+            eventBus.post(new DefaultAccountCreationEvent(account));
+        }
+        catch (EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Override
+    public Account getById(String id) {
+        return accounts.get(id);
+    }
+
+    @Override
+    public List<Account> get() {
+        return new ArrayList<Account>(accounts.values());
+    }
+
+    @Override
+    public void test() {
+    }
+
+    @Override
+    public Account getAccountByKey(String key) {
+        for (Account account : accounts.values()) {
+            if (key.equals(account.getExternalKey())) {
+                return account;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public UUID getIdFromKey(String externalKey) {
+        Account account = getAccountByKey(externalKey);
+        return account == null ? null : account.getId();
+    }
+
+    @Override
+    public void update(Account account) {
+        Account currentAccount = accounts.put(account.getId().toString(), account);
+
+        AccountChangeNotification changeEvent = new DefaultAccountChangeNotification(account.getId(), currentAccount, account);
+        if (changeEvent.hasChanges()) {
+            try {
+                eventBus.post(changeEvent);
+            }
+            catch (EventBusException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+}
diff --git a/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
new file mode 100644
index 0000000..fb72404
--- /dev/null
+++ b/account/src/test/java/com/ning/billing/account/glue/AccountModuleWithMocks.java
@@ -0,0 +1,28 @@
+/*
+ * 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.account.glue;
+
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.MockAccountDao;
+
+public class AccountModuleWithMocks extends AccountModule {
+    @Override
+    protected void installAccountDao() {
+        bind(MockAccountDao.class).asEagerSingleton();
+        bind(AccountDao.class).to(MockAccountDao.class);
+    }
+}
diff --git a/analytics/src/test/java/com/ning/billing/analytics/dao/MockBusinessAccountDao.java b/analytics/src/test/java/com/ning/billing/analytics/dao/MockBusinessAccountDao.java
new file mode 100644
index 0000000..f3dead0
--- /dev/null
+++ b/analytics/src/test/java/com/ning/billing/analytics/dao/MockBusinessAccountDao.java
@@ -0,0 +1,44 @@
+/*
+ * 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.analytics.dao;
+
+import com.ning.billing.analytics.BusinessAccount;
+
+public class MockBusinessAccountDao implements BusinessAccountDao {
+
+    @Override
+    public BusinessAccount getAccount(String key) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public int createAccount(BusinessAccount account) {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public int saveAccount(BusinessAccount account) {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    public void test() {
+    }
+}

api/pom.xml 8(+8 -0)

diff --git a/api/pom.xml b/api/pom.xml
index dce65e7..ca679be 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -50,6 +50,14 @@
             <groupId>org.skife.config</groupId>
             <artifactId>config-magic</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+        </dependency>
 
     </dependencies>
     <build>
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
index 89c0d87..5ecd311 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceCreationNotification.java
@@ -16,17 +16,19 @@
 
 package com.ning.billing.invoice.api;
 
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.util.eventbus.EventBusNotification;
-import org.joda.time.DateTime;
-
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.util.eventbus.EventBusNotification;
+
 public interface InvoiceCreationNotification extends EventBusNotification {
     public UUID getInvoiceId();
     public UUID getAccountId();
     public BigDecimal getAmountOwed();
     public Currency getCurrency();
     public DateTime getInvoiceCreationDate();
+
 }
\ No newline at end of file
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
index bde12a9..aaa89a5 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoicePaymentApi.java
@@ -16,19 +16,29 @@
 
 package com.ning.billing.invoice.api;
 
-import com.ning.billing.catalog.api.Currency;
-
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
+
 import org.joda.time.DateTime;
 
-public interface InvoicePaymentApi {
-    public void paymentSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentAttemptDate);
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.InvoicePayment;
 
-    public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate);
+public interface InvoicePaymentApi {
 
     public List<Invoice> getInvoicesByAccount(UUID accountId);
 
     public Invoice getInvoice(UUID invoiceId);
+
+    public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId);
+
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId);
+
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
+
+    public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate);
+
+    public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate);
+
 }
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
index 2c8d02e..ed4e671 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
@@ -16,12 +16,12 @@
 
 package com.ning.billing.invoice.api;
 
-import org.joda.time.DateTime;
-
-import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
-import com.ning.billing.catalog.api.Currency;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.payment.api.InvoicePayment;
 
 public interface InvoiceUserApi {
     public List<UUID> getInvoicesForPayment(DateTime targetDate, int numberOfDays);
@@ -30,8 +30,10 @@ public interface InvoiceUserApi {
 
     public Invoice getInvoice(UUID invoiceId);
 
-    public void paymentAttemptFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate);
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
 
-    public void paymentAttemptSuccessful(UUID invoiceId, BigDecimal amount, Currency currency,
-                                         UUID paymentId, DateTime paymentDate);
+//    public void paymentAttemptFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate);
+//
+//    public void paymentAttemptSuccessful(UUID invoiceId, BigDecimal amount, Currency currency,
+//                                         UUID paymentId, DateTime paymentDate);
 }
diff --git a/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java
new file mode 100644
index 0000000..2071982
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/CreditCardPaymentMethodInfo.java
@@ -0,0 +1,94 @@
+/*
+ * 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.api;
+
+
+public final class CreditCardPaymentMethodInfo extends PaymentMethodInfo {
+    public static final class Builder extends BuilderBase<CreditCardPaymentMethodInfo, Builder> {
+        private String cardHolderName;
+        private String cardType;
+        private String expirationDate;
+        private String maskNumber;
+
+        public Builder() {
+            super(Builder.class);
+        }
+
+        public Builder(CreditCardPaymentMethodInfo src) {
+            super(Builder.class, src);
+        }
+
+        public Builder setCardHolderName(String cardHolderName) {
+            this.cardHolderName = cardHolderName;
+            return this;
+        }
+
+        public Builder setCardType(String cardType) {
+            this.cardType = cardType;
+            return this;
+        }
+
+        public Builder setExpirationDateStr(String expirationDateStr) {
+            this.expirationDate = expirationDateStr;
+            return this;
+        }
+
+        public Builder setMaskNumber(String maskNumber) {
+            this.maskNumber = maskNumber;
+            return this;
+        }
+
+        public CreditCardPaymentMethodInfo build() {
+            return new CreditCardPaymentMethodInfo(id, accountId, defaultMethod, cardHolderName, cardType, expirationDate, maskNumber);
+        }
+    }
+
+    private final String cardHolderName;
+    private final String cardType;
+    private final String expirationDate;
+    private final String maskNumber;
+
+    public CreditCardPaymentMethodInfo(String id,
+                                   String accountId,
+                                   Boolean defaultMethod,
+                                   String cardHolderName,
+                                   String cardType,
+                                   String expirationDate,
+                                   String maskNumber) {
+      super(id, accountId, defaultMethod, "creditCard");
+      this.cardHolderName = cardHolderName;
+      this.cardType = cardType;
+      this.expirationDate = expirationDate;
+      this.maskNumber = maskNumber;
+    }
+
+    public String getCardHolderName() {
+      return cardHolderName;
+    }
+
+    public String getCardType() {
+      return cardType;
+    }
+
+    public String getExpirationDate() {
+      return expirationDate;
+    }
+
+    public String getMaskNumber() {
+      return maskNumber;
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/Either.java b/api/src/main/java/com/ning/billing/payment/api/Either.java
new file mode 100644
index 0000000..25ce8f8
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/Either.java
@@ -0,0 +1,79 @@
+/*
+ * 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.api;
+
+import org.codehaus.jackson.annotate.JsonValue;
+
+public abstract class Either<T, V> {
+    public static <T, V> Either<T, V> left(T value) {
+        return new Left<T, V>(value);
+    }
+    public static <T, V> Either<T, V> right(V value) {
+        return new Right<T, V>(value);
+    }
+
+    private Either() {
+    }
+
+    public boolean isLeft() {
+        return false;
+    }
+    public boolean isRight() {
+        return false;
+    }
+    public T getLeft() {
+        throw new UnsupportedOperationException();
+    }
+    public V getRight() {
+        throw new UnsupportedOperationException();
+    }
+
+    public static class Left<T, V> extends Either<T, V> {
+        private final T value;
+
+        public Left(T value) {
+            this.value = value;
+        }
+        @Override
+        public boolean isLeft() {
+            return true;
+        }
+        @Override
+        @JsonValue
+        public T getLeft() {
+            return value;
+        }
+    }
+
+    public static class Right<T, V> extends Either<T, V> {
+        private final V value;
+
+        public Right(V value) {
+            this.value = value;
+        }
+        @Override
+        public boolean isRight() {
+            return true;
+        }
+
+        @Override
+        @JsonValue
+        public V getRight() {
+            return value;
+        }
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/InvoicePayment.java b/api/src/main/java/com/ning/billing/payment/api/InvoicePayment.java
new file mode 100644
index 0000000..c43c179
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/InvoicePayment.java
@@ -0,0 +1,70 @@
+/*
+ * 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.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+
+public class InvoicePayment {
+        private final UUID invoiceId;
+        private final UUID paymentAttemptId;
+        private final DateTime paymentAttemptDate;
+        private final BigDecimal amount;
+        private final Currency currency;
+
+        public InvoicePayment(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+            this.invoiceId = invoiceId;
+            this.amount = amount;
+            this.currency = currency;
+            this.paymentAttemptId = paymentAttemptId;
+            this.paymentAttemptDate = paymentAttemptDate;
+        }
+
+        public InvoicePayment(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+            this(invoiceId, null, null, paymentAttemptId, paymentAttemptDate);
+        }
+
+        public UUID getInvoiceId() {
+            return invoiceId;
+        }
+
+        public UUID getPaymentAttemptId() {
+            return paymentAttemptId;
+        }
+
+        public DateTime getPaymentAttemptDate() {
+            return paymentAttemptDate;
+        }
+
+        public BigDecimal getAmount() {
+            return amount;
+        }
+
+        public Currency getCurrency() {
+            return currency;
+        }
+
+        @Override
+        public String toString() {
+            return "InvoicePayment [invoiceId=" + invoiceId + ", paymentAttemptId=" + paymentAttemptId + ", paymentAttemptDate=" + paymentAttemptDate + ", amount=" + amount + ", currency=" + currency + "]";
+        }
+
+}
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
new file mode 100644
index 0000000..1d62eb5
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentApi.java
@@ -0,0 +1,51 @@
+/*
+ * 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.api;
+
+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);
+
+    Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+
+    Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId);
+
+    Either<PaymentError, Void> updatePaymentGateway(String accountKey);
+
+    Either<PaymentError, String> addPaypalPaymentMethod(@Nullable String accountKey, PaypalPaymentMethodInfo paypalPaymentMethod);
+
+    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);
+
+    List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds); //TODO
+
+    Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+
+    Either<PaymentError, PaymentProviderAccount> createPaymentProviderAccount(PaymentProviderAccount account);
+
+    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
new file mode 100644
index 0000000..0c8f8c4
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentAttempt.java
@@ -0,0 +1,260 @@
+/*
+ * 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.api;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.common.base.Objects;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+
+public class PaymentAttempt {
+    private final UUID paymentAttemptId;
+    private final UUID invoiceId;
+    private final UUID accountId;
+    private final BigDecimal amount;
+    private final Currency currency;
+    private final String paymentId;
+    private final DateTime invoiceDate;
+    private final DateTime paymentAttemptDate;
+    private final DateTime createdDate;
+    private final DateTime updatedDate;
+
+    public PaymentAttempt(UUID paymentAttemptId,
+                          UUID invoiceId,
+                          UUID accountId,
+                          BigDecimal amount,
+                          Currency currency,
+                          DateTime invoiceDate,
+                          DateTime paymentAttemptDate,
+                          String paymentId,
+                          DateTime createdDate,
+                          DateTime updatedDate) {
+        this.paymentAttemptId = paymentAttemptId;
+        this.invoiceId = invoiceId;
+        this.accountId = accountId;
+        this.amount = amount;
+        this.currency = currency;
+        this.invoiceDate = invoiceDate;
+        this.paymentAttemptDate = paymentAttemptDate == null ? new DateTime(DateTimeZone.UTC) : paymentAttemptDate;
+        this.paymentId = paymentId;
+        this.createdDate = createdDate;
+        this.updatedDate = updatedDate;
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId,
+                          UUID invoiceId,
+                          UUID accountId,
+                          BigDecimal amount,
+                          Currency currency,
+                          DateTime invoiceDate,
+                          DateTime paymentAttemptDate,
+                          String paymentId) {
+        this(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, paymentId, new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC));
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, BigDecimal amount, Currency currency, DateTime invoiceDate, DateTime paymentAttemptDate) {
+        this(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, null);
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, UUID invoiceId, UUID accountId, DateTime invoiceDate, DateTime paymentAttemptDate) {
+        this(paymentAttemptId, invoiceId, accountId, null, null, invoiceDate, paymentAttemptDate, null);
+    }
+
+    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+        this(paymentAttemptId, invoice.getId(), invoice.getAccountId(), invoice.getAmountOutstanding(), invoice.getCurrency(), invoice.getInvoiceDate(), null);
+    }
+
+    public DateTime getInvoiceDate() {
+        return invoiceDate;
+    }
+
+    public UUID getPaymentAttemptId() {
+        return paymentAttemptId;
+    }
+
+    public String getPaymentId() {
+        return paymentId;
+    }
+
+    public DateTime getPaymentAttemptDate() {
+        return paymentAttemptDate;
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", invoiceId=" + invoiceId + ", amount=" + amount + ", currency=" + currency + ", paymentId=" + paymentId + ", paymentAttemptDate=" + paymentAttemptDate + "]";
+    }
+
+    public Builder cloner() {
+        return new Builder(this);
+    }
+
+    public static class Builder {
+        private UUID paymentAttemptId;
+        private UUID invoiceId;
+        private UUID accountId;
+        private BigDecimal amount;
+        private Currency currency;
+        private DateTime invoiceDate;
+        private DateTime paymentAttemptDate;
+        private String paymentId;
+        private DateTime createdDate;
+        private DateTime updatedDate;
+
+        public Builder() {
+        }
+
+        public Builder(PaymentAttempt src) {
+            this.paymentAttemptId = src.paymentAttemptId;
+            this.invoiceId = src.invoiceId;
+            this.accountId = src.accountId;
+            this.amount = src.amount;
+            this.currency = src.currency;
+            this.invoiceDate = src.invoiceDate;
+            this.paymentAttemptDate = src.paymentAttemptDate;
+            this.paymentId = src.paymentId;
+            this.createdDate = src.createdDate;
+            this.updatedDate = src.updatedDate;
+        }
+
+        public Builder setPaymentAttemptId(UUID paymentAttemptId) {
+            this.paymentAttemptId = paymentAttemptId;
+            return this;
+        }
+
+        public Builder setInvoiceId(UUID invoiceId) {
+            this.invoiceId = invoiceId;
+            return this;
+        }
+
+        public Builder setAccountId(UUID accountId) {
+            this.accountId = accountId;
+            return this;
+        }
+
+        public Builder setAmount(BigDecimal amount) {
+            this.amount = amount;
+            return this;
+        }
+
+        public Builder setCurrency(Currency currency) {
+            this.currency = currency;
+            return this;
+        }
+
+        public Builder setCreatedDate(DateTime createdDate) {
+            this.createdDate = createdDate;
+            return this;
+        }
+
+        public Builder setUpdatedDate(DateTime updatedDate) {
+            this.updatedDate = updatedDate;
+            return this;
+        }
+
+        public Builder setInvoiceDate(DateTime invoiceDate) {
+            this.invoiceDate = invoiceDate;
+            return this;
+        }
+
+        public Builder setPaymentAttemptDate(DateTime paymentAttemptDate) {
+            this.paymentAttemptDate = paymentAttemptDate;
+            return this;
+        }
+
+        public Builder setPaymentId(String paymentId) {
+            this.paymentId = paymentId;
+            return this;
+        }
+
+        public PaymentAttempt build() {
+            return new PaymentAttempt(paymentAttemptId,
+                                      invoiceId,
+                                      accountId,
+                                      amount,
+                                      currency,
+                                      invoiceDate,
+                                      paymentAttemptDate,
+                                      paymentId,
+                                      createdDate,
+                                      updatedDate);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(paymentAttemptId,
+                                invoiceId,
+                                accountId,
+                                amount,
+                                currency,
+                                invoiceDate,
+                                paymentAttemptDate,
+                                paymentId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentAttempt other = (PaymentAttempt)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(paymentAttemptId, other.paymentAttemptId) &&
+                       Objects.equal(invoiceId, other.invoiceId) &&
+                       Objects.equal(accountId, other.accountId) &&
+                       Objects.equal(amount, other.amount) &&
+                       Objects.equal(currency, other.currency) &&
+                       Objects.equal(invoiceDate, other.invoiceDate) &&
+                       Objects.equal(paymentAttemptDate, other.paymentAttemptDate) &&
+                       Objects.equal(paymentId, other.paymentId);
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentError.java b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
new file mode 100644
index 0000000..45e8555
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
@@ -0,0 +1,83 @@
+/*
+ * 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.api;
+import org.codehaus.jackson.annotate.JsonTypeInfo;
+import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
+
+import com.ning.billing.util.eventbus.EventBusNotification;
+
+@JsonTypeInfo(use = Id.NAME, property = "error")
+public class PaymentError implements EventBusNotification {
+    private final String type;
+    private final String message;
+
+    public PaymentError(PaymentError src) {
+        this.type = src.type;
+        this.message = src.message;
+    }
+
+    public PaymentError(String type, String message) {
+        this.type = type;
+        this.message = message;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((message == null) ? 0 : message.hashCode());
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        PaymentError other = (PaymentError) obj;
+        if (message == null) {
+            if (other.message != null)
+                return false;
+        }
+        else if (!message.equals(other.message))
+            return false;
+        if (type == null) {
+            if (other.type != null)
+                return false;
+        }
+        else if (!type.equals(other.type))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentError [type=" + type + ", message=" + message + "]";
+    }
+}
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
new file mode 100644
index 0000000..9cfa71f
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
@@ -0,0 +1,271 @@
+/*
+ * 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.api;
+
+import java.math.BigDecimal;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.google.common.base.Objects;
+import com.ning.billing.util.eventbus.EventBusNotification;
+
+public class PaymentInfo implements EventBusNotification {
+    private final String paymentId;
+    private final BigDecimal amount;
+    private final BigDecimal refundAmount;
+    private final String paymentNumber;
+    private final String bankIdentificationNumber;
+    private final String status;
+    private final String type;
+    private final String referenceId;
+    private final DateTime effectiveDate;
+    private final DateTime createdDate;
+    private final DateTime updatedDate;
+
+    public PaymentInfo(PaymentInfo src) {
+        this.paymentId = src.paymentId;
+        this.amount = src.amount;
+        this.refundAmount = src.refundAmount;
+        this.paymentNumber = src.paymentNumber;
+        this.bankIdentificationNumber = src.bankIdentificationNumber;
+        this.status = src.status;
+        this.type = src.type;
+        this.referenceId = src.referenceId;
+        this.effectiveDate = src.effectiveDate;
+        this.createdDate = src.createdDate;
+        this.updatedDate = src.updatedDate;
+    }
+
+    @JsonCreator
+    public PaymentInfo(@JsonProperty("paymentId") String paymentId,
+                       @JsonProperty("amount") BigDecimal amount,
+                       @JsonProperty("refundAmount") BigDecimal refundAmount,
+                       @JsonProperty("bankIdentificationNumber") String bankIdentificationNumber,
+                       @JsonProperty("paymentNumber") String paymentNumber,
+                       @JsonProperty("status") String status,
+                       @JsonProperty("type") String type,
+                       @JsonProperty("referenceId") String referenceId,
+                       @JsonProperty("effectiveDate") DateTime effectiveDate,
+                       @JsonProperty("createdDate") DateTime createdDate,
+                       @JsonProperty("updatedDate") DateTime updatedDate) {
+        this.paymentId = paymentId;
+        this.amount = amount;
+        this.refundAmount = refundAmount;
+        this.bankIdentificationNumber = bankIdentificationNumber;
+        this.effectiveDate = effectiveDate;
+        this.paymentNumber = paymentNumber;
+        this.referenceId = referenceId;
+        this.status = status;
+        this.type = type;
+        this.createdDate = createdDate;
+        this.updatedDate = updatedDate;
+    }
+
+    public Builder cloner() {
+        return new Builder(this);
+    }
+
+    public String getPaymentId() {
+        return paymentId;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public String getBankIdentificationNumber() {
+        return bankIdentificationNumber;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public String getPaymentNumber() {
+        return paymentNumber;
+    }
+
+    public String getReferenceId() {
+        return referenceId;
+    }
+
+    public BigDecimal getRefundAmount() {
+        return refundAmount;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
+
+    public static class Builder {
+        private String paymentId;
+        private BigDecimal amount;
+        private BigDecimal refundAmount;
+        private String paymentNumber;
+        private String bankIdentificationNumber;
+        private String type;
+        private String status;
+        private String referenceId;
+        private DateTime effectiveDate;
+        private DateTime createdDate;
+        private DateTime updatedDate;
+
+        public Builder() {
+        }
+
+        public Builder(PaymentInfo src) {
+            this.paymentId = src.paymentId;
+            this.amount = src.amount;
+            this.refundAmount = src.refundAmount;
+            this.paymentNumber = src.paymentNumber;
+            this.bankIdentificationNumber = src.bankIdentificationNumber;
+            this.type = src.type;
+            this.status = src.status;
+            this.effectiveDate = src.effectiveDate;
+            this.referenceId = src.referenceId;
+            this.createdDate = src.createdDate;
+            this.updatedDate = src.updatedDate;
+        }
+
+        public Builder setPaymentId(String paymentId) {
+            this.paymentId = paymentId;
+            return this;
+        }
+
+        public Builder setAmount(BigDecimal amount) {
+            this.amount = amount;
+            return this;
+        }
+
+        public Builder setBankIdentificationNumber(String bankIdentificationNumber) {
+            this.bankIdentificationNumber = bankIdentificationNumber;
+            return this;
+        }
+
+        public Builder setCreatedDate(DateTime createdDate) {
+            this.createdDate = createdDate;
+            return this;
+        }
+
+        public Builder setEffectiveDate(DateTime effectiveDate) {
+            this.effectiveDate = effectiveDate;
+            return this;
+        }
+
+        public Builder setPaymentNumber(String paymentNumber) {
+            this.paymentNumber = paymentNumber;
+            return this;
+        }
+
+        public Builder setReferenceId(String referenceId) {
+            this.referenceId = referenceId;
+            return this;
+        }
+
+        public Builder setRefundAmount(BigDecimal refundAmount) {
+            this.refundAmount = refundAmount;
+            return this;
+        }
+
+        public Builder setStatus(String status) {
+            this.status = status;
+            return this;
+        }
+
+        public Builder setType(String type) {
+            this.type = type;
+            return this;
+        }
+
+        public Builder setUpdatedDate(DateTime updatedDate) {
+            this.updatedDate = updatedDate;
+            return this;
+        }
+
+        public PaymentInfo build() {
+            return new PaymentInfo(paymentId,
+                                   amount,
+                                   refundAmount,
+                                   bankIdentificationNumber,
+                                   paymentNumber,
+                                   type,
+                                   status,
+                                   referenceId,
+                                   effectiveDate,
+                                   createdDate,
+                                   updatedDate);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(amount,
+                                bankIdentificationNumber,
+                                createdDate,
+                                effectiveDate,
+                                paymentId,
+                                paymentNumber,
+                                referenceId,
+                                refundAmount,
+                                status,
+                                type,
+                                updatedDate);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentInfo other = (PaymentInfo)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(amount, other.amount) &&
+                       Objects.equal(bankIdentificationNumber, other.bankIdentificationNumber) &&
+                       Objects.equal(createdDate, other.createdDate) &&
+                       Objects.equal(effectiveDate, other.effectiveDate) &&
+                       Objects.equal(paymentId, other.paymentId) &&
+                       Objects.equal(paymentNumber, other.paymentNumber) &&
+                       Objects.equal(referenceId, other.referenceId) &&
+                       Objects.equal(refundAmount, other.refundAmount) &&
+                       Objects.equal(status, other.status) &&
+                       Objects.equal(type, other.type) &&
+                       Objects.equal(updatedDate, other.updatedDate);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentInfo [paymentId=" + paymentId + ", amount=" + amount + ", refundAmount=" + refundAmount + ", paymentNumber=" + paymentNumber + ", bankIdentificationNumber=" + bankIdentificationNumber + ", status=" + status + ", type=" + type + ", referenceId=" + referenceId + ", effectiveDate=" + effectiveDate + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + "]";
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaymentMethodInfo.java
new file mode 100644
index 0000000..78f73d3
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentMethodInfo.java
@@ -0,0 +1,137 @@
+/*
+ * 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.api;
+
+import com.google.common.base.Objects;
+
+public class PaymentMethodInfo {
+    private final String id;
+    private final String accountId;
+    private final Boolean defaultMethod;
+    private final String type;
+
+    public PaymentMethodInfo(String id,
+                             String accountId,
+                             Boolean defaultMethod,
+                             String type) {
+        this.id = id;
+        this.accountId = accountId;
+        this.defaultMethod = defaultMethod;
+        this.type = type;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    public Boolean getDefaultMethod() {
+        return defaultMethod;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id,
+                                accountId,
+                                defaultMethod,
+                                type);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentMethodInfo other = (PaymentMethodInfo)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(id, other.id) &&
+                       Objects.equal(accountId, other.accountId) &&
+                       Objects.equal(defaultMethod, other.defaultMethod) &&
+                       Objects.equal(type, other.type);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentMethodInfo [id=" + id + ", accountId=" + accountId + ", defaultMethod=" + defaultMethod + ", type=" + type + "]";
+    }
+
+    protected abstract static class BuilderBase<T extends PaymentMethodInfo, V extends BuilderBase<T, V>> {
+        protected final Class<V> builderClazz;
+        protected String id;
+        protected String accountId;
+        protected Boolean defaultMethod;
+
+        protected BuilderBase(Class<V> builderClazz) {
+            this.builderClazz = builderClazz;
+        }
+
+        protected BuilderBase(Class<V> builderClazz, T src) {
+            this(builderClazz);
+            this.id = src.id;
+            this.accountId = src.accountId;
+            this.defaultMethod = src.defaultMethod;
+        }
+
+        public V setId(String id) {
+            this.id = id;
+            return builderClazz.cast(this);
+        }
+
+        public V setAccountId(String accountId) {
+            this.accountId = accountId;
+            return builderClazz.cast(this);
+        }
+
+        public V setDefaultMethod(Boolean defaultMethod) {
+            this.defaultMethod = defaultMethod;
+            return builderClazz.cast(this);
+        }
+    }
+
+    public static class Builder extends BuilderBase<PaymentMethodInfo, Builder> {
+        private String type;
+
+        public Builder() {
+            super(Builder.class);
+        }
+
+        public Builder(PaymentMethodInfo src) {
+            super(Builder.class, src);
+            this.type = src.type;
+        }
+
+        public Builder setType(String type) {
+            this.type = type;
+            return this;
+        }
+
+        public PaymentMethodInfo build() {
+            return new PaymentMethodInfo(id, accountId, defaultMethod, type);
+        }
+    }
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java
new file mode 100644
index 0000000..553d6a2
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderAccount.java
@@ -0,0 +1,124 @@
+/*
+ * 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.api;
+
+import com.google.common.base.Objects;
+
+public class PaymentProviderAccount {
+    private final String id;
+    private final String accountNumber;
+    private final String accountName;
+    private final String phoneNumber;
+    private final String defaultPaymentMethodId;
+
+    public PaymentProviderAccount(String id,
+                                  String accountNumber,
+                                  String accountName,
+                                  String phoneNumber,
+                                  String defaultPaymentMethodId) {
+        this.id = id;
+        this.accountNumber = accountNumber;
+        this.accountName = accountName;
+        this.phoneNumber = phoneNumber;
+        this.defaultPaymentMethodId = defaultPaymentMethodId;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getAccountNumber() {
+        return accountNumber;
+    }
+
+    public String getAccountName() {
+        return accountName;
+    }
+
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    public String getDefaultPaymentMethodId() {
+        return defaultPaymentMethodId;
+    }
+
+    public static class Builder {
+        private String id;
+        private String accountNumber;
+        private String accountName;
+        private String phoneNumber;
+        private String defaultPaymentMethodId;
+
+        public Builder setId(String id) {
+            this.id = id;
+            return this;
+        }
+
+        public Builder setAccountNumber(String accountNumber) {
+            this.accountNumber = accountNumber;
+            return this;
+        }
+
+        public Builder setAccountName(String accountName) {
+            this.accountName = accountName;
+            return this;
+        }
+
+        public Builder setPhoneNumber(String phoneNumber) {
+            this.phoneNumber = phoneNumber;
+            return this;
+        }
+
+        public Builder setDefaultPaymentMethod(String defaultPaymentMethod) {
+            this.defaultPaymentMethodId = defaultPaymentMethod;
+            return this;
+        }
+
+        public PaymentProviderAccount build() {
+            return new PaymentProviderAccount(id, accountNumber, accountName, phoneNumber, defaultPaymentMethodId);
+        }
+
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id,
+                                accountNumber,
+                                accountName,
+                                phoneNumber,
+                                defaultPaymentMethodId);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentProviderAccount other = (PaymentProviderAccount)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(id, other.id) &&
+                       Objects.equal(accountNumber, other.accountNumber) &&
+                       Objects.equal(phoneNumber, other.phoneNumber) &&
+                       Objects.equal(defaultPaymentMethodId, other.defaultPaymentMethodId);
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentProviderContactData.java b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderContactData.java
new file mode 100644
index 0000000..7be9908
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentProviderContactData.java
@@ -0,0 +1,157 @@
+/*
+ * 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.api;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.google.common.base.Objects;
+import com.ning.billing.catalog.api.Currency;
+
+public class PaymentProviderContactData {
+    private final String firstName;
+    private final String lastName;
+    private final String email;
+    private final String phoneNumber;
+    private final String externalKey;
+    private final String locale;
+    private final Currency currency;
+
+    public PaymentProviderContactData(String firstName,
+                                      String lastName,
+                                      String email,
+                                      String phoneNumber,
+                                      String externalKey,
+                                      String locale,
+                                      Currency currency) {
+        this.firstName = StringUtils.substring(firstName, 0, 100);
+        this.lastName  = StringUtils.substring(lastName, 0, 100);
+        this.email     = StringUtils.substring(email, 0, 80);
+        this.phoneNumber = phoneNumber;
+        this.externalKey = externalKey;
+        this.locale = locale;
+        this.currency = currency;
+    }
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    public String getLocale() {
+        return locale;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    public static class Builder {
+        private String firstName;
+        private String lastName;
+        private String email;
+        private String phoneNumber;
+        private String externalKey;
+        private String locale;
+        private Currency currency;
+
+        public Builder setExternalKey(String externalKey) {
+            this.externalKey = externalKey;
+            return this;
+        }
+
+        public Builder setFirstName(String firstName) {
+            this.firstName = firstName;
+            return this;
+        }
+
+        public Builder setLastName(String lastName) {
+            this.lastName = lastName;
+            return this;
+        }
+
+        public Builder setEmail(String email) {
+            this.email = email;
+            return this;
+        }
+
+        public Builder setPhoneNumber(String phoneNumber) {
+            this.phoneNumber = phoneNumber;
+            return this;
+        }
+
+        public Builder setLocale(String locale) {
+            this.locale = locale;
+            return this;
+        }
+
+        public Builder setCurrency(Currency currency) {
+            this.currency = currency;
+            return this;
+        }
+
+        public PaymentProviderContactData build() {
+            return new PaymentProviderContactData(firstName, lastName, email, phoneNumber, externalKey, locale, currency);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(firstName,
+                                lastName,
+                                email,
+                                phoneNumber,
+                                externalKey,
+                                locale,
+                                currency);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (getClass() == obj.getClass()) {
+            PaymentProviderContactData other = (PaymentProviderContactData)obj;
+            if (obj == other) {
+                return true;
+            }
+            else {
+                return Objects.equal(firstName, other.firstName) &&
+                       Objects.equal(lastName, other.lastName) &&
+                       Objects.equal(email, other.email) &&
+                       Objects.equal(phoneNumber, other.phoneNumber) &&
+                       Objects.equal(externalKey, other.externalKey) &&
+                       Objects.equal(locale, other.locale) &&
+                       Objects.equal(currency, other.currency);
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentService.java b/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
new file mode 100644
index 0000000..988a00a
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentService.java
@@ -0,0 +1,26 @@
+/*
+ * 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.api;
+
+import com.ning.billing.lifecycle.KillbillService;
+
+public interface PaymentService extends KillbillService {
+    @Override
+    String getName();
+
+    PaymentApi getPaymentApi();
+}
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java
new file mode 100644
index 0000000..702d672
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/payment/api/PaypalPaymentMethodInfo.java
@@ -0,0 +1,75 @@
+/*
+ * 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.api;
+
+import com.google.common.base.Strings;
+
+
+public final class PaypalPaymentMethodInfo extends PaymentMethodInfo {
+    public static final class Builder extends BuilderBase<PaypalPaymentMethodInfo, Builder> {
+        private String baid;
+        private String email;
+
+        public Builder() {
+            super(Builder.class);
+        }
+
+        public Builder(PaypalPaymentMethodInfo src) {
+            super(Builder.class, src);
+        }
+
+        public Builder setBaid(String baid) {
+            this.baid = baid;
+            return this;
+        }
+
+        public Builder setEmail(String email) {
+            this.email = email;
+            return this;
+        }
+
+        public PaypalPaymentMethodInfo build() {
+            return new PaypalPaymentMethodInfo(id, accountId, defaultMethod, baid, email);
+        }
+    }
+
+    private final String baid;
+    private final String email;
+
+    public PaypalPaymentMethodInfo(String id,
+                                   String accountId,
+                                   Boolean defaultMethod,
+                                   String baid,
+                                   String email) {
+        super(id, accountId, defaultMethod, "paypal");
+
+        if (Strings.isNullOrEmpty(accountId) || Strings.isNullOrEmpty(baid) || Strings.isNullOrEmpty(email)) {
+            throw new IllegalArgumentException("accountId, baid and email should be present");
+        }
+
+        this.baid = baid;
+        this.email = email;
+    }
+
+    public String getBaid() {
+        return baid;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+}
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index f0d0aae..627ad6b 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -52,6 +52,11 @@
         </dependency>
         <dependency>
             <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
             <artifactId>killbill-util</artifactId>
             <type>test-jar</type>
             <scope>test</scope>
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
index 4a44942..6f5ac4e 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/glue/MockEngineModule.java
@@ -16,7 +16,7 @@
 
 package com.ning.billing.entitlement.glue;
 
-import com.ning.billing.account.glue.AccountModuleMock;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
 import com.ning.billing.catalog.glue.CatalogModule;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
@@ -34,7 +34,7 @@ public class MockEngineModule extends EntitlementModule {
         super.configure();
         install(new EventBusModule());
         install(new CatalogModule());
-        install(new AccountModuleMock());
+        install(new AccountModuleWithMocks());
     }
 
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index fac63ba..31006ea 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -1,4 +1,5 @@
 /*
+
  * Copyright 2010-2011 Ning, Inc.
  *
  * Ning licenses this file to you under the Apache License, version 2.0
@@ -19,13 +20,15 @@ package com.ning.billing.invoice.api.invoice;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
+
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.IDBI;
+
 import com.google.inject.Inject;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.payment.api.InvoicePayment;
 
 public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     private final InvoiceDao dao;
@@ -36,14 +39,14 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     }
 
     @Override
-    public void paymentSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentAttemptDate) {
-        dao.notifySuccessfulPayment(invoiceId.toString(), amount, currency.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        dao.notifyOfPaymentAttempt(invoicePayment);
     }
 
-    @Override
-    public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
-        dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
-    }
+//    @Override
+//    public void paymentFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
+//        dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+//    }
 
     @Override
     public List<Invoice> getInvoicesByAccount(UUID accountId) {
@@ -54,4 +57,28 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     public Invoice getInvoice(UUID invoiceId) {
         return dao.getById(invoiceId.toString());
     }
+
+    @Override
+    public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId) {
+        String invoiceIdStr = dao.getInvoiceIdByPaymentAttemptId(paymentAttemptId);
+        return invoiceIdStr == null ? null : dao.getById(invoiceIdStr);
+    }
+
+    @Override
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        return dao.getInvoicePayment(paymentAttemptId);
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+        InvoicePayment invoicePayment = new InvoicePayment(invoiceId, amountOutstanding, currency, paymentAttemptId, paymentAttemptDate);
+        dao.notifyOfPaymentAttempt(invoicePayment);
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+        InvoicePayment invoicePayment = new InvoicePayment(invoiceId, null, null, paymentAttemptId, paymentAttemptDate);
+        dao.notifyOfPaymentAttempt(invoicePayment);
+    }
+
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
index 094e6f1..5c6785c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
@@ -18,7 +18,9 @@ package com.ning.billing.invoice.api.user;
 
 import java.math.BigDecimal;
 import java.util.UUID;
+
 import org.joda.time.DateTime;
+
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceCreationNotification;
 
@@ -61,4 +63,10 @@ public class DefaultInvoiceCreationNotification implements InvoiceCreationNotifi
     public DateTime getInvoiceCreationDate() {
         return invoiceCreationDate;
     }
+
+    @Override
+    public String toString() {
+        return "DefaultInvoiceCreationNotification [invoiceId=" + invoiceId + ", accountId=" + accountId + ", amountOwed=" + amountOwed + ", currency=" + currency + ", invoiceCreationDate=" + invoiceCreationDate + "]";
+    }
+
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 22f4990..ed66ec7 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -16,19 +16,16 @@
 
 package com.ning.billing.invoice.api.user;
 
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
 import com.google.inject.Inject;
-import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceUserApi;
-import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 import com.ning.billing.invoice.dao.InvoiceDao;
-import com.ning.billing.util.eventbus.EventBus;
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.IDBI;
-
-import java.math.BigDecimal;
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.payment.api.InvoicePayment;
 
 public class DefaultInvoiceUserApi implements InvoiceUserApi {
     private final InvoiceDao dao;
@@ -54,12 +51,8 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
-    public void paymentAttemptFailed(UUID invoiceId, UUID paymentId, DateTime paymentAttemptDate) {
-        dao.notifyFailedPayment(invoiceId.toString(), paymentId.toString(), paymentAttemptDate.toDate());
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        dao.notifyOfPaymentAttempt(invoicePayment);
     }
 
-    @Override
-    public void paymentAttemptSuccessful(UUID invoiceId, BigDecimal amount, Currency currency, UUID paymentId, DateTime paymentDate) {
-        dao.notifySuccessfulPayment(invoiceId.toString(), amount, currency.toString(), paymentId.toString(), paymentDate.toDate());
-    }
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index 20f33c7..d5d4ce9 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -16,24 +16,23 @@
 
 package com.ning.billing.invoice.dao;
 
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
 
-import com.google.inject.Inject;
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceCreationNotification;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
-import com.ning.billing.util.eventbus.EventBus;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.math.BigDecimal;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceCreationNotification;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
+import com.ning.billing.payment.api.InvoicePayment;
+import com.ning.billing.util.eventbus.EventBus;
 
 public class DefaultInvoiceDao implements InvoiceDao {
     private final InvoiceSqlDao invoiceDao;
@@ -138,17 +137,23 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     @Override
-    public void notifySuccessfulPayment(String invoiceId, BigDecimal paymentAmount, String currency, String paymentId, Date paymentDate) {
-        invoiceDao.notifySuccessfulPayment(invoiceId, paymentAmount, currency, paymentId, paymentDate);
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        invoiceDao.notifyOfPaymentAttempt(invoicePayment);
     }
 
     @Override
-    public void notifyFailedPayment(String invoiceId, String paymentId, Date paymentAttemptDate) {
-        invoiceDao.notifyFailedPayment(invoiceId, paymentId, paymentAttemptDate);
+    public void test() {
+        invoiceDao.test();
     }
 
     @Override
-    public void test() {
-        invoiceDao.test();
+    public String getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+        return invoiceDao.getInvoiceIdByPaymentAttemptId(paymentAttemptId.toString());
+    }
+
+    @Override
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        return invoiceDao.getInvoicePayment(paymentAttemptId);
     }
+
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index e9306b2..e90d22c 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -16,11 +16,12 @@
 
 package com.ning.billing.invoice.dao;
 
-import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 import java.util.UUID;
+
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.InvoicePayment;
 
 public interface InvoiceDao {
     void create(Invoice invoice);
@@ -36,15 +37,12 @@ public interface InvoiceDao {
     List<UUID> getInvoicesForPayment(final Date targetDate,
                                      final int numberOfDays);
 
-    void notifySuccessfulPayment(final String invoiceId,
-                                 final BigDecimal paymentAmount,
-                                 final String currency,
-                                 final String paymentId,
-                                 final Date paymentDate);
-
-    void notifyFailedPayment(final String invoiceId,
-                             final String paymentId,
-                             final Date paymentAttemptDate);
+    String getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId);
 
     void test();
+
+    InvoicePayment getInvoicePayment(UUID paymentAttemptId);
+
+    void notifyOfPaymentAttempt(InvoicePayment invoicePayment);
+
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
index 9ede145..bb03e8b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceSqlDao.java
@@ -16,13 +16,22 @@
 
 package com.ning.billing.invoice.dao;
 
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.DefaultInvoice;
-import com.ning.billing.util.UuidMapper;
-import com.ning.billing.util.entity.EntityDao;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+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;
@@ -38,15 +47,13 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 import org.skife.jdbi.v2.tweak.ResultSetMapper;
 
-import java.lang.annotation.*;
-import java.math.BigDecimal;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.payment.api.InvoicePayment;
+import com.ning.billing.util.UuidMapper;
+import com.ning.billing.util.entity.EntityDao;
 
 @ExternalizedSqlViaStringTemplate3()
 @RegisterMapper({UuidMapper.class, InvoiceSqlDao.InvoiceMapper.class})
@@ -66,29 +73,28 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
     List<Invoice> getInvoicesBySubscription(@Bind("subscriptionId") final String subscriptionId);
 
     @SqlQuery
+    String getInvoiceIdByPaymentAttemptId(@Bind("paymentAttemptId") final String paymentAttemptId);
+
+    @SqlQuery
     List<UUID> getInvoicesForPayment(@Bind("targetDate") final Date targetDate,
                                      @Bind("numberOfDays") final int numberOfDays);
 
-    @SqlUpdate
-    void notifySuccessfulPayment(@Bind("invoiceId") final String invoiceId,
-                                 @Bind("amount") final BigDecimal paymentAmount,
-                                 @Bind("currency") final String currency,
-                                 @Bind("paymentId") final String paymentId,
-                                 @Bind("paymentDate") final Date paymentDate);
+    @SqlQuery
+    InvoicePayment getInvoicePayment(@Bind("paymentAttemptId") UUID paymentAttemptId);
 
     @SqlUpdate
-    void notifyFailedPayment(@Bind("invoiceId") final String invoiceId,
-                             @Bind("paymentId") final String paymentId,
-                             @Bind("paymentAttemptDate") final Date paymentAttemptDate);
+    void notifyOfPaymentAttempt(@Bind(binder = InvoicePaymentBinder.class) InvoicePayment invoicePayment);
 
     @BindingAnnotation(InvoiceBinder.InvoiceBinderFactory.class)
     @Retention(RetentionPolicy.RUNTIME)
     @Target({ElementType.PARAMETER})
     public @interface InvoiceBinder {
         public static class InvoiceBinderFactory implements BinderFactory {
-            public Binder build(Annotation annotation) {
+            @Override
+            public Binder<InvoiceBinder, Invoice> build(Annotation annotation) {
                 return new Binder<InvoiceBinder, Invoice>() {
-                    public void bind(SQLStatement q, InvoiceBinder bind, Invoice invoice) {
+                    @Override
+                    public void bind(@SuppressWarnings("rawtypes") SQLStatement q, InvoiceBinder bind, Invoice invoice) {
                         q.bind("id", invoice.getId().toString());
                         q.bind("accountId", invoice.getAccountId().toString());
                         q.bind("invoiceDate", invoice.getInvoiceDate().toDate());
@@ -122,5 +128,41 @@ public interface InvoiceSqlDao extends EntityDao<Invoice>, Transactional<Invoice
             return new DefaultInvoice(id, accountId, invoiceDate, targetDate, currency, lastPaymentAttempt, amountPaid, new ArrayList<InvoiceItem>());
         }
     }
+
+    @SqlUpdate
+    void notifyFailedPayment(@Bind(binder = InvoicePaymentBinder.class) InvoicePayment invoicePayment);
+
+    public static final class InvoicePaymentBinder implements Binder<Bind, InvoicePayment> {
+
+        @Override
+        public void bind(@SuppressWarnings("rawtypes") SQLStatement stmt, Bind bind, InvoicePayment invoicePayment) {
+            stmt.bind("invoice_id", invoicePayment.getInvoiceId().toString());
+            stmt.bind("amount", invoicePayment.getAmount());
+            stmt.bind("currency", invoicePayment.getCurrency().toString());
+            stmt.bind("payment_attempt_id", invoicePayment.getPaymentAttemptId().toString());
+            stmt.bind("payment_attempt_date", invoicePayment.getPaymentAttemptDate() == null ? null : invoicePayment.getPaymentAttemptDate().toDate());
+        }
+    }
+
+
+    public static class InvoicePaymentMapper implements ResultSetMapper<InvoicePayment> {
+
+        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 InvoicePayment map(int index, ResultSet rs, StatementContext ctx) throws SQLException {
+
+            UUID invoiceId = UUID.fromString(rs.getString("invoice_id"));
+            BigDecimal amount = rs.getBigDecimal("amount");
+            Currency currency = Currency.valueOf(rs.getString("currency"));
+            UUID paymentAttemptId = UUID.fromString(rs.getString("payment_attempt_id"));
+            DateTime paymentAttemptDate = getDate(rs, "payment_attempt_date");
+
+            return new InvoicePayment(invoiceId, amount, currency, paymentAttemptId, paymentAttemptDate);
+        }
+    }
 }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
index 54990c5..0e1c657 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/glue/InvoiceModule.java
@@ -18,14 +18,14 @@ package com.ning.billing.invoice.glue;
 
 import com.google.inject.AbstractModule;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.api.invoice.DefaultInvoicePaymentApi;
 import com.ning.billing.invoice.api.user.DefaultInvoiceUserApi;
-import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.dao.DefaultInvoiceDao;
 import com.ning.billing.invoice.dao.InvoiceDao;
 
 public class InvoiceModule extends AbstractModule {
-    private void installInvoiceDao() {
+    protected void installInvoiceDao() {
         bind(InvoiceDao.class).to(DefaultInvoiceDao.class).asEagerSingleton();
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
index 97f82e0..17926f7 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/DefaultInvoice.java
@@ -16,26 +16,27 @@
 
 package com.ning.billing.invoice.model;
 
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.Invoice;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.util.clock.DefaultClock;
-import org.joda.time.DateTime;
-
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.util.clock.DefaultClock;
+
 public class DefaultInvoice implements Invoice {
     private final InvoiceItemList items = new InvoiceItemList();
     private final UUID id;
-    private UUID accountId;
+    private final UUID accountId;
     private final DateTime invoiceDate;
     private final DateTime targetDate;
-    private Currency currency;
-    private BigDecimal amountPaid;
-    private DateTime lastPaymentAttempt;
+    private final Currency currency;
+    private final BigDecimal amountPaid;
+    private final DateTime lastPaymentAttempt;
 
     public DefaultInvoice(UUID accountId, DateTime targetDate, Currency currency) {
         this(UUID.randomUUID(), accountId, new DefaultClock().getUTCNow(), targetDate, currency, null, BigDecimal.ZERO, new ArrayList<InvoiceItem>());
@@ -136,5 +137,11 @@ public class DefaultInvoice implements Invoice {
 
         return lastPaymentAttempt.plusDays(numberOfDays).isBefore(targetDate);
     }
+
+    @Override
+    public String toString() {
+        return "DefaultInvoice [items=" + items + ", id=" + id + ", accountId=" + accountId + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + amountPaid + ", lastPaymentAttempt=" + lastPaymentAttempt + "]";
+    }
+
 }
 
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
index 8766e18..1657f7e 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/com/ning/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -2,7 +2,7 @@ group InvoiceDao;
 
 get() ::= <<
   SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+         SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
   FROM invoices i
   LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
   LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
@@ -12,7 +12,7 @@ get() ::= <<
 
 getInvoicesByAccount() ::= <<
   SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+         SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
   FROM invoices i
   LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
   LEFT JOIN invoice_items ii ON ii.invoice_id = i.id
@@ -23,7 +23,7 @@ getInvoicesByAccount() ::= <<
 
 getInvoicesBySubscription() ::= <<
   SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+         SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
   FROM invoices i
   LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
   LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
@@ -44,7 +44,7 @@ getInvoicesForPayment() ::= <<
 
 getById() ::= <<
   SELECT i.id, i.account_id, i.invoice_date, i.target_date, i.currency, SUM(ii.amount) AS amount,
-         SUM(ip.amount) AS amount_paid, MAX(ip.payment_date) AS last_payment_attempt
+         SUM(ip.amount) AS amount_paid, MAX(ip.payment_attempt_date) AS last_payment_attempt
   FROM invoices i
   LEFT JOIN invoice_items ii ON i.id = ii.invoice_id
   LEFT JOIN invoice_payments ip ON ip.invoice_id = i.id
@@ -57,20 +57,28 @@ create() ::= <<
   VALUES (:id, :accountId, :invoiceDate, :targetDate, :currency);
 >>
 
+getInvoiceIdByPaymentAttemptId() ::= <<
+  SELECT i.id
+    FROM invoices i, invoice_payments ip
+   WHERE ip.invoice_id = i.id
+     AND ip.payment_attempt_id = :paymentAttemptId
+>>
+
 update() ::= <<
   UPDATE invoices
   SET account_id = :accountId, invoice_date = :invoiceDate, target_date = :targetDate, currency = :currency
   WHERE id = :id;
 >>
 
-notifySuccessfulPayment() ::= <<
-  INSERT INTO invoice_payments(invoice_id, payment_id, payment_date, amount, currency)
-  VALUES(:invoiceId, :paymentId, :paymentDate, :amount, :currency);
+notifyOfPaymentAttempt() ::= <<
+  INSERT INTO invoice_payments(invoice_id, payment_attempt_id, payment_attempt_date, amount, currency, created_date, updated_date)
+  VALUES(:invoice_id, :payment_attempt_id, :payment_attempt_date, :amount, :currency, NOW(), NOW());
 >>
 
-notifyFailedPayment() ::= <<
-  INSERT INTO invoice_payments(invoice_id, payment_id, payment_date)
-  VALUES(:invoiceId, :paymentId, :paymentAttemptDate);
+getInvoicePayment() ::= <<
+    SELECT invoice_id, payment_attempt_id, payment_attempt_date, amount, currency, created_date, updated_date
+      FROM invoice_payments
+     WHERE payment_id = :payment_id
 >>
 
 test() ::= <<
diff --git a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
index 5d8b44f..651e9d4 100644
--- a/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/com/ning/billing/invoice/ddl.sql
@@ -27,17 +27,19 @@ CREATE INDEX invoices_account_id ON invoices(account_id ASC);
 DROP TABLE IF EXISTS invoice_payments;
 CREATE TABLE invoice_payments (
   invoice_id char(36) NOT NULL,
-  payment_id char(36) NOT NULL,
-  payment_date datetime NOT NULL,
+  payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
+  payment_attempt_date datetime,
   amount numeric(10,4),
   currency char(3),
-  PRIMARY KEY(invoice_id, payment_id)
+  created_date datetime NOT NULL,
+  updated_date datetime NOT NULL,
+  PRIMARY KEY(invoice_id, payment_attempt_id)
 ) ENGINE=innodb;
-CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_id);
+CREATE UNIQUE INDEX invoice_payments_unique ON invoice_payments(invoice_id, payment_attempt_id);
 
 DROP VIEW IF EXISTS invoice_payment_summary;
 CREATE VIEW invoice_payment_summary AS
-SELECT invoice_id, SUM(amount) AS total_paid, MAX(payment_date) AS last_payment_date
+SELECT invoice_id, SUM(amount) AS total_paid, MAX(payment_attempt_date) AS last_payment_date
 FROM invoice_payments
 GROUP BY invoice_id;
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
new file mode 100644
index 0000000..37d5f0c
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/MockInvoicePaymentApi.java
@@ -0,0 +1,102 @@
+/*
+ * 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.invoice.api;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.InvoicePayment;
+
+public class MockInvoicePaymentApi implements InvoicePaymentApi
+{
+    private final CopyOnWriteArrayList<Invoice> invoices = new CopyOnWriteArrayList<Invoice>();
+    private final CopyOnWriteArrayList<InvoicePayment> invoicePayments = new CopyOnWriteArrayList<InvoicePayment>();
+
+    public void add(Invoice invoice) {
+        invoices.add(invoice);
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+        for (InvoicePayment existingInvoicePayment : invoicePayments) {
+            if (existingInvoicePayment.getInvoiceId().equals(invoicePayment.getInvoiceId()) && existingInvoicePayment.getPaymentAttemptId().equals(invoicePayment.getPaymentAttemptId())) {
+                invoicePayments.remove(existingInvoicePayment);
+            }
+        }
+        invoicePayments.add(invoicePayment);
+    }
+
+    @Override
+    public List<Invoice> getInvoicesByAccount(UUID accountId) {
+        ArrayList<Invoice> result = new ArrayList<Invoice>();
+
+        for (Invoice invoice : invoices) {
+            if (accountId.equals(invoice.getAccountId())) {
+                result.add(invoice);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public Invoice getInvoice(UUID invoiceId) {
+        for (Invoice invoice : invoices) {
+            if (invoiceId.equals(invoice.getId())) {
+                return invoice;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Invoice getInvoiceForPaymentAttemptId(UUID paymentAttemptId) {
+        for (InvoicePayment invoicePayment : invoicePayments) {
+            if (invoicePayment.getPaymentAttemptId().equals(paymentAttemptId)) {
+                return getInvoice(invoicePayment.getInvoiceId());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        for (InvoicePayment invoicePayment : invoicePayments) {
+            if (paymentAttemptId.equals(invoicePayment.getPaymentAttemptId())) {
+                return invoicePayment;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(UUID invoiceId, BigDecimal amountOutstanding, Currency currency, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+        InvoicePayment invoicePayment = new InvoicePayment(invoiceId, amountOutstanding, currency, paymentAttemptId, paymentAttemptDate);
+        notifyOfPaymentAttempt(invoicePayment);
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(UUID invoiceId, UUID paymentAttemptId, DateTime paymentAttemptDate) {
+        InvoicePayment invoicePayment = new InvoicePayment(invoiceId, null, null, paymentAttemptId, paymentAttemptDate);
+        notifyOfPaymentAttempt(invoicePayment);
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
index 2e3a3ef..ed5497f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTestBase.java
@@ -16,29 +16,32 @@
 
 package com.ning.billing.invoice.dao;
 
+import static org.testng.Assert.fail;
+
 import java.io.IOException;
+
 import org.apache.commons.io.IOUtils;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
+
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
-import com.ning.billing.invoice.glue.InvoiceModuleMock;
+import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
 import com.ning.billing.util.eventbus.DefaultEventBusService;
 import com.ning.billing.util.eventbus.EventBusService;
 
-import static org.testng.Assert.fail;
-
 public abstract class InvoiceDaoTestBase {
     protected InvoiceDao invoiceDao;
     protected InvoiceItemSqlDao invoiceItemDao;
-    private InvoiceModuleMock module;
+    private InvoiceModuleWithEmbeddedDb module;
 
     @BeforeClass()
     protected void setup() throws IOException {
         // Health check test to make sure MySQL is setup properly
         try {
-            module = new InvoiceModuleMock();
+
+            module = new InvoiceModuleWithEmbeddedDb();
             final String ddl = IOUtils.toString(DefaultInvoiceDao.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
             module.createDb(ddl);
 
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
index 3fe72ed..c840928 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceDaoTests.java
@@ -16,25 +16,28 @@
 
 package com.ning.billing.invoice.dao;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
+
 import org.joda.time.DateTime;
-import org.joda.time.Days;
+import org.joda.time.DateTimeZone;
 import org.testng.annotations.Test;
+
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.payment.api.InvoicePayment;
 import com.ning.billing.util.clock.DefaultClock;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-
 @Test(groups = {"invoicing", "invoicing-invoiceDao"})
 public class InvoiceDaoTests extends InvoiceDaoTestBase {
     private final int NUMBER_OF_DAY_BETWEEN_RETRIES = 8;
@@ -78,8 +81,9 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(savedInvoice.getItems().size(), 1);
 
         BigDecimal paymentAmount = new BigDecimal("11.00");
-        String paymentId = UUID.randomUUID().toString();
-        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), paymentAmount, Currency.USD.toString(), paymentId, new DefaultClock().getUTCNow().plusDays(12).toDate());
+        UUID paymentAttemptId = UUID.randomUUID();
+
+        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoiceId, paymentAmount, Currency.USD, paymentAttemptId, new DateTime(DateTimeZone.UTC).plusDays(12)));
 
         Invoice retrievedInvoice = invoiceDao.getById(invoiceId.toString());
         assertNotNull(retrievedInvoice);
@@ -101,12 +105,13 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
         Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
 
-        String paymentId = UUID.randomUUID().toString();
+        UUID paymentAttemptId = UUID.randomUUID();
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
         BigDecimal paymentAmount = new BigDecimal("14.0");
 
         invoiceDao.create(invoice);
-        invoiceDao.notifySuccessfulPayment(invoice.getId().toString(), paymentAmount, Currency.USD.toString(), paymentId, paymentAttemptDate.toDate());
+
+        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), paymentAmount, Currency.USD, paymentAttemptId, paymentAttemptDate));
 
         invoice = invoiceDao.getById(invoice.getId().toString());
         assertEquals(invoice.getAmountPaid().compareTo(paymentAmount), 0);
@@ -122,7 +127,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         DateTime paymentAttemptDate = new DateTime(2011, 6, 24, 12, 14, 36, 0);
 
         invoiceDao.create(invoice);
-        invoiceDao.notifyFailedPayment(invoice.getId().toString(), UUID.randomUUID().toString(), paymentAttemptDate.toDate());
+        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), UUID.randomUUID(), paymentAttemptDate));
 
         invoice = invoiceDao.getById(invoice.getId().toString());
         assertTrue(invoice.getLastPaymentAttempt().equals(paymentAttemptDate));
@@ -132,11 +137,11 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
     public void testGetInvoicesForPaymentWithNoResults() {
         DateTime notionalDate = new DateTime();
         DateTime targetDate = new DateTime(2011, 10, 6, 0, 0, 0, 0);
-        
+
         // determine the number of existing invoices available for payment (to avoid side effects from other tests)
         List<UUID> invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
         int existingInvoiceCount = invoices.size();
-        
+
         UUID accountId = UUID.randomUUID();
         Invoice invoice = new DefaultInvoice(accountId, targetDate, Currency.USD);
 
@@ -174,7 +179,7 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
 
         // attempt a payment; ensure that the number of invoices for payment has decreased by 1
         // (no retries for NUMBER_OF_DAYS_BETWEEN_RETRIES days)
-        invoiceDao.notifyFailedPayment(invoice.getId().toString(), UUID.randomUUID().toString(), notionalDate.toDate());
+        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), UUID.randomUUID(), notionalDate));
         invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
@@ -187,7 +192,8 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(invoices.size(), count);
 
         // post successful partial payment; ensure that number of invoices for payment has decreased by 1
-        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), new BigDecimal("22.0000"), Currency.USD.toString(), UUID.randomUUID().toString(), notionalDate.toDate());
+        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), new BigDecimal("22.0000"), Currency.USD, UUID.randomUUID(), notionalDate));
+
         invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
@@ -204,7 +210,8 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         assertEquals(invoices.size(), count);
 
         // post completed payment; ensure that the number of invoices for payment has decreased by 1
-        invoiceDao.notifySuccessfulPayment(invoiceId.toString(), new BigDecimal("5.0000"), Currency.USD.toString(), UUID.randomUUID().toString(), notionalDate.toDate());
+        invoiceDao.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(), new BigDecimal("5.0000"), Currency.USD, UUID.randomUUID(), notionalDate));
+
         invoices = invoiceDao.getInvoicesForPayment(notionalDate.toDate(), NUMBER_OF_DAY_BETWEEN_RETRIES);
         count = getInvoicesDueForPaymentAttempt(invoiceDao.get(), notionalDate).size();
         assertEquals(invoices.size(), count);
@@ -298,5 +305,5 @@ public class InvoiceDaoTests extends InvoiceDaoTestBase {
         List<Invoice> items4 = invoiceDao.getInvoicesBySubscription(subscriptionId4.toString());
         assertEquals(items4.size(), 1);
     }
-    
+
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
index fa743ff..dc2afd8 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/InvoiceItemDaoTests.java
@@ -21,7 +21,7 @@ import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.glue.InvoiceModuleMock;
+import com.ning.billing.invoice.glue.InvoiceModuleWithEmbeddedDb;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.DefaultInvoiceItem;
 import org.apache.commons.io.IOUtils;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
new file mode 100644
index 0000000..446cc2b
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -0,0 +1,196 @@
+/*
+ * 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.invoice.dao;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+
+import com.google.inject.Inject;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.user.DefaultInvoiceCreationNotification;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.payment.api.InvoicePayment;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.EventBus.EventBusException;
+
+public class MockInvoiceDao implements InvoiceDao {
+    private final EventBus eventBus;
+    private final Object monitor = new Object();
+    private final Map<String, Invoice> invoices = new LinkedHashMap<String, Invoice>();
+    private final Map<UUID, InvoicePayment> invoicePayments = new LinkedHashMap<UUID, InvoicePayment>();
+
+    @Inject
+    public MockInvoiceDao(EventBus eventBus) {
+        this.eventBus = eventBus;
+    }
+
+    @Override
+    public void create(Invoice invoice) {
+        synchronized (monitor) {
+            invoices.put(invoice.getId().toString(), invoice);
+        }
+        try {
+            eventBus.post(new DefaultInvoiceCreationNotification(invoice.getId(), invoice.getAccountId(),
+                                                                 invoice.getAmountOutstanding(), invoice.getCurrency(),
+                                                                 invoice.getInvoiceDate()));
+        }
+        catch (EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private Invoice munge(Invoice invoice) {
+        if (invoice == null) {
+            return null;
+        }
+
+        DateTime lastPaymentDate = null;
+        BigDecimal amountPaid = new BigDecimal("0");
+
+        for (InvoicePayment invoicePayment : invoicePayments.values()) {
+            if (invoicePayment.getInvoiceId().equals(invoice.getId().toString())) {
+                if (lastPaymentDate == null || lastPaymentDate.isBefore(invoicePayment.getPaymentAttemptDate())) {
+                    lastPaymentDate = invoicePayment.getPaymentAttemptDate();
+                }
+                if (invoicePayment.getAmount() != null) {
+                    amountPaid.add(invoicePayment.getAmount());
+                }
+            }
+        }
+        return new DefaultInvoice(invoice.getId(),
+                                  invoice.getAccountId(),
+                                  invoice.getInvoiceDate(),
+                                  invoice.getTargetDate(),
+                                  invoice.getCurrency(),
+                                  lastPaymentDate,
+                                  amountPaid,
+                                  invoice.getItems());
+    }
+
+    private List<Invoice> munge(Collection<Invoice> invoices) {
+        List<Invoice> result = new ArrayList<Invoice>();
+        for (Invoice invoice : invoices) {
+            result.add(munge(invoice));
+        }
+        return result;
+    }
+
+    @Override
+    public Invoice getById(String id) {
+        synchronized (monitor) {
+            return munge(invoices.get(id));
+        }
+    }
+
+    @Override
+    public List<Invoice> get() {
+        synchronized (monitor) {
+            return munge(invoices.values());
+        }
+    }
+
+    @Override
+    public List<Invoice> getInvoicesByAccount(String accountId) {
+        List<Invoice> result = new ArrayList<Invoice>();
+
+        synchronized (monitor) {
+            for (Invoice invoice : invoices.values()) {
+                if (accountId.equals(invoice.getAccountId().toString())) {
+                    result.add(invoice);
+                }
+            }
+        }
+        return munge(result);
+    }
+
+    @Override
+    public List<Invoice> getInvoicesBySubscription(String subscriptionId) {
+        List<Invoice> result = new ArrayList<Invoice>();
+
+        synchronized (monitor) {
+            for (Invoice invoice : invoices.values()) {
+                for (InvoiceItem item : invoice.getItems()) {
+                    if (subscriptionId.equals(item.getSubscriptionId().toString())) {
+                        result.add(invoice);
+                        break;
+                    }
+                }
+            }
+        }
+        return munge(result);
+    }
+
+    @Override
+    public List<UUID> getInvoicesForPayment(Date targetDate, int numberOfDays) {
+        Set<UUID> result = new LinkedHashSet<UUID>();
+
+        synchronized (monitor) {
+            for (InvoicePayment invoicePayment : invoicePayments.values()) {
+                Invoice invoice = invoices.get(invoicePayment.getInvoiceId());
+                if ((invoice != null) &&
+                    (((invoicePayment.getPaymentAttemptDate() == null) || !invoicePayment.getPaymentAttemptDate().plusDays(numberOfDays).isAfter(targetDate.getTime())) &&
+                    (invoice.getTotalAmount() != null) && invoice.getTotalAmount().doubleValue() >= 0) &&
+                    ((invoicePayment.getAmount() == null) || invoicePayment.getAmount().doubleValue() >= invoice.getTotalAmount().doubleValue())) {
+                        result.add(invoice.getId());
+                }
+            }
+        }
+
+        return new ArrayList<UUID>(result);
+    }
+
+    @Override
+    public void test() {
+    }
+
+    @Override
+    public String getInvoiceIdByPaymentAttemptId(UUID paymentAttemptId) {
+        synchronized(monitor) {
+            for (InvoicePayment invoicePayment : invoicePayments.values()) {
+                if (paymentAttemptId.toString().equals(invoicePayment.getPaymentAttemptId())) {
+                    return invoicePayment.getInvoiceId().toString();
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public InvoicePayment getInvoicePayment(UUID paymentAttemptId) {
+        synchronized(monitor) {
+            return invoicePayments.get(paymentAttemptId);
+        }
+    }
+
+    @Override
+    public void notifyOfPaymentAttempt(InvoicePayment invoicePayment) {
+      synchronized (monitor) {
+          invoicePayments.put(invoicePayment.getPaymentAttemptId(), invoicePayment);
+      }
+    }
+}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
new file mode 100644
index 0000000..d01301b
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/glue/InvoiceModuleWithMocks.java
@@ -0,0 +1,28 @@
+/*
+ * 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.invoice.glue;
+
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+
+public class InvoiceModuleWithMocks extends InvoiceModule {
+    @Override
+    protected void installInvoiceDao() {
+        bind(MockInvoiceDao.class).asEagerSingleton();
+        bind(InvoiceDao.class).to(MockInvoiceDao.class);
+    }
+}

payment/pom.xml 98(+96 -2)

diff --git a/payment/pom.xml b/payment/pom.xml
index 3d6356e..f7ebbc4 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -20,7 +20,101 @@
     <name>killbill-payment</name>
     <packaging>jar</packaging>
     <dependencies>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-invoice</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-multibindings</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.jayway.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-invoice</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+             <groupId>com.mysql</groupId>
+             <artifactId>management</artifactId>
+             <scope>test</scope>
+         </dependency>
+         <dependency>
+             <groupId>com.mysql</groupId>
+             <artifactId>management-dbfiles</artifactId>
+             <scope>test</scope>
+         </dependency>
     </dependencies>
-    <build>
-    </build>
 </project>
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
new file mode 100644
index 0000000..5674972
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -0,0 +1,196 @@
+/*
+ * 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.api;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.InvoicePaymentApi;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.PaymentProviderPlugin;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+
+public class DefaultPaymentApi implements PaymentApi {
+    private final PaymentProviderPluginRegistry pluginRegistry;
+    private final AccountUserApi accountUserApi;
+    private final InvoicePaymentApi invoicePaymentApi;
+    private final PaymentDao paymentDao;
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultPaymentApi.class);
+
+    @Inject
+    public DefaultPaymentApi(PaymentProviderPluginRegistry pluginRegistry,
+                             AccountUserApi accountUserApi,
+                             InvoicePaymentApi invoicePaymentApi,
+                             PaymentDao paymentDao) {
+        this.pluginRegistry = pluginRegistry;
+        this.accountUserApi = accountUserApi;
+        this.invoicePaymentApi = invoicePaymentApi;
+        this.paymentDao = paymentDao;
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> getPaymentMethod(@Nullable String accountKey, String paymentMethodId) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.getPaymentMethodInfo(paymentMethodId);
+    }
+
+    private PaymentProviderPlugin getPaymentProviderPlugin(String accountKey) {
+        String paymentProviderName = null;
+
+        if (accountKey != null) {
+            final Account account = accountUserApi.getAccountByKey(accountKey);
+            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);
+    }
+
+    @Override
+    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.getPaymentMethods(accountKey);
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.updatePaymentGateway(accountKey);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.getPaymentProviderAccount(accountKey);
+    }
+
+    @Override
+    public Either<PaymentError, String> addPaypalPaymentMethod(@Nullable String accountKey, PaypalPaymentMethodInfo paypalPaymentMethod) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.addPaypalPaymentMethod(accountKey, paypalPaymentMethod);
+    }
+
+    @Override
+    public Either<PaymentError, Void> deletePaymentMethod(String accountKey, String paymentMethodId) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.deletePaypalPaymentMethod(accountKey, paymentMethodId);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> updatePaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin(accountKey);
+        return plugin.updatePaypalPaymentMethod(accountKey, paymentMethodInfo);
+    }
+
+    @Override
+    public List<Either<PaymentError, PaymentInfo>> createPayment(String accountKey, List<String> invoiceIds) {
+        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));
+
+            if (invoice.getAmountOutstanding().compareTo(BigDecimal.ZERO) == 0 ) {
+            // TODO: send a notification that invoice was ignored?
+                log.info("Received invoice for payment with outstanding amount of 0 {} ", invoice);
+            }
+            else if (invoiceId.equals(paymentDao.getPaymentAttemptForInvoiceId(invoiceId))) {
+                //TODO: do equals on invoice instead and only reject when invoice is exactly the same?
+                log.info("Duplicate invoice payment event, already received invoice {} ", invoice);
+            }
+            else {
+                PaymentAttempt paymentAttempt = paymentDao.createPaymentAttempt(invoice);
+                Either<PaymentError, PaymentInfo> paymentOrError = plugin.processInvoice(account, invoice);
+                processedPaymentsOrErrors.add(paymentOrError);
+
+                PaymentInfo paymentInfo = null;
+
+                if (paymentOrError.isRight()) {
+                    paymentInfo = paymentOrError.getRight();
+
+                    paymentDao.savePaymentInfo(paymentInfo);
+
+                    if (paymentInfo.getPaymentId() != null) {
+                        paymentDao.updatePaymentAttemptWithPaymentId(paymentAttempt.getPaymentAttemptId(), paymentInfo.getPaymentId());
+                    }
+                }
+
+                invoicePaymentApi.notifyOfPaymentAttempt(new InvoicePayment(invoice.getId(),
+                                                                     paymentInfo == null ? null : paymentInfo.getAmount(),
+//                                                                   paymentInfo.getRefundAmount(), TODO
+                                                                     paymentInfo == null ? null : invoice.getCurrency(),
+                                                                     paymentAttempt.getPaymentAttemptId(),
+                                                                     paymentAttempt.getPaymentAttemptDate()));
+
+            }
+        }
+
+        return processedPaymentsOrErrors;
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> createPaymentProviderAccount(PaymentProviderAccount account) {
+        final PaymentProviderPlugin plugin = getPaymentProviderPlugin((Account)null);
+        return plugin.createPaymentProviderAccount(account);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> updatePaymentProviderAccount(PaymentProviderAccount account) {
+        //TODO
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForPaymentId(String id) {
+        return paymentDao.getPaymentAttemptForPaymentId(id);
+    }
+
+    @Override
+    public List<Either<PaymentError, PaymentInfo>> createRefund(Account account, List<String> invoiceIds) {
+        //TODO
+        throw new UnsupportedOperationException();
+    }
+
+}
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..ceba7a7
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/DefaultPaymentDao.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.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 getPaymentAttemptForInvoiceId(String invoiceId) {
+        return sqlDao.getPaymentAttemptForInvoiceId(invoiceId);
+    }
+
+    @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 paymentId) {
+        sqlDao.updatePaymentAttemptWithPaymentId(paymentAttemptId.toString(), paymentId);
+    }
+
+}
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
new file mode 100644
index 0000000..d40b264
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentDao.java
@@ -0,0 +1,37 @@
+/*
+ * 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 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);
+
+    PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId);
+
+}
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..7cd8adb
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/dao/PaymentSqlDao.java
@@ -0,0 +1,169 @@
+/*
+ * 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.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("payment_id") String paymentId);
+
+    @SqlQuery
+    @Mapper(PaymentAttemptMapper.class)
+    PaymentAttempt getPaymentAttemptForInvoiceId(@Bind("invoice_id") String invoiceId);
+
+    @SqlUpdate
+    void updatePaymentAttemptWithPaymentId(@Bind("payment_attempt_id") String paymentAttemptId,
+                                           @Bind("payment_id") String payment_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) {
+            stmt.bind("payment_attempt_id", paymentAttempt.getPaymentAttemptId().toString());
+            stmt.bind("invoice_id", paymentAttempt.getInvoiceId().toString());
+            stmt.bind("account_id", paymentAttempt.getAccountId().toString());
+            stmt.bind("amount", paymentAttempt.getAmount()); //TODO: suppport partial payments
+            stmt.bind("currency", paymentAttempt.getCurrency().toString());
+            stmt.bind("invoice_dt", getDate(paymentAttempt.getInvoiceDate()));
+            stmt.bind("payment_attempt_dt", getDate(paymentAttempt.getPaymentAttemptDate()));
+            stmt.bind("payment_id", paymentAttempt.getPaymentId());
+            stmt.bind("created_dt", getDate(paymentAttempt.getCreatedDate()));
+            stmt.bind("updated_dt", getDate(paymentAttempt.getUpdatedDate()));
+        }
+    }
+
+    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("payment_attempt_id"));
+            UUID invoiceId = UUID.fromString(rs.getString("invoice_id"));
+            UUID accountId = UUID.fromString(rs.getString("account_id"));
+            BigDecimal amount = rs.getBigDecimal("amount");
+            Currency currency = Currency.valueOf(rs.getString("currency"));
+            DateTime invoiceDate = getDate(rs, "invoice_dt");
+            DateTime paymentAttemptDate = getDate(rs, "payment_attempt_dt");
+            String paymentId = rs.getString("payment_id");
+            DateTime createdDate = getDate(rs, "created_dt");
+            DateTime updatedDate = getDate(rs, "updated_dt");
+
+            return new PaymentAttempt(paymentAttemptId, invoiceId, accountId, amount, currency, invoiceDate, paymentAttemptDate, paymentId, createdDate, updatedDate);
+        }
+    }
+
+    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("payment_id", paymentInfo.getPaymentId().toString());
+            stmt.bind("amount", paymentInfo.getAmount());
+            stmt.bind("refund_amount", paymentInfo.getRefundAmount());
+            stmt.bind("payment_number", paymentInfo.getPaymentNumber());
+            stmt.bind("bank_identification_number", paymentInfo.getBankIdentificationNumber());
+            stmt.bind("status", paymentInfo.getStatus());
+            stmt.bind("payment_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 paymentId = rs.getString("payment_id");
+            BigDecimal amount = rs.getBigDecimal("amount");
+            BigDecimal refundAmount = rs.getBigDecimal("refund_amount");
+            String paymentNumber = rs.getString("payment_number");
+            String bankIdentificationNumber = rs.getString("bank_identification_number");
+            String status = rs.getString("status");
+            String type = rs.getString("payment_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(paymentId,
+                                   amount,
+                                   refundAmount,
+                                   bankIdentificationNumber,
+                                   paymentNumber,
+                                   status,
+                                   type,
+                                   referenceId,
+                                   effectiveDate,
+                                   createdDate,
+                                   updatedDate);
+        }
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
new file mode 100644
index 0000000..353587d
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/PaymentAttempt.java
@@ -0,0 +1,67 @@
+/*
+ * 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 java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.invoice.api.Invoice;
+
+public class PaymentAttempt {
+    private final UUID paymentAttemptId;
+    private final UUID accountId;
+    private final UUID invoiceId;
+    private final BigDecimal paymentAttemptAmount;
+    private final DateTime paymentAttemptDate;
+
+    public PaymentAttempt(UUID paymentAttemptId, Invoice invoice) {
+        this.paymentAttemptId = paymentAttemptId;
+        this.accountId = invoice.getAccountId();
+        this.invoiceId = invoice.getId();
+        this.paymentAttemptAmount = invoice.getAmountOutstanding();
+        this.paymentAttemptDate = new DateTime(DateTimeZone.UTC);
+    }
+
+    public UUID getPaymentAttemptId() {
+        return paymentAttemptId;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    public BigDecimal getPaymentAttemptAmount() {
+        return paymentAttemptAmount;
+    }
+
+        public DateTime getPaymentAttemptDate() {
+            return paymentAttemptDate;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentAttempt [paymentAttemptId=" + paymentAttemptId + ", accountId=" + accountId + ", invoiceId=" + invoiceId + ", paymentAttemptAmount=" + paymentAttemptAmount + "]";
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/PaymentInfoRequest.java b/payment/src/main/java/com/ning/billing/payment/PaymentInfoRequest.java
new file mode 100644
index 0000000..22098ed
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/PaymentInfoRequest.java
@@ -0,0 +1,45 @@
+/*
+ * 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 java.util.UUID;
+
+import com.ning.billing.util.eventbus.EventBusNotification;
+
+public class PaymentInfoRequest implements EventBusNotification {
+    private final UUID accountId;
+    private final String paymentId;
+
+    public PaymentInfoRequest(UUID accountId, String paymentId) {
+        this.accountId = accountId;
+        this.paymentId = paymentId;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public String getPaymentId() {
+        return paymentId;
+    }
+
+    @Override
+    public String toString() {
+        return "PaymentInfoRequest [accountId=" + accountId + ", paymentId=" + paymentId + "]";
+    }
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
new file mode 100644
index 0000000..4f19eb7
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPlugin.java
@@ -0,0 +1,44 @@
+/*
+ * 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.provider;
+
+import java.util.List;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.Either;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentMethodInfo;
+import com.ning.billing.payment.api.PaymentProviderAccount;
+import com.ning.billing.payment.api.PaypalPaymentMethodInfo;
+
+public interface PaymentProviderPlugin {
+    Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice);
+    Either<PaymentError, PaymentProviderAccount> createPaymentProviderAccount(PaymentProviderAccount account);
+    Either<PaymentError, String> addPaypalPaymentMethod(String accountId, PaypalPaymentMethodInfo paypalPaymentMethod);
+
+    Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId);
+    Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId);
+    Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey);
+    Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountKey);
+
+    Either<PaymentError, Void> updatePaymentGateway(String accountKey);
+    Either<PaymentError, Void> deletePaypalPaymentMethod(String accountKey, String paymentMethodId);
+    Either<PaymentError, PaymentMethodInfo> updatePaypalPaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo);
+
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
new file mode 100644
index 0000000..f7778d8
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/provider/PaymentProviderPluginRegistry.java
@@ -0,0 +1,43 @@
+/*
+ * 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.provider;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.google.inject.Inject;
+import com.ning.billing.payment.setup.PaymentConfig;
+
+public class PaymentProviderPluginRegistry {
+    private final String defaultPlugin;
+    private final Map<String, PaymentProviderPlugin> pluginsByName = new ConcurrentHashMap<String, PaymentProviderPlugin>();
+
+    @Inject
+    public PaymentProviderPluginRegistry(PaymentConfig config) {
+        this.defaultPlugin = config.getDefaultPaymentProvider();
+    }
+
+    public void register(PaymentProviderPlugin plugin, String name) {
+        pluginsByName.put(name.toLowerCase(), plugin);
+    }
+
+    public PaymentProviderPlugin getPlugin(String name) {
+        return pluginsByName.get(StringUtils.defaultIfEmpty(name, defaultPlugin).toLowerCase());
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
new file mode 100644
index 0000000..466f297
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/RequestProcessor.java
@@ -0,0 +1,100 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.InvoiceCreationNotification;
+import com.ning.billing.payment.api.Either;
+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;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.EventBus.EventBusException;
+
+public class RequestProcessor {
+    public static final String PAYMENT_PROVIDER_KEY = "paymentProvider";
+    private final AccountUserApi accountUserApi;
+    private final PaymentApi paymentApi;
+    private final PaymentProviderPluginRegistry pluginRegistry;
+    private final EventBus eventBus;
+
+    private static final Logger log = LoggerFactory.getLogger(RequestProcessor.class);
+
+    @Inject
+    public RequestProcessor(AccountUserApi accountUserApi,
+                            PaymentApi paymentApi,
+                            PaymentProviderPluginRegistry pluginRegistry,
+                            EventBus eventBus) {
+        this.accountUserApi = accountUserApi;
+        this.paymentApi = paymentApi;
+        this.pluginRegistry = pluginRegistry;
+        this.eventBus = eventBus;
+    }
+
+    @Subscribe
+    public void receiveInvoice(InvoiceCreationNotification event) {
+        log.info("Received invoice creation notification for account {} and invoice {}", event.getAccountId(), event.getInvoiceId());
+        try {
+            final Account account = accountUserApi.getAccountById(event.getAccountId());
+
+            if (account == null) {
+                log.info("could not process invoice payment: could not find a valid account for event {}", event);
+            }
+            else {
+                List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account, Arrays.asList(event.getInvoiceId().toString()));
+
+                if (results.isEmpty()) {
+                    eventBus.post(new PaymentError("unknown", "No payment processed"));
+                }
+                else {
+                    Either<PaymentError, PaymentInfo> result = results.get(0);
+                    eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
+                }
+            }
+        }
+        catch (EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Subscribe
+    public void receivePaymentInfoRequest(PaymentInfoRequest paymentInfoRequest) throws EventBusException {
+        final Account account = accountUserApi.getAccountById(paymentInfoRequest.getAccountId());
+        if (account == null) {
+            log.info("could not process payment info request: could not find a valid account for event {}", paymentInfoRequest);
+        }
+        else {
+            final String paymentProviderName = account.getFieldValue(PAYMENT_PROVIDER_KEY);
+            final PaymentProviderPlugin plugin = pluginRegistry.getPlugin(paymentProviderName);
+
+            Either<PaymentError, PaymentInfo> result = plugin.getPaymentInfo(paymentInfoRequest.getPaymentId());
+
+            eventBus.post(result.isLeft() ? result.getLeft() : result.getRight());
+        }
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
new file mode 100644
index 0000000..cdc5384
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentConfig.java
@@ -0,0 +1,26 @@
+/*
+ * 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.skife.config.Config;
+import org.skife.config.DefaultNull;
+
+public interface PaymentConfig {
+    @Config("killbill.payment.provider.default")
+    @DefaultNull
+    public String getDefaultPaymentProvider();
+}
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
new file mode 100644
index 0000000..3c05c13
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/setup/PaymentModule.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.setup;
+
+import java.util.Properties;
+
+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.DefaultPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
+
+public class PaymentModule extends AbstractModule {
+    private final Properties props;
+
+    public PaymentModule() {
+        this.props = System.getProperties();
+    }
+
+    public PaymentModule(Properties props) {
+        this.props = props;
+    }
+
+    protected void installPaymentDao() {
+        bind(PaymentDao.class).to(DefaultPaymentDao.class).asEagerSingleton();
+    }
+
+    protected void installPaymentProviderPlugins(PaymentConfig config) {
+    }
+
+    @Override
+    protected void configure() {
+        final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(props);
+        final PaymentConfig paymentConfig = factory.build(PaymentConfig.class);
+
+        bind(PaymentConfig.class).toInstance(paymentConfig);
+        bind(PaymentProviderPluginRegistry.class).asEagerSingleton();
+        bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
+        installPaymentProviderPlugins(paymentConfig);
+        installPaymentDao();
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
new file mode 100644
index 0000000..d82a9a7
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusFuture.java
@@ -0,0 +1,75 @@
+/*
+ * 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.util;
+
+import javax.annotation.Nullable;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.common.util.concurrent.AbstractFuture;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.EventBus.EventBusException;
+
+public class EventBusFuture<T, V extends EventBusResponse<T>> extends AbstractFuture<V> {
+    public static <V, W extends EventBusRequest<V>, X extends EventBusResponse<V>> EventBusFuture<V, X> post(final EventBus eventBus, final W event) throws EventBusException {
+        final EventBusFuture<V, X> responseFuture = new EventBusFuture<V, X>(eventBus, event.getId());
+
+        eventBus.register(responseFuture);
+        eventBus.post(event);
+        return responseFuture;
+    }
+
+    private final EventBus eventBus;
+    private final T requestId;
+
+    private EventBusFuture(EventBus eventBus, T requestId) {
+        this.eventBus = eventBus;
+        this.requestId = requestId;
+    }
+
+    @Subscribe
+    public void handleResponse(V response) {
+        if (requestId.equals(response.getRequestId())) {
+            set(response);
+        }
+    }
+
+    @Override
+    public boolean set(@Nullable V value) {
+        boolean result = super.set(value);
+
+        try {
+            eventBus.unregister(this);
+        }
+        catch (EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean setException(Throwable throwable) {
+        boolean result = super.setException(throwable);
+
+        try {
+            eventBus.unregister(this);
+        }
+        catch (EventBusException ex) {
+            throw new RuntimeException(ex);
+        }
+        return result;
+    }
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
new file mode 100644
index 0000000..b9dab5c
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusRequest.java
@@ -0,0 +1,23 @@
+/*
+ * 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.util;
+
+import com.ning.billing.util.eventbus.EventBusNotification;
+
+public interface EventBusRequest<T> extends EventBusNotification {
+    T getId();
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
new file mode 100644
index 0000000..d8a70b1
--- /dev/null
+++ b/payment/src/main/java/com/ning/billing/payment/util/EventBusResponse.java
@@ -0,0 +1,23 @@
+/*
+ * 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.util;
+
+import com.ning.billing.util.eventbus.EventBusNotification;
+
+public interface EventBusResponse<T> extends EventBusNotification {
+    T getRequestId();
+}
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..5156b03
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -0,0 +1,57 @@
+group PaymentSqlDao;
+
+paymentAttemptFields(prefix) ::= <<
+    <prefix>payment_attempt_id,
+    <prefix>invoice_id,
+    <prefix>account_id,
+    <prefix>amount,
+    <prefix>currency,
+    <prefix>payment_id,
+    <prefix>payment_attempt_dt,
+    <prefix>invoice_dt,
+    <prefix>created_dt,
+    <prefix>updated_dt
+>>
+
+paymentInfoFields(prefix) ::= <<
+    <prefix>payment_id,
+    <prefix>amount,
+    <prefix>refund_amount,
+    <prefix>bank_identification_number,
+    <prefix>payment_number,
+    <prefix>payment_type,
+    <prefix>status,
+    <prefix>reference_id,
+    <prefix>effective_dt,
+    <prefix>created_dt,
+    <prefix>updated_dt
+>>
+
+insertPaymentAttempt() ::= <<
+    INSERT INTO payment_attempts (<paymentAttemptFields()>)
+    VALUES (:payment_attempt_id, :invoice_id, :account_id, :amount, :currency, :payment_id, :payment_attempt_dt, :invoice_dt, :created_dt, :updated_dt);
+>>
+
+getPaymentAttemptForPaymentId() ::= <<
+    SELECT <paymentAttemptFields()>
+      FROM payment_attempts
+     WHERE payment_id = :payment_id
+>>
+
+getPaymentAttemptForInvoiceId() ::= <<
+    SELECT <paymentAttemptFields()>
+      FROM payment_attempts
+     WHERE invoice_id = :invoice_id
+>>
+
+updatePaymentAttemptWithPaymentId() ::= <<
+    UPDATE payment_attempts
+       SET payment_id = :payment_id,
+           updated_dt = NOW()
+     WHERE payment_attempt_id = :payment_attempt_id
+>>
+
+insertPaymentInfo() ::= <<
+    INSERT INTO payments (<paymentInfoFields()>)
+    VALUES (:payment_id, :amount, :refund_amount, :bank_identification_number, :payment_number, :payment_type, :status, :reference_id, :effective_dt, NOW(), NOW());
+>>
diff --git a/payment/src/main/resources/com/ning/billing/payment/ddl.sql b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
new file mode 100644
index 0000000..fd6a1ea
--- /dev/null
+++ b/payment/src/main/resources/com/ning/billing/payment/ddl.sql
@@ -0,0 +1,30 @@
+DROP TABLE IF EXISTS payment_attempts;
+CREATE TABLE payment_attempts (
+      payment_attempt_id char(36) COLLATE utf8_bin NOT NULL,
+      account_id char(36) COLLATE utf8_bin NOT NULL,
+      invoice_id char(36) COLLATE utf8_bin NOT NULL,
+      amount decimal(8,2),
+      currency char(3),
+      payment_attempt_dt datetime NOT NULL,
+      payment_id varchar(36) COLLATE utf8_bin,
+      invoice_dt datetime NOT NULL,
+      created_dt datetime NOT NULL,
+      updated_dt datetime NOT NULL,
+      PRIMARY KEY (payment_attempt_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
+
+DROP TABLE IF EXISTS payments; 
+CREATE TABLE payments (
+      payment_id varchar(36) COLLATE utf8_bin NOT NULL,
+      amount decimal(8,2),
+      refund_amount decimal(8,2),
+      payment_number varchar(36) COLLATE utf8_bin,
+      bank_identification_number varchar(36) COLLATE utf8_bin,
+      status varchar(20) COLLATE utf8_bin,
+      payment_type varchar(20) COLLATE utf8_bin,
+      reference_id varchar(36) COLLATE utf8_bin,
+      effective_dt datetime,
+      created_dt datetime NOT NULL,
+      updated_dt datetime NOT NULL,
+      PRIMARY KEY (payment_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
new file mode 100644
index 0000000..b9f761a
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestMockPaymentApi.java
@@ -0,0 +1,30 @@
+/*
+ * 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.api;
+
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.ning.billing.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+
+@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+@Test(groups = "fast")
+public class TestMockPaymentApi extends TestPaymentApi {
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
new file mode 100644
index 0000000..a1b02b3
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -0,0 +1,89 @@
+/*
+ * 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.api;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.model.DefaultInvoiceItem;
+import com.ning.billing.payment.TestHelper;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.EventBus.EventBusException;
+
+public abstract class TestPaymentApi {
+    @Inject
+    private EventBus eventBus;
+    @Inject
+    protected PaymentApi paymentApi;
+    @Inject
+    protected TestHelper testHelper;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        eventBus.start();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        eventBus.stop();
+    }
+
+//    @Test(groups = "fast")
+    @Test
+    public void testCreatePayment() throws AccountApiException {
+        final DateTime now = new DateTime();
+        final Account account = testHelper.createTestAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+        final BigDecimal amount = new BigDecimal("10.00");
+        final UUID subscriptionId = UUID.randomUUID();
+
+        invoice.add(new DefaultInvoiceItem(invoice.getId(),
+                                           subscriptionId,
+                                           now,
+                                           now.plusMonths(1),
+                                           "Test",
+                                           amount,
+                                           new BigDecimal("1.0"),
+                                           Currency.USD));
+
+        List<Either<PaymentError, PaymentInfo>> results = paymentApi.createPayment(account.getExternalKey(), Arrays.asList(invoice.getId().toString()));
+
+        assertEquals(results.size(), 1);
+        assertTrue(results.get(0).isRight());
+
+        PaymentInfo paymentInfo = results.get(0).getRight();
+
+        assertNotNull(paymentInfo.getPaymentId());
+        assertEquals(paymentInfo.getAmount().doubleValue(), amount.doubleValue());
+    }
+}
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..74d8552
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/dao/MockPaymentDao.java
@@ -0,0 +1,73 @@
+/*
+ * 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.getPaymentId(), paymentInfo);
+    }
+
+    @Override
+    public void updatePaymentAttemptWithPaymentId(UUID paymentAttemptId, String paymentId) {
+        PaymentAttempt existingPaymentAttempt = paymentAttempts.get(paymentAttemptId);
+
+        if (existingPaymentAttempt != null) {
+            paymentAttempts.put(existingPaymentAttempt.getPaymentAttemptId(),
+                                existingPaymentAttempt.cloner().setPaymentId(paymentId).build());
+        }
+    }
+
+    @Override
+    public PaymentAttempt getPaymentAttemptForInvoiceId(String invoiceId) {
+        for (PaymentAttempt paymentAttempt : paymentAttempts.values()) {
+            if (invoiceId.equals(paymentAttempt.getInvoiceId())) {
+                return paymentAttempt;
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/MockPaymentInfoReceiver.java b/payment/src/test/java/com/ning/billing/payment/MockPaymentInfoReceiver.java
new file mode 100644
index 0000000..cf2494d
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockPaymentInfoReceiver.java
@@ -0,0 +1,53 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+
+public class MockPaymentInfoReceiver {
+    private final List<PaymentInfo> processedPayments = Collections.synchronizedList(new ArrayList<PaymentInfo>());
+    private final List<PaymentError> errors = Collections.synchronizedList(new ArrayList<PaymentError>());
+
+    @Subscribe
+    public void processedPayment(PaymentInfo paymentInfo) {
+        processedPayments.add(paymentInfo);
+    }
+
+    @Subscribe
+    public void processedPaymentError(PaymentError paymentError) {
+        errors.add(paymentError);
+    }
+
+    public List<PaymentInfo> getProcessedPayments() {
+        return new ArrayList<PaymentInfo>(processedPayments);
+    }
+
+    public List<PaymentError> getErrors() {
+        return new ArrayList<PaymentError>(errors);
+    }
+
+    public void clear() {
+        processedPayments.clear();
+        errors.clear();
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..321631e
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -0,0 +1,125 @@
+/*
+ * 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.provider;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.Either;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.api.PaymentMethodInfo;
+import com.ning.billing.payment.api.PaymentProviderAccount;
+import com.ning.billing.payment.api.PaypalPaymentMethodInfo;
+
+public class MockPaymentProviderPlugin implements PaymentProviderPlugin {
+    private final Map<String, PaymentInfo> payments = new ConcurrentHashMap<String, PaymentInfo>();
+    private final Map<String, PaymentProviderAccount> accounts = new ConcurrentHashMap<String, PaymentProviderAccount>();
+
+    @Override
+    public Either<PaymentError, PaymentInfo> processInvoice(Account account, Invoice invoice) {
+        PaymentInfo payment = new PaymentInfo.Builder().setPaymentId(UUID.randomUUID().toString())
+                                            .setAmount(invoice.getAmountOutstanding())
+                                            .setStatus("Processed")
+                                            .setBankIdentificationNumber("1234")
+                                            .setCreatedDate(new DateTime())
+                                            .setEffectiveDate(new DateTime())
+                                            .setPaymentNumber("12345")
+                                            .setReferenceId("12345")
+                                            .setType("Electronic")
+                                            .build();
+
+        payments.put(payment.getPaymentId(), payment);
+        return Either.right(payment);
+    }
+
+    @Override
+    public Either<PaymentError, PaymentInfo> getPaymentInfo(String paymentId) {
+        PaymentInfo payment = payments.get(paymentId);
+
+        if (payment == null) {
+            return Either.left(new PaymentError("notfound", "No payment found for id " + paymentId));
+        }
+        else {
+            return Either.right(payment);
+        }
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> createPaymentProviderAccount(PaymentProviderAccount account) {
+        if (account != null) {
+            PaymentProviderAccount paymentProviderAccount = accounts.put(account.getAccountName(),
+                                                                         new PaymentProviderAccount.Builder().setAccountName(account.getAccountName())
+                                                                                                             .setAccountNumber(account.getAccountName())
+                                                                                                             .setId(account.getId())
+                                                                                                             .build());
+
+            return Either.right(paymentProviderAccount);
+        }
+        else {
+            return Either.left(new PaymentError("unknown", "Did not get account to create payment provider account"));
+        }
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> getPaymentMethodInfo(String paymentMethodId) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, List<PaymentMethodInfo>> getPaymentMethods(String accountId) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, Void> updatePaymentGateway(String accountKey) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, PaymentProviderAccount> getPaymentProviderAccount(String accountKey) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, String> addPaypalPaymentMethod(String accountId, PaypalPaymentMethodInfo paypalPaymentMethod) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, Void> deletePaypalPaymentMethod(String accountKey, String paymentMethodId) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+
+    @Override
+    public Either<PaymentError, PaymentMethodInfo> updatePaypalPaymentMethod(String accountKey, PaymentMethodInfo paymentMethodInfo) {
+        // TODO
+        return Either.left(new PaymentError("unknown", "Not implemented"));
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.java
new file mode 100644
index 0000000..441fcf8
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginModule.java
@@ -0,0 +1,36 @@
+/*
+ * 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.provider;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class MockPaymentProviderPluginModule extends AbstractModule {
+    private final String instanceName;
+
+    public MockPaymentProviderPluginModule(String instanceName) {
+        this.instanceName = instanceName;
+    }
+
+    @Override
+    protected void configure() {
+        bind(MockPaymentProviderPlugin.class)
+            .annotatedWith(Names.named(instanceName))
+            .toProvider(new MockPaymentProviderPluginProvider(instanceName))
+            .asEagerSingleton();
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.java b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.java
new file mode 100644
index 0000000..1170007
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/provider/MockPaymentProviderPluginProvider.java
@@ -0,0 +1,42 @@
+/*
+ * 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.provider;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class MockPaymentProviderPluginProvider implements Provider<MockPaymentProviderPlugin> {
+    private PaymentProviderPluginRegistry registry;
+    private final String instanceName;
+
+    public MockPaymentProviderPluginProvider(String instanceName) {
+        this.instanceName = instanceName;
+    }
+
+    @Inject
+    public void setPaymentProviderPluginRegistry(PaymentProviderPluginRegistry registry) {
+        this.registry = registry;
+    }
+
+    @Override
+    public MockPaymentProviderPlugin get() {
+        MockPaymentProviderPlugin plugin = new MockPaymentProviderPlugin();
+
+        registry.register(plugin, instanceName);
+        return plugin;
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java
new file mode 100644
index 0000000..e051440
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentModuleWithMocks.java
@@ -0,0 +1,27 @@
+/*
+ * 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 com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+
+public class PaymentModuleWithMocks extends PaymentModule {
+    @Override
+    protected void installPaymentDao() {
+        bind(PaymentDao.class).to(MockPaymentDao.class).asEagerSingleton();
+    }
+}
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/setup/PaymentTestModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
new file mode 100644
index 0000000..ce7f502
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/setup/PaymentTestModuleWithMocks.java
@@ -0,0 +1,56 @@
+/*
+ * 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.account.dao.AccountDao;
+import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.MemoryEventBus;
+
+public class PaymentTestModuleWithMocks extends PaymentModule {
+    public PaymentTestModuleWithMocks() {
+        super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock")));
+    }
+
+    @Override
+    protected void installPaymentDao() {
+        bind(PaymentDao.class).to(MockPaymentDao.class).asEagerSingleton();
+    }
+
+    @Override
+    protected void installPaymentProviderPlugins(PaymentConfig config) {
+        install(new MockPaymentProviderPluginModule("my-mock"));
+    }
+
+    @Override
+    protected void configure() {
+        super.configure();
+        bind(EventBus.class).to(MemoryEventBus.class).asEagerSingleton();
+        bind(MockAccountDao.class).asEagerSingleton();
+        bind(AccountDao.class).to(MockAccountDao.class);
+        bind(MockInvoiceDao.class).asEagerSingleton();
+        bind(InvoiceDao.class).to(MockInvoiceDao.class);
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestHelper.java b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
new file mode 100644
index 0000000..862d98c
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestHelper.java
@@ -0,0 +1,90 @@
+/*
+ * 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 java.math.BigDecimal;
+import java.util.UUID;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.user.AccountBuilder;
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.DefaultInvoiceItem;
+
+public class TestHelper {
+    protected final AccountDao accountDao;
+    protected final InvoiceDao invoiceDao;
+
+    @Inject
+    public TestHelper(AccountDao accountDao, InvoiceDao invoiceDao) {
+        this.accountDao = accountDao;
+        this.invoiceDao = invoiceDao;
+    }
+
+    public Account createTestAccount() throws AccountApiException {
+        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.create(account);
+        return account;
+    }
+
+    public 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.create(invoice);
+        return invoice;
+    }
+
+    public Invoice createTestInvoice(Account account) {
+        final DateTime now = new DateTime(DateTimeZone.UTC);
+        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);
+
+        return createTestInvoice(account, now, Currency.USD, item);
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
new file mode 100644
index 0000000..e6d1334
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestNotifyInvoicePaymentApi.java
@@ -0,0 +1,97 @@
+/*
+ * 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 org.testng.Assert.assertNotNull;
+
+import java.util.UUID;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.payment.api.InvoicePayment;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.EventBus.EventBusException;
+
+@Test
+@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+public class TestNotifyInvoicePaymentApi {
+    @Inject
+    private EventBus eventBus;
+    @Inject
+    private RequestProcessor invoiceProcessor;
+    @Inject
+    private InvoicePaymentApi invoicePaymentApi;
+    @Inject
+    private TestHelper testHelper;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        eventBus.start();
+        eventBus.register(invoiceProcessor);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        eventBus.unregister(invoiceProcessor);
+        eventBus.stop();
+    }
+
+    @Test
+    public void testNotifyPaymentSuccess() throws AccountApiException {
+        final Account account = testHelper.createTestAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account);
+
+        PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+
+        invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
+                                     invoice.getAmountOutstanding(),
+                                     invoice.getCurrency(),
+                                     paymentAttempt.getPaymentAttemptId(),
+                                     paymentAttempt.getPaymentAttemptDate());
+
+        InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayment(paymentAttempt.getPaymentAttemptId());
+
+        assertNotNull(invoicePayment);
+    }
+
+    @Test
+    public void testNotifyPaymentFailure() throws AccountApiException {
+        final Account account = testHelper.createTestAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account);
+
+        PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+        invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
+                                                 paymentAttempt.getPaymentAttemptId(),
+                                                 paymentAttempt.getPaymentAttemptDate());
+
+        InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayment(paymentAttempt.getPaymentAttemptId());
+
+        assertNotNull(invoicePayment);
+    }
+
+}
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..1027abe
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentInvoiceIntegration.java
@@ -0,0 +1,152 @@
+/*
+ * 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.concurrent.Callable;
+
+import org.apache.commons.io.IOUtils;
+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.glue.AccountModule;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.glue.InvoiceModule;
+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
+    private InvoicePaymentApi invoicePaymentApi;
+    @Inject
+    private PaymentApi paymentApi;
+    @Inject
+    private TestHelper testHelper;
+
+    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 utilddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+        final String invoiceddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+        final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+
+        helper = new MysqlTestingHelper();
+        helper.startMysql();
+        helper.initDb(accountddl + "\n" + invoiceddl + "\n" + utilddl + "\n" + paymentddl);
+        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() throws EventBusException {
+        eventBus.unregister(invoiceProcessor);
+        eventBus.unregister(paymentInfoReceiver);
+        eventBus.stop();
+    }
+
+    @Test
+    public void testInvoiceIntegration() throws Exception {
+        final Account account = testHelper.createTestAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account);
+
+        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).getPaymentId());
+        Assert.assertNotNull(paymentAttempt);
+
+        Invoice invoiceForPayment = invoicePaymentApi.getInvoiceForPaymentAttemptId(paymentAttempt.getPaymentAttemptId());
+
+        Assert.assertNotNull(invoiceForPayment);
+        Assert.assertEquals(invoiceForPayment.getId(), invoice.getId());
+        Assert.assertEquals(invoiceForPayment.getAccountId(), account.getId());
+        Assert.assertTrue(invoiceForPayment.getLastPaymentAttempt().isEqual(paymentAttempt.getPaymentAttemptDate()));
+        Assert.assertEquals(invoiceForPayment.getAmountOutstanding().floatValue(), new BigDecimal("0").floatValue());
+        Assert.assertEquals(invoiceForPayment.getAmountPaid().floatValue(), invoice.getAmountOutstanding().floatValue());
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
new file mode 100644
index 0000000..9f9720c
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/TestPaymentProvider.java
@@ -0,0 +1,108 @@
+/*
+ * 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.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.payment.api.PaymentError;
+import com.ning.billing.payment.api.PaymentInfo;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.EventBus.EventBusException;
+
+@Guice(modules = { PaymentTestModuleWithMocks.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class })
+public class TestPaymentProvider {
+    @Inject
+    private EventBus eventBus;
+    @Inject
+    private RequestProcessor invoiceProcessor;
+    @Inject
+    private TestHelper testHelper;
+
+    private MockPaymentInfoReceiver paymentInfoReceiver;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        paymentInfoReceiver = new MockPaymentInfoReceiver();
+
+        eventBus.start();
+        eventBus.register(invoiceProcessor);
+        eventBus.register(paymentInfoReceiver);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        eventBus.unregister(invoiceProcessor);
+        eventBus.unregister(paymentInfoReceiver);
+        eventBus.stop();
+    }
+
+    @Test
+    public void testSimpleInvoice() throws Exception {
+        final Account account = testHelper.createTestAccount();
+
+        testHelper.createTestInvoice(account);
+
+        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());
+
+        final PaymentInfo paymentInfo = paymentInfoReceiver.getProcessedPayments().get(0);
+        final PaymentInfoRequest paymentInfoRequest = new PaymentInfoRequest(account.getId(), paymentInfo.getPaymentId());
+
+        paymentInfoReceiver.clear();
+        eventBus.post(paymentInfoRequest);
+        await().atMost(5, 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());
+        assertEquals(paymentInfoReceiver.getProcessedPayments().get(0), paymentInfo);
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
new file mode 100644
index 0000000..3e446cf
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
@@ -0,0 +1,102 @@
+/*
+ * 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.util;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.UUID;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.util.eventbus.EventBus;
+import com.ning.billing.util.eventbus.MemoryEventBus;
+
+@Test
+public class TestSyncWaitOnEventBus {
+    private static final class TestEvent implements EventBusRequest<UUID> {
+        private final UUID id;
+        private final String msg;
+
+        public TestEvent(UUID id, String msg) {
+            this.id = id;
+            this.msg = msg;
+        }
+
+        @Override
+        public UUID getId() {
+            return id;
+        }
+
+        public String getMsg() {
+            return msg;
+        }
+    }
+
+    private static final class TestResponse implements EventBusResponse<UUID> {
+        private final UUID id;
+        private final String msg;
+
+        public TestResponse(UUID id, String msg) {
+            this.id = id;
+            this.msg = msg;
+        }
+
+        @Override
+        public UUID getRequestId() {
+            return id;
+        }
+
+        public String getMsg() {
+            return msg;
+        }
+    }
+
+    private EventBus eventBus;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception {
+        eventBus = new MemoryEventBus();
+        eventBus.start();
+        eventBus.register(new Object() {
+            @Subscribe
+            public void handleEvent(TestEvent event) throws Exception {
+                Thread.sleep(100);
+                eventBus.post(new TestResponse(event.getId(), event.getMsg()));
+            }
+        });
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() {
+        eventBus.stop();
+    }
+
+    public void test() throws Exception {
+        final TestEvent event = new TestEvent(UUID.randomUUID(), "Hello World!");
+
+        Future<TestResponse> future = EventBusFuture.post(eventBus, event);
+        TestResponse response = future.get(1, TimeUnit.SECONDS);
+
+        assertEquals(response.getRequestId(), event.getId());
+        assertEquals(response.getMsg(), event.getMsg());
+    }
+}
diff --git a/payment/src/test/resources/log4j.xml b/payment/src/test/resources/log4j.xml
new file mode 100644
index 0000000..82b5a26
--- /dev/null
+++ b/payment/src/test/resources/log4j.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~ Copyright 2010 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.
+  -->
+<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
+<log4j:configuration debug="false"
+                     xmlns:log4j='http://jakarta.apache.org/log4j/'>
+    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%p	%d{ISO8601}	%t	%c	%m%n"/>
+        </layout>
+    </appender>
+
+    <root>
+        <level value="info"/>
+        <appender-ref ref="CONSOLE"/>
+    </root>
+</log4j:configuration>

pom.xml 82(+73 -9)

diff --git a/pom.xml b/pom.xml
index 8c8fbb3..6d4aab5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,12 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-api</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-account</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -64,7 +70,6 @@
                 <artifactId>killbill-account</artifactId>
                 <version>${project.version}</version>
                 <type>test-jar</type>
-                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
@@ -73,6 +78,12 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-entitlement</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-catalog</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -81,14 +92,17 @@
                 <artifactId>killbill-catalog</artifactId>
                 <version>${project.version}</version>
                 <type>test-jar</type>
-                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
-                <artifactId>killbill-util</artifactId>
+                <artifactId>killbill-invoice</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-invoice</artifactId>
                 <version>${project.version}</version>
                 <type>test-jar</type>
-                <scope>test</scope>
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
@@ -96,19 +110,25 @@
                 <version>${project.version}</version>
             </dependency>
             <dependency>
+                <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-util</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+            </dependency>
+            <dependency>
                 <groupId>org.codehaus.jackson</groupId>
                 <artifactId>jackson-core-asl</artifactId>
-                <version>1.9.0</version>
+                <version>1.9.2</version>
             </dependency>
             <dependency>
                 <groupId>org.codehaus.jackson</groupId>
                 <artifactId>jackson-jaxrs</artifactId>
-                <version>1.9.0</version>
+                <version>1.9.2</version>
             </dependency>
             <dependency>
                 <groupId>org.codehaus.jackson</groupId>
                 <artifactId>jackson-mapper-asl</artifactId>
-                <version>1.9.0</version>
+                <version>1.9.2</version>
             </dependency>
             <dependency>
                 <groupId>com.jolbox</groupId>
@@ -134,9 +154,15 @@
                 <scope>provided</scope>
             </dependency>
             <dependency>
+                <groupId>com.google.inject.extensions</groupId>
+                <artifactId>guice-multibindings</artifactId>
+                <version>3.0</version>
+                <scope>provided</scope>
+            </dependency>
+            <dependency>
                 <groupId>com.mogwee</groupId>
                 <artifactId>mogwee-executors</artifactId>
-                <version>1.1.0</version>
+                <version>1.2.0</version>
             </dependency>
             <dependency>
                 <groupId>com.mysql</groupId>
@@ -176,6 +202,11 @@
                 <version>2.5</version>
             </dependency>
             <dependency>
+                <groupId>commons-collections</groupId>
+                <artifactId>commons-collections</artifactId>
+                <version>3.2.1</version>
+            </dependency>
+            <dependency>
                 <groupId>joda-time</groupId>
                 <artifactId>joda-time</artifactId>
                 <version>2.0</version>
@@ -231,7 +262,13 @@
             <dependency>
                 <groupId>org.testng</groupId>
                 <artifactId>testng</artifactId>
-                <version>6.0</version>
+                <version>6.3.1</version>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.jayway.awaitility</groupId>
+                <artifactId>awaitility</artifactId>
+                <version>1.3.3</version>
                 <scope>test</scope>
             </dependency>
         </dependencies>
@@ -284,6 +321,18 @@
                 </configuration>
             </plugin>
             <plugin>
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-jar-plugin</artifactId>
+              <version>2.2</version>
+              <executions>
+                <execution>
+                  <goals>
+                    <goal>test-jar</goal>
+                  </goals>
+               </execution>
+              </executions>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-dependency-plugin</artifactId>
                 <version>2.3</version>
@@ -391,6 +440,21 @@
                     <attachClasses>true</attachClasses>
                 </configuration>
             </plugin>
+            <plugin>
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-source-plugin</artifactId>
+              <version>2.1.2</version>
+              <executions>
+                <execution>
+                  <id>attach-sources</id>
+                  <phase>verify</phase>
+                  <goals>
+                    <goal>jar</goal>
+                    <goal>test-jar</goal>
+                  </goals>
+                </execution>
+              </executions>
+            </plugin>
         </plugins>
     </build>
     <profiles>
diff --git a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
index e308dad..9fc34ea 100644
--- a/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/EntityDao.java
@@ -16,15 +16,14 @@
 
 package com.ning.billing.util.entity;
 
+import java.util.List;
+
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
-import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 
-import java.util.List;
 import com.ning.billing.account.api.AccountApiException;
-import com.ning.billing.util.eventbus.EventBus;
 
 public interface EntityDao<T extends Entity> {
     @SqlUpdate
diff --git a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
index e8a77f6..be53073 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -16,8 +16,12 @@
 
 package com.ning.billing.dbi;
 
-import com.mysql.management.MysqldResource;
-import com.mysql.management.MysqldResourceI;
+import java.io.File;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.commons.io.FileUtils;
 import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.Handle;
@@ -27,11 +31,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
-import java.io.File;
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.HashMap;
-import java.util.Map;
+import com.mysql.management.MysqldResource;
+import com.mysql.management.MysqldResourceI;
 
 /**
  * Utility class to embed MySQL for testing purposes
@@ -73,7 +74,7 @@ public class MysqlTestingHelper
         dbOpts.put(MysqldResourceI.PORT, Integer.toString(port));
         dbOpts.put(MysqldResourceI.INITIALIZE_USER, "true");
         dbOpts.put(MysqldResourceI.INITIALIZE_USER_NAME, USERNAME);
-        dbOpts.put(MysqldResourceI.INITIALIZE_PASSWORD, PASSWORD);
+        dbOpts.put("default-time-zone", "+00:00");
 
         mysqldResource.start("test-mysqld-thread", dbOpts);
         if (!mysqldResource.isRunning()) {
diff --git a/util/src/test/java/com/ning/billing/util/eventbus/TestEventBus.java b/util/src/test/java/com/ning/billing/util/eventbus/TestEventBus.java
index 47691be..1419b8f 100644
--- a/util/src/test/java/com/ning/billing/util/eventbus/TestEventBus.java
+++ b/util/src/test/java/com/ning/billing/util/eventbus/TestEventBus.java
@@ -52,6 +52,16 @@ public class TestEventBus {
         }
     }
 
+    public static final class MyOtherEvent implements EventBusNotification {
+        String name;
+        Long value;
+
+        public MyOtherEvent(String name, Long value) {
+            this.name = name;
+            this.value = value;
+        }
+    }
+
     public static class MyEventHandler {
 
         private final int expectedEvents;
@@ -87,8 +97,8 @@ public class TestEventBus {
         }
     }
 
-    @Test()
-    public void test() {
+    @Test
+    public void testSimple() {
         try {
 
             int nbEvents = 127;
@@ -104,6 +114,25 @@ public class TestEventBus {
         } catch (Exception e) {
             Assert.fail("",e);
         }
+    }
+
+    @Test
+    public void testDifferentType() {
+        try {
+
+            MyEventHandler handler = new MyEventHandler(1);
+            eventBus.register(handler);
+
+            for (int i = 0; i < 10; i++) {
+                eventBus.post(new MyOtherEvent("my-other-event", (long) i));
+            }
+            eventBus.post(new MyEvent("my-event", 11l));
+
+            boolean completed = handler.waitForCompletion(3000);
+            Assert.assertEquals(completed, true);
+        } catch (Exception e) {
+            Assert.fail("",e);
+        }
 
     }
 }