killbill-uncached

Merge branch 'master' into usage

9/10/2014 6:53:25 PM

Changes

.gitignore 2(+2 -0)

.idea/compiler.xml 14(+5 -9)

.idea/encodings.xml 17(+3 -14)

.idea/modules.xml 18(+3 -15)

account/pom.xml 31(+26 -5)

api/pom.xml 12(+10 -2)

api/src/main/java/org/killbill/billing/lifecycle/KillbillService.java 56(+0 -56)

api/src/main/java/org/killbill/billing/lifecycle/LifecycleHandlerType.java 114(+0 -114)

api/src/main/java/org/killbill/billing/osgi/api/OSGIServiceRegistration.java 51(+0 -51)

beatrix/pom.xml 60(+26 -34)

beatrix/src/main/java/org/killbill/billing/beatrix/glue/ExternalPersistentBusConfig.java 126(+0 -126)

beatrix/src/main/java/org/killbill/billing/beatrix/lifecycle/DefaultLifecycle.java 179(+0 -179)

beatrix/src/main/java/org/killbill/billing/beatrix/lifecycle/ServiceFinder.java 226(+0 -226)

beatrix/src/test/java/org/killbill/billing/beatrix/integration/osgi/TestBasicOSGIWithTestBundle.java 238(+0 -238)

beatrix/src/test/java/org/killbill/billing/beatrix/integration/osgi/TestJrubyCurrencyPlugin.java 99(+0 -99)

beatrix/src/test/java/org/killbill/billing/beatrix/integration/osgi/TestJrubyNotificationPlugin.java 62(+0 -62)

beatrix/src/test/java/org/killbill/billing/beatrix/integration/osgi/TestJrubyPaymentPlugin.java 212(+0 -212)

beatrix/src/test/java/org/killbill/billing/beatrix/integration/osgi/TestPaymentOSGIWithTestPaymentBundle.java 183(+0 -183)

beatrix/src/test/java/org/killbill/billing/beatrix/lifecycle/TestLifecycle.java 187(+0 -187)

beatrix/src/test/java/org/killbill/billing/beatrix/osgi/SetupBundleWithAssertion.java 292(+0 -292)

beatrix/src/test/resources/killbill-currency-plugin-test.tar.gz 0(+0 -0)

beatrix/src/test/resources/killbill-notification-test.tar.gz 0(+0 -0)

beatrix/src/test/resources/killbill-payment-test.tar.gz 0(+0 -0)

bin/db-helper 2(+1 -1)

bin/start-server 3(+2 -1)

catalog/pom.xml 35(+34 -1)

currency/pom.xml 20(+18 -2)

entitlement/pom.xml 21(+20 -1)

invoice/pom.xml 47(+46 -1)

invoice/src/test/java/org/killbill/billing/invoice/tests/TestChargeBacks.java 151(+0 -151)

jaxrs/pom.xml 66(+61 -5)

jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ChargebackJson.java 161(+0 -161)

jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RefundJson.java 250(+0 -250)

jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/RefundResource.java 195(+0 -195)

jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestChargebackJson.java 57(+0 -57)

jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestRefundJson.java 88(+0 -88)

junction/pom.xml 37(+33 -4)

NEWS 55(+55 -0)

osgi/pom.xml 139(+0 -139)

osgi/src/main/java/org/killbill/billing/osgi/ContextClassLoaderHelper.java 103(+0 -103)

osgi/src/main/java/org/killbill/billing/osgi/DefaultOSGIKillbill.java 173(+0 -173)

osgi/src/main/java/org/killbill/billing/osgi/DefaultOSGIService.java 173(+0 -173)

osgi/src/main/java/org/killbill/billing/osgi/FileInstall.java 225(+0 -225)

osgi/src/main/java/org/killbill/billing/osgi/glue/DefaultOSGIModule.java 94(+0 -94)

osgi/src/main/java/org/killbill/billing/osgi/glue/OSGIDataSourceConfig.java 57(+0 -57)

osgi/src/main/java/org/killbill/billing/osgi/glue/OSGIDataSourceProvider.java 154(+0 -154)

osgi/src/main/java/org/killbill/billing/osgi/http/DefaultHttpService.java 75(+0 -75)

osgi/src/main/java/org/killbill/billing/osgi/http/DefaultServletRouter.java 161(+0 -161)

osgi/src/main/java/org/killbill/billing/osgi/http/OSGIServlet.java 132(+0 -132)

osgi/src/main/java/org/killbill/billing/osgi/http/StaticServlet.java 86(+0 -86)

osgi/src/main/java/org/killbill/billing/osgi/KillbillActivator.java 169(+0 -169)

osgi/src/main/java/org/killbill/billing/osgi/KillbillEventObservable.java 63(+0 -63)

osgi/src/main/java/org/killbill/billing/osgi/pluginconf/DefaultPluginConfig.java 117(+0 -117)

osgi/src/main/java/org/killbill/billing/osgi/pluginconf/DefaultPluginConfigServiceApi.java 56(+0 -56)

osgi/src/main/java/org/killbill/billing/osgi/pluginconf/DefaultPluginJavaConfig.java 64(+0 -64)

osgi/src/main/java/org/killbill/billing/osgi/pluginconf/DefaultPluginRubyConfig.java 72(+0 -72)

osgi/src/main/java/org/killbill/billing/osgi/pluginconf/PluginFinder.java 211(+0 -211)

osgi/src/main/java/org/killbill/billing/osgi/PureOSGIBundleFinder.java 72(+0 -72)

osgi/src/test/java/org/killbill/billing/osgi/TestKillbillActivator.java 75(+0 -75)

osgi-bundles/bundles/jruby/pom.xml 224(+0 -224)

osgi-bundles/bundles/jruby/README.md 17(+0 -17)

osgi-bundles/bundles/jruby/src/main/java/org/killbill/billing/osgi/bundles/jruby/JRubyActivator.java 207(+0 -207)

osgi-bundles/bundles/jruby/src/main/java/org/killbill/billing/osgi/bundles/jruby/JRubyCurrencyPlugin.java 133(+0 -133)

osgi-bundles/bundles/jruby/src/main/java/org/killbill/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java 49(+0 -49)

osgi-bundles/bundles/jruby/src/main/java/org/killbill/billing/osgi/bundles/jruby/JRubyPaymentPlugin.java 212(+0 -212)

osgi-bundles/bundles/jruby/src/main/java/org/killbill/billing/osgi/bundles/jruby/JRubyPlugin.java 297(+0 -297)

osgi-bundles/bundles/logger/pom.xml 112(+0 -112)

osgi-bundles/bundles/logger/src/main/java/org/killbill/billing/osgi/bundles/logger/Activator.java 100(+0 -100)

osgi-bundles/bundles/logger/src/main/java/org/killbill/billing/osgi/bundles/logger/KillbillLogWriter.java 193(+0 -193)

osgi-bundles/bundles/meter/pom.xml 216(+0 -216)

osgi-bundles/bundles/meter/src/main/java/org/killbill/billing/meter/jaxrs/resources/MeterResource.java 230(+0 -230)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/MeterTestSuite.java 71(+0 -71)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/aggregator/TestTimelineAggregator.java 169(+0 -169)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/chunks/TestTimelineChunk.java 73(+0 -73)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/consumer/TestAccumulatorSampleConsumer.java 51(+0 -51)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/persistent/TestFileBackedBuffer.java 149(+0 -149)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/persistent/TestSamplesReplayer.java 118(+0 -118)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/TestDateTimeUtils.java 41(+0 -41)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/TestInMemoryEventHandler.java 113(+0 -113)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/TestTimelineEventHandler.java 130(+0 -130)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/TestTimelineSourceEventAccumulator.java 97(+0 -97)

osgi-bundles/bundles/meter/src/test/java/org/killbill/billing/meter/timeline/TimelineLoadGenerator.java 212(+0 -212)

osgi-bundles/bundles/pom.xml 33(+0 -33)

osgi-bundles/bundles/webconsolebranding/pom.xml 64(+0 -64)

osgi-bundles/bundles/webconsolebranding/src/main/resources/META-INF/webconsole.properties 16(+0 -16)

osgi-bundles/bundles/webconsolebranding/src/main/resources/res/killbill/logo.png 0(+0 -0)

osgi-bundles/defaultbundles/pom.xml 165(+0 -165)

osgi-bundles/defaultbundles/README.md 12(+0 -12)

osgi-bundles/defaultbundles/src/main/assembly/assembly.xml 32(+0 -32)

osgi-bundles/libs/killbill/pom.xml 75(+0 -75)

osgi-bundles/libs/killbill/src/main/java/org/killbill/killbill/osgi/libs/killbill/KillbillActivatorBase.java 93(+0 -93)

osgi-bundles/libs/killbill/src/main/java/org/killbill/killbill/osgi/libs/killbill/OSGIKillbillAPI.java 218(+0 -218)

osgi-bundles/libs/killbill/src/main/java/org/killbill/killbill/osgi/libs/killbill/OSGIKillbillDataSource.java 50(+0 -50)

osgi-bundles/libs/killbill/src/main/java/org/killbill/killbill/osgi/libs/killbill/OSGIKillbillEventDispatcher.java 95(+0 -95)

osgi-bundles/libs/killbill/src/main/java/org/killbill/killbill/osgi/libs/killbill/OSGIKillbillLibraryBase.java 53(+0 -53)

osgi-bundles/libs/killbill/src/main/java/org/killbill/killbill/osgi/libs/killbill/OSGIKillbillLogService.java 91(+0 -91)

osgi-bundles/libs/killbill/src/main/java/org/killbill/killbill/osgi/libs/killbill/OSGIKillbillRegistrar.java 52(+0 -52)

osgi-bundles/libs/slf4j-osgi/pom.xml 48(+0 -48)

osgi-bundles/libs/slf4j-osgi/src/main/java/org/slf4j/impl/OSGISlf4jLoggerAdapter.java 67(+0 -67)

osgi-bundles/libs/slf4j-osgi/src/main/java/org/slf4j/impl/OSGISlf4jLoggerFactory.java 53(+0 -53)

osgi-bundles/libs/slf4j-osgi/src/main/java/org/slf4j/impl/SimpleLogger.java 644(+0 -644)

osgi-bundles/libs/slf4j-osgi/src/main/java/org/slf4j/impl/StaticLoggerBinder.java 70(+0 -70)

osgi-bundles/pom.xml 34(+0 -34)

osgi-bundles/tests/beatrix/pom.xml 188(+0 -188)

osgi-bundles/tests/beatrix/src/test/java/org/killbill/billing/osgi/bundles/test/dao/TestDao.java 89(+0 -89)

osgi-bundles/tests/beatrix/src/test/java/org/killbill/billing/osgi/bundles/test/TestActivator.java 106(+0 -106)

osgi-bundles/tests/beatrix/src/test/java/org/killbill/billing/osgi/bundles/test/TestPaymentPluginApi.java 236(+0 -236)

osgi-bundles/tests/beatrix/src/test/resources/ddl_test.sql 10(+0 -10)

osgi-bundles/tests/payment/pom.xml 187(+0 -187)

osgi-bundles/tests/payment/src/test/java/org/killbill/billing/osgi/bundles/test/PaymentActivator.java 63(+0 -63)

osgi-bundles/tests/payment/src/test/java/org/killbill/billing/osgi/bundles/test/TestPaymentPluginApi.java 368(+0 -368)

osgi-bundles/tests/payment/src/test/resources/ddl_test.sql 10(+0 -10)

osgi-bundles/tests/pom.xml 32(+0 -32)

osgi-bundles/tests/src/assemble/assembly.xml 58(+0 -58)

osgi-bundles/tests/src/test/resources/ddl_test.sql 10(+0 -10)

overdue/pom.xml 43(+40 -3)

payment/pom.xml 49(+47 -2)

payment/src/main/java/org/killbill/billing/payment/api/svcs/DefaultPaymentInternalApi.java 68(+0 -68)

payment/src/main/java/org/killbill/billing/payment/core/RefundProcessor.java 504(+0 -504)

payment/src/main/java/org/killbill/billing/payment/dao/RefundModelDao.java 224(+0 -224)

payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpRefundInfoPlugin.java 160(+0 -160)

payment/src/main/java/org/killbill/billing/payment/retry/FailedPaymentRetryService.java 108(+0 -108)

payment/src/main/java/org/killbill/billing/payment/retry/PluginFailureRetryService.java 111(+0 -111)

pom.xml 11(+5 -6)

profiles/pom.xml 10(+5 -5)

README.md 2(+1 -1)

server/bitcoin.log 1037(+0 -1037)

server/killbill-bitcoin-testnet.spvchain 0(+0 -0)

server/killbill-bitcoin-testnet.wallet 0(+0 -0)

server/src/deb/control/changelog 5(+0 -5)

server/src/deb/control/compat 1(+0 -1)

server/src/deb/control/config 26(+0 -26)

server/src/deb/control/control 22(+0 -22)

server/src/deb/control/copyright 27(+0 -27)

server/src/deb/control/killbill-server.default 16(+0 -16)

server/src/deb/control/killbill-server.upstart 18(+0 -18)

server/src/deb/control/postinst 64(+0 -64)

server/src/deb/control/postrm 33(+0 -33)

server/src/deb/control/prerm 8(+0 -8)

server/src/deb/control/rules 8(+0 -8)

server/src/deb/control/templates 38(+0 -38)

server/src/deb/control/watch 3(+0 -3)

server/src/deb/README.adoc 28(+0 -28)

server/src/deb/support/killbill.properties 43(+0 -43)

server/src/deb/support/killbill.sh 20(+0 -20)

server/src/deb/support/logback.xml 36(+0 -36)

server/src/main/java/org/killbill/billing/server/config/DaoConfig.java 89(+0 -89)

server/src/main/java/org/killbill/billing/server/config/KillbillServerConfig.java 36(+0 -36)

server/src/main/java/org/killbill/billing/server/config/UpdateCheckConfig.java 43(+0 -43)

server/src/main/java/org/killbill/billing/server/dao/EmbeddedDBFactory.java 56(+0 -56)

server/src/main/java/org/killbill/billing/server/filters/KillbillGuiceFilter.java 47(+0 -47)

server/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java 181(+0 -181)

server/src/main/java/org/killbill/billing/server/modules/DataSourceProvider.java 123(+0 -123)

server/src/main/java/org/killbill/billing/server/modules/DBIProvider.java 95(+0 -95)

server/src/main/java/org/killbill/billing/server/modules/EmbeddedDBProvider.java 84(+0 -84)

server/src/main/java/org/killbill/billing/server/updatechecker/ClientInfo.java 161(+0 -161)

server/src/main/java/org/killbill/billing/server/updatechecker/ProductInfo.java 106(+0 -106)

server/src/main/java/org/killbill/billing/server/updatechecker/Tracker.java 91(+0 -91)

server/src/main/java/org/killbill/billing/server/updatechecker/UpdateChecker.java 113(+0 -113)

server/src/main/java/org/killbill/billing/server/updatechecker/UpdateListProperties.java 88(+0 -88)

server/src/main/resources/ehcache.xml 104(+0 -104)

server/src/main/resources/logback.xml 36(+0 -36)

server/src/main/resources/MichielCatalog.xml 369(+0 -369)

server/src/main/resources/NitinCatalog.xml 184(+0 -184)

server/src/test/java/org/killbill/billing/server/security/TestKillbillJdbcRealm.java 99(+0 -99)

server/src/test/java/org/killbill/billing/server/security/TestTenantFilter.java 84(+0 -84)

server/src/test/resources/overdue.xml 59(+0 -59)

server/src/test/resources/shiro.ini 30(+0 -30)

tenant/pom.xml 24(+15 -9)

usage/pom.xml 38(+25 -13)

util/pom.xml 46(+38 -8)

util/src/main/java/org/killbill/billing/util/bus/DefaultBusService.java 61(+0 -61)

util/src/main/java/org/killbill/billing/util/config/catalog/UriAccessor.java 86(+0 -86)

util/src/main/java/org/killbill/billing/util/config/catalog/ValidatingConfig.java 52(+0 -52)

util/src/main/java/org/killbill/billing/util/config/catalog/ValidationError.java 61(+0 -61)

util/src/main/java/org/killbill/billing/util/config/catalog/ValidationErrors.java 47(+0 -47)

util/src/main/java/org/killbill/billing/util/config/catalog/XMLLoader.java 114(+0 -114)

util/src/main/java/org/killbill/billing/util/config/catalog/XMLSchemaGenerator.java 110(+0 -110)

util/src/main/java/org/killbill/billing/util/config/catalog/XMLWriter.java 37(+0 -37)

util/src/main/java/org/killbill/billing/util/config/OSGIConfig.java 99(+0 -99)

util/src/main/java/org/killbill/billing/util/glue/BusModule.java 75(+0 -75)

util/src/main/java/org/killbill/billing/util/glue/BusProvider.java 55(+0 -55)

util/src/main/java/org/killbill/billing/util/glue/NotificationQueueModule.java 48(+0 -48)

util/src/main/java/org/killbill/billing/util/KillbillConfigSource.java 113(+0 -113)

util/src/test/java/org/killbill/billing/dbi/DBIProvider.java 65(+0 -65)

util/src/test/java/org/killbill/billing/mock/glue/MockNotificationQueueModule.java 48(+0 -48)

util/src/test/java/org/killbill/billing/payment/plugin/api/PaymentPluginApiWithTestControl.java 26(+0 -26)

util/src/test/java/org/killbill/billing/TestKillbillConfigSource.java 94(+0 -94)

util/src/test/java/org/killbill/billing/util/config/TestXMLLoader.java 55(+0 -55)

util/src/test/java/org/killbill/billing/util/config/TestXMLSchemaGenerator.java 38(+0 -38)

util/src/test/java/org/killbill/billing/util/config/TestXMLWriter.java 53(+0 -53)

util/src/test/java/org/killbill/billing/util/config/XmlTestClass.java 49(+0 -49)

Details

.gitignore 2(+2 -0)

diff --git a/.gitignore b/.gitignore
index f7f01e8..aeae6f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,5 @@ dependency-reduced-pom.xml.bak
 killbill.h2.db
 killbill.lock.db
 killbill.trace.db
+server/test.db
+.idea/dictionaries/

.idea/compiler.xml 14(+5 -9)

diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 9cdcfe3..a3d57a5 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -32,17 +32,10 @@
         <module name="killbill-invoice" />
         <module name="killbill-jaxrs" />
         <module name="killbill-junction" />
-        <module name="killbill-osgi" />
-        <module name="killbill-osgi-bundles-jruby" />
-        <module name="killbill-osgi-bundles-lib-killbill" />
-        <module name="killbill-osgi-bundles-lib-slf4j-osgi" />
-        <module name="killbill-osgi-bundles-logger" />
-        <module name="killbill-osgi-bundles-test-beatrix" />
-        <module name="killbill-osgi-bundles-test-payment" />
-        <module name="killbill-osgi-bundles-webconsolebranding" />
         <module name="killbill-overdue" />
         <module name="killbill-payment" />
-        <module name="killbill-server" />
+        <module name="killbill-profiles-killbill" />
+        <module name="killbill-profiles-killpay" />
         <module name="killbill-subscription" />
         <module name="killbill-tenant" />
         <module name="killbill-usage" />
@@ -76,6 +69,9 @@
       <module name="killbill-osgi-test-bundles" target="1.6" />
       <module name="killbill-overdue" target="1.6" />
       <module name="killbill-payment" target="1.6" />
+      <module name="killbill-profiles" target="1.6" />
+      <module name="killbill-profiles-killbill" target="1.6" />
+      <module name="killbill-profiles-killpay" target="1.6" />
       <module name="killbill-server" target="1.6" />
       <module name="killbill-subscription" target="1.6" />
       <module name="killbill-tenant" target="1.6" />
diff --git a/.idea/copyright/apache.xml b/.idea/copyright/apache.xml
index acd508f..1e2da96 100644
--- a/.idea/copyright/apache.xml
+++ b/.idea/copyright/apache.xml
@@ -1,6 +1,6 @@
 <component name="CopyrightManager">
   <copyright>
-    <option name="notice" value="Copyright &amp;#36;today.year The Billing Project, LLC&#10;&#10;Ning licenses this file to you under the Apache License, version 2.0&#10;(the &quot;License&quot;); you may not use this file except in compliance with the&#10;License.  You may obtain a copy of the License at:&#10;&#10;   http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS, WITHOUT&#10;WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the&#10;License for the specific language governing permissions and limitations&#10;under the License." />
+    <option name="notice" value="Copyright &amp;#36;today.year Groupon, Inc&#10;Copyright &amp;#36;today.year The Billing Project, LLC&#10;&#10;The Billing Project licenses this file to you under the Apache License, version 2.0&#10;(the &quot;License&quot;); you may not use this file except in compliance with the&#10;License.  You may obtain a copy of the License at:&#10;&#10;   http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS, WITHOUT&#10;WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the&#10;License for the specific language governing permissions and limitations&#10;under the License." />
     <option name="keyword" value="Copyright" />
     <option name="allowReplaceKeyword" value="" />
     <option name="myName" value="apache" />
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
index 265f455..7fafdd7 100644
--- a/.idea/copyright/profiles_settings.xml
+++ b/.idea/copyright/profiles_settings.xml
@@ -1,5 +1,3 @@
 <component name="CopyrightManager">
-  <settings default="apache">
-    <module2copyright />
-  </settings>
+  <settings default="apache" />
 </component>
\ No newline at end of file

.idea/encodings.xml 17(+3 -14)

diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 88e6931..d0e8896 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -11,22 +11,11 @@
     <file url="file://$PROJECT_DIR$/invoice" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/jaxrs" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/junction" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/bundles" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/bundles/jruby" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/bundles/logger" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/bundles/webconsolebranding" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/defaultbundles" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/libs" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/libs/killbill" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/libs/slf4j-osgi" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/tests" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/tests/beatrix" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/osgi-bundles/tests/payment" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/overdue" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/payment" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/server" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/profiles" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/profiles/killbill" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/profiles/killpay" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/subscription" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/tenant" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/usage" charset="UTF-8" />

.idea/modules.xml 18(+3 -15)

diff --git a/.idea/modules.xml b/.idea/modules.xml
index 7d9bd7c..c733a45 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -7,28 +7,16 @@
       <module fileurl="file://$PROJECT_DIR$/beatrix/killbill-beatrix.iml" filepath="$PROJECT_DIR$/beatrix/killbill-beatrix.iml" />
       <module fileurl="file://$PROJECT_DIR$/catalog/killbill-catalog.iml" filepath="$PROJECT_DIR$/catalog/killbill-catalog.iml" />
       <module fileurl="file://$PROJECT_DIR$/currency/killbill-currency.iml" filepath="$PROJECT_DIR$/currency/killbill-currency.iml" />
-      <module fileurl="file://$PROJECT_DIR$/currency/killbill-currency.iml" filepath="$PROJECT_DIR$/currency/killbill-currency.iml" />
       <module fileurl="file://$PROJECT_DIR$/entitlement/killbill-entitlement.iml" filepath="$PROJECT_DIR$/entitlement/killbill-entitlement.iml" />
       <module fileurl="file://$PROJECT_DIR$/api/killbill-internal-api.iml" filepath="$PROJECT_DIR$/api/killbill-internal-api.iml" />
       <module fileurl="file://$PROJECT_DIR$/invoice/killbill-invoice.iml" filepath="$PROJECT_DIR$/invoice/killbill-invoice.iml" />
       <module fileurl="file://$PROJECT_DIR$/jaxrs/killbill-jaxrs.iml" filepath="$PROJECT_DIR$/jaxrs/killbill-jaxrs.iml" />
       <module fileurl="file://$PROJECT_DIR$/junction/killbill-junction.iml" filepath="$PROJECT_DIR$/junction/killbill-junction.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi/killbill-osgi.iml" filepath="$PROJECT_DIR$/osgi/killbill-osgi.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/killbill-osgi-all-bundles.iml" filepath="$PROJECT_DIR$/osgi-bundles/killbill-osgi-all-bundles.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/bundles/killbill-osgi-bundles.iml" filepath="$PROJECT_DIR$/osgi-bundles/bundles/killbill-osgi-bundles.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/defaultbundles/killbill-osgi-bundles-defaultbundles.iml" filepath="$PROJECT_DIR$/osgi-bundles/defaultbundles/killbill-osgi-bundles-defaultbundles.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/bundles/jruby/killbill-osgi-bundles-jruby.iml" filepath="$PROJECT_DIR$/osgi-bundles/bundles/jruby/killbill-osgi-bundles-jruby.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/libs/killbill/killbill-osgi-bundles-lib-killbill.iml" filepath="$PROJECT_DIR$/osgi-bundles/libs/killbill/killbill-osgi-bundles-lib-killbill.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/libs/slf4j-osgi/killbill-osgi-bundles-lib-slf4j-osgi.iml" filepath="$PROJECT_DIR$/osgi-bundles/libs/slf4j-osgi/killbill-osgi-bundles-lib-slf4j-osgi.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/bundles/logger/killbill-osgi-bundles-logger.iml" filepath="$PROJECT_DIR$/osgi-bundles/bundles/logger/killbill-osgi-bundles-logger.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/tests/beatrix/killbill-osgi-bundles-test-beatrix.iml" filepath="$PROJECT_DIR$/osgi-bundles/tests/beatrix/killbill-osgi-bundles-test-beatrix.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/tests/payment/killbill-osgi-bundles-test-payment.iml" filepath="$PROJECT_DIR$/osgi-bundles/tests/payment/killbill-osgi-bundles-test-payment.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/bundles/webconsolebranding/killbill-osgi-bundles-webconsolebranding.iml" filepath="$PROJECT_DIR$/osgi-bundles/bundles/webconsolebranding/killbill-osgi-bundles-webconsolebranding.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/libs/killbill-osgi-lib-bundles.iml" filepath="$PROJECT_DIR$/osgi-bundles/libs/killbill-osgi-lib-bundles.iml" />
-      <module fileurl="file://$PROJECT_DIR$/osgi-bundles/tests/killbill-osgi-test-bundles.iml" filepath="$PROJECT_DIR$/osgi-bundles/tests/killbill-osgi-test-bundles.iml" />
       <module fileurl="file://$PROJECT_DIR$/overdue/killbill-overdue.iml" filepath="$PROJECT_DIR$/overdue/killbill-overdue.iml" />
       <module fileurl="file://$PROJECT_DIR$/payment/killbill-payment.iml" filepath="$PROJECT_DIR$/payment/killbill-payment.iml" />
-      <module fileurl="file://$PROJECT_DIR$/server/killbill-server.iml" filepath="$PROJECT_DIR$/server/killbill-server.iml" />
+      <module fileurl="file://$PROJECT_DIR$/profiles/killbill-profiles.iml" filepath="$PROJECT_DIR$/profiles/killbill-profiles.iml" />
+      <module fileurl="file://$PROJECT_DIR$/profiles/killbill/killbill-profiles-killbill.iml" filepath="$PROJECT_DIR$/profiles/killbill/killbill-profiles-killbill.iml" />
+      <module fileurl="file://$PROJECT_DIR$/profiles/killpay/killbill-profiles-killpay.iml" filepath="$PROJECT_DIR$/profiles/killpay/killbill-profiles-killpay.iml" />
       <module fileurl="file://$PROJECT_DIR$/subscription/killbill-subscription.iml" filepath="$PROJECT_DIR$/subscription/killbill-subscription.iml" />
       <module fileurl="file://$PROJECT_DIR$/tenant/killbill-tenant.iml" filepath="$PROJECT_DIR$/tenant/killbill-tenant.iml" />
       <module fileurl="file://$PROJECT_DIR$/usage/killbill-usage.iml" filepath="$PROJECT_DIR$/usage/killbill-usage.iml" />

account/pom.xml 31(+26 -5)

diff --git a/account/pom.xml b/account/pom.xml
index 31e5cbe..2bc7a7b 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
@@ -76,6 +76,25 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-lifecycle</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
@@ -109,13 +128,15 @@
             <artifactId>killbill-queue</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-all</artifactId>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-queue</artifactId>
+            <type>test-jar</type>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.skife.config</groupId>
-            <artifactId>config-magic</artifactId>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountEmailModelDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountEmailModelDao.java
index 5628e4a..41cd2e8 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/AccountEmailModelDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/AccountEmailModelDao.java
@@ -22,8 +22,9 @@ import org.killbill.billing.account.api.AccountEmail;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class AccountEmailModelDao extends EntityBase implements EntityModelDao<AccountEmail> {
+public class AccountEmailModelDao extends EntityModelDaoBase implements EntityModelDao<AccountEmail> {
 
     private UUID accountId;
     private String email;
diff --git a/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java b/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java
index 4f9b770..4f8cd7d 100644
--- a/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java
+++ b/account/src/main/java/org/killbill/billing/account/dao/AccountModelDao.java
@@ -29,8 +29,9 @@ import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class AccountModelDao extends EntityBase implements EntityModelDao<Account> {
+public class AccountModelDao extends EntityModelDaoBase implements EntityModelDao<Account> {
 
     private String externalKey;
     private String email;
diff --git a/account/src/main/java/org/killbill/billing/account/glue/DefaultAccountModule.java b/account/src/main/java/org/killbill/billing/account/glue/DefaultAccountModule.java
index 60c300f..705ae60 100644
--- a/account/src/main/java/org/killbill/billing/account/glue/DefaultAccountModule.java
+++ b/account/src/main/java/org/killbill/billing/account/glue/DefaultAccountModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,8 +18,7 @@
 
 package org.killbill.billing.account.glue;
 
-import org.skife.config.ConfigSource;
-
+import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.account.api.AccountService;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.account.api.DefaultAccountService;
@@ -26,16 +27,13 @@ import org.killbill.billing.account.api.user.DefaultAccountUserApi;
 import org.killbill.billing.account.dao.AccountDao;
 import org.killbill.billing.account.dao.DefaultAccountDao;
 import org.killbill.billing.glue.AccountModule;
-import org.killbill.billing.account.api.AccountInternalApi;
-
-import com.google.inject.AbstractModule;
-
-public class DefaultAccountModule extends AbstractModule implements AccountModule {
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 
-    protected final ConfigSource configSource;
+public class DefaultAccountModule extends KillBillModule implements AccountModule {
 
-    public DefaultAccountModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public DefaultAccountModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     private void installConfig() {
diff --git a/account/src/test/java/org/killbill/billing/account/glue/TestAccountModule.java b/account/src/test/java/org/killbill/billing/account/glue/TestAccountModule.java
index 366316c..8243902 100644
--- a/account/src/test/java/org/killbill/billing/account/glue/TestAccountModule.java
+++ b/account/src/test/java/org/killbill/billing/account/glue/TestAccountModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,9 +18,8 @@
 
 package org.killbill.billing.account.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.mock.glue.MockSubscriptionModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.AuditModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
@@ -27,7 +28,7 @@ import org.killbill.billing.util.glue.TagStoreModule;
 
 public class TestAccountModule extends DefaultAccountModule {
 
-    public TestAccountModule(final ConfigSource configSource) {
+    public TestAccountModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -35,12 +36,12 @@ public class TestAccountModule extends DefaultAccountModule {
     protected void configure() {
         super.configure();
 
-        install(new AuditModule());
+        install(new AuditModule(configSource));
         install(new CacheModule(configSource));
-        install(new CallContextModule());
-        install(new CustomFieldModule());
+        install(new CallContextModule(configSource));
+        install(new CustomFieldModule(configSource));
         // Needed for Audit
-        install(new MockSubscriptionModule());
-        install(new TagStoreModule());
+        install(new MockSubscriptionModule(configSource));
+        install(new TagStoreModule(configSource));
     }
 }
diff --git a/account/src/test/java/org/killbill/billing/account/glue/TestAccountModuleNoDB.java b/account/src/test/java/org/killbill/billing/account/glue/TestAccountModuleNoDB.java
index 52693dc..5a245bb 100644
--- a/account/src/test/java/org/killbill/billing/account/glue/TestAccountModuleNoDB.java
+++ b/account/src/test/java/org/killbill/billing/account/glue/TestAccountModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,17 +18,15 @@
 
 package org.killbill.billing.account.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
 import org.killbill.billing.account.dao.AccountDao;
 import org.killbill.billing.account.dao.MockAccountDao;
 import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
-import org.killbill.billing.util.bus.InMemoryBusModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestAccountModuleNoDB extends TestAccountModule {
 
-    public TestAccountModuleNoDB(final ConfigSource configSource) {
+    public TestAccountModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -39,8 +39,7 @@ public class TestAccountModuleNoDB extends TestAccountModule {
     public void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestNoDBModule());
-        install(new MockNonEntityDaoModule());
-        install(new InMemoryBusModule(configSource));
+        install(new GuicyKillbillTestNoDBModule(configSource));
+        install(new MockNonEntityDaoModule(configSource));
     }
 }
diff --git a/account/src/test/java/org/killbill/billing/account/glue/TestAccountModuleWithEmbeddedDB.java b/account/src/test/java/org/killbill/billing/account/glue/TestAccountModuleWithEmbeddedDB.java
index 2ce5520..4c52d4a 100644
--- a/account/src/test/java/org/killbill/billing/account/glue/TestAccountModuleWithEmbeddedDB.java
+++ b/account/src/test/java/org/killbill/billing/account/glue/TestAccountModuleWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,16 +18,13 @@
 
 package org.killbill.billing.account.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
-import org.killbill.billing.util.glue.BusModule;
-import org.killbill.billing.util.glue.MetricsModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
 
 public class TestAccountModuleWithEmbeddedDB extends TestAccountModule {
 
-    public TestAccountModuleWithEmbeddedDB(final ConfigSource configSource) {
+    public TestAccountModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -33,9 +32,7 @@ public class TestAccountModuleWithEmbeddedDB extends TestAccountModule {
     public void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
-        install(new NonEntityDaoModule());
-        install(new MetricsModule());
-        install(new BusModule(configSource));
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
+        install(new NonEntityDaoModule(configSource));
     }
 }

api/pom.xml 12(+10 -2)

diff --git a/api/pom.xml b/api/pom.xml
index 4258082..9129b20 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,12 +19,12 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-internal-api</artifactId>
     <packaging>jar</packaging>
-    <name>Kill Bill internal apis</name>
+    <name>killbill-api</name>
     <dependencies>
         <dependency>
             <groupId>com.google.code.findbugs</groupId>
@@ -45,6 +45,14 @@
             <artifactId>killbill-api</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing.plugin</groupId>
+            <artifactId>killbill-plugin-api-invoice</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.kill-bill.billing.plugin</groupId>
             <artifactId>killbill-plugin-api-payment</artifactId>
         </dependency>
diff --git a/api/src/main/java/org/killbill/billing/account/api/AccountService.java b/api/src/main/java/org/killbill/billing/account/api/AccountService.java
index 8f05d05..998b2b9 100644
--- a/api/src/main/java/org/killbill/billing/account/api/AccountService.java
+++ b/api/src/main/java/org/killbill/billing/account/api/AccountService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,7 +18,7 @@
 
 package org.killbill.billing.account.api;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 /**
  * The interface {@code AccountService} is a {@code KillbillService} required to handle account operations
@@ -24,5 +26,4 @@ import org.killbill.billing.lifecycle.KillbillService;
  * @see KillbillService
  */
 public interface AccountService extends KillbillService {
-
 }
diff --git a/api/src/main/java/org/killbill/billing/beatrix/bus/api/BeatrixService.java b/api/src/main/java/org/killbill/billing/beatrix/bus/api/BeatrixService.java
index 3cc2a6a..116d178 100644
--- a/api/src/main/java/org/killbill/billing/beatrix/bus/api/BeatrixService.java
+++ b/api/src/main/java/org/killbill/billing/beatrix/bus/api/BeatrixService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,11 +18,10 @@
 
 package org.killbill.billing.beatrix.bus.api;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 /**
  * The interface {@code BeatrixService} is a {@code KillbillService} required to manage the {@code ExternalBus}
- *
  */
 public interface BeatrixService extends KillbillService {
 }
diff --git a/api/src/main/java/org/killbill/billing/callcontext/InternalCallContext.java b/api/src/main/java/org/killbill/billing/callcontext/InternalCallContext.java
index ea11dcf..5370614 100644
--- a/api/src/main/java/org/killbill/billing/callcontext/InternalCallContext.java
+++ b/api/src/main/java/org/killbill/billing/callcontext/InternalCallContext.java
@@ -22,6 +22,7 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 
+import org.joda.time.DateTimeZone;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.CallOrigin;
 import org.killbill.billing.util.callcontext.UserType;
@@ -52,7 +53,7 @@ public class InternalCallContext extends InternalTenantContext {
         this.contextUserType = userType;
         this.reasonCode = reasonCode;
         this.comments = comment;
-        this.createdDate = createdDate;
+        this.createdDate = new DateTime(createdDate, DateTimeZone.UTC);
         this.updatedDate = updatedDate;
     }
 
diff --git a/api/src/main/java/org/killbill/billing/catalog/api/CatalogService.java b/api/src/main/java/org/killbill/billing/catalog/api/CatalogService.java
index 979b350..d250244 100644
--- a/api/src/main/java/org/killbill/billing/catalog/api/CatalogService.java
+++ b/api/src/main/java/org/killbill/billing/catalog/api/CatalogService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,8 +18,7 @@
 
 package org.killbill.billing.catalog.api;
 
-import org.killbill.billing.lifecycle.KillbillService;
-
+import org.killbill.billing.platform.api.KillbillService;
 
 /**
  * The interface {@code CatalogService} is a {@code KillbillService} required to handle catalog operations.
diff --git a/api/src/main/java/org/killbill/billing/currency/api/CurrencyService.java b/api/src/main/java/org/killbill/billing/currency/api/CurrencyService.java
index ee40c48..a46e880 100644
--- a/api/src/main/java/org/killbill/billing/currency/api/CurrencyService.java
+++ b/api/src/main/java/org/killbill/billing/currency/api/CurrencyService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,7 +18,7 @@
 
 package org.killbill.billing.currency.api;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 public interface CurrencyService extends KillbillService {
 }
diff --git a/api/src/main/java/org/killbill/billing/entitlement/EntitlementService.java b/api/src/main/java/org/killbill/billing/entitlement/EntitlementService.java
index 7df0c68..32fc8c3 100644
--- a/api/src/main/java/org/killbill/billing/entitlement/EntitlementService.java
+++ b/api/src/main/java/org/killbill/billing/entitlement/EntitlementService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,7 +18,7 @@
 
 package org.killbill.billing.entitlement;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 public interface EntitlementService extends KillbillService {
 
diff --git a/api/src/main/java/org/killbill/billing/entity/EntityBase.java b/api/src/main/java/org/killbill/billing/entity/EntityBase.java
index 12eb1bd..60c8a1b 100644
--- a/api/src/main/java/org/killbill/billing/entity/EntityBase.java
+++ b/api/src/main/java/org/killbill/billing/entity/EntityBase.java
@@ -90,10 +90,10 @@ public abstract class EntityBase implements Entity {
         if (id != null ? !id.equals(that.id) : that.id != null) {
             return false;
         }
-        if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+        if (createdDate != null ? createdDate.compareTo(that.createdDate) != 0 : that.createdDate != null) {
             return false;
         }
-        if (updatedDate != null ? !updatedDate.equals(that.updatedDate) : that.updatedDate != null) {
+        if (updatedDate != null ? updatedDate.compareTo(that.updatedDate) != 0 : that.updatedDate != null) {
             return false;
         }
 
diff --git a/api/src/main/java/org/killbill/billing/events/PaymentErrorInternalEvent.java b/api/src/main/java/org/killbill/billing/events/PaymentErrorInternalEvent.java
index 0f98db9..3b5889f 100644
--- a/api/src/main/java/org/killbill/billing/events/PaymentErrorInternalEvent.java
+++ b/api/src/main/java/org/killbill/billing/events/PaymentErrorInternalEvent.java
@@ -17,14 +17,15 @@ package org.killbill.billing.events;
 
 import java.util.UUID;
 
+import org.killbill.billing.payment.api.TransactionType;
 
 public interface PaymentErrorInternalEvent extends BusInternalEvent {
 
     public String getMessage();
 
-    public UUID getInvoiceId();
-
     public UUID getAccountId();
 
     public UUID getPaymentId();
+
+    public TransactionType getTransactionType();
 }
diff --git a/api/src/main/java/org/killbill/billing/events/PaymentInfoInternalEvent.java b/api/src/main/java/org/killbill/billing/events/PaymentInfoInternalEvent.java
index ac59f9d..f1265b9 100644
--- a/api/src/main/java/org/killbill/billing/events/PaymentInfoInternalEvent.java
+++ b/api/src/main/java/org/killbill/billing/events/PaymentInfoInternalEvent.java
@@ -20,8 +20,9 @@ import java.math.BigDecimal;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-
-import org.killbill.billing.payment.api.PaymentStatus;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
 
 public interface PaymentInfoInternalEvent extends BusInternalEvent {
 
@@ -29,13 +30,13 @@ public interface PaymentInfoInternalEvent extends BusInternalEvent {
 
     public UUID getAccountId();
 
-    public UUID getInvoiceId();
-
     public BigDecimal getAmount();
 
+    public Currency getCurrency();
+
     public DateTime getEffectiveDate();
 
-    public Integer getPaymentNumber();
+    public TransactionStatus getStatus();
 
-    public PaymentStatus getStatus();
+    public TransactionType getTransactionType();
 }
diff --git a/api/src/main/java/org/killbill/billing/events/PaymentPluginErrorInternalEvent.java b/api/src/main/java/org/killbill/billing/events/PaymentPluginErrorInternalEvent.java
index 7314160..a0198cc 100644
--- a/api/src/main/java/org/killbill/billing/events/PaymentPluginErrorInternalEvent.java
+++ b/api/src/main/java/org/killbill/billing/events/PaymentPluginErrorInternalEvent.java
@@ -17,14 +17,15 @@ package org.killbill.billing.events;
 
 import java.util.UUID;
 
+import org.killbill.billing.payment.api.TransactionType;
 
 public interface PaymentPluginErrorInternalEvent extends BusInternalEvent {
 
     public String getMessage();
 
-    public UUID getInvoiceId();
-
     public UUID getAccountId();
 
     public UUID getPaymentId();
+
+    public TransactionType getTransactionType();
 }
diff --git a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
index b9bd129..cc1c061 100644
--- a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceInternalApi.java
@@ -46,6 +46,10 @@ public interface InvoiceInternalApi {
 
     public InvoicePayment getInvoicePaymentForAttempt(UUID paymentId, InternalTenantContext context) throws InvoiceApiException;
 
+    public InvoicePayment getInvoicePaymentForRefund(UUID paymentId, InternalTenantContext context) throws InvoiceApiException;
+
+    public InvoicePayment getInvoicePaymentForChargeback(UUID paymentId, InternalTenantContext context) throws InvoiceApiException;
+
     public Invoice getInvoiceForPaymentId(UUID paymentId, InternalTenantContext context) throws InvoiceApiException;
 
     /**
@@ -55,13 +59,16 @@ public interface InvoiceInternalApi {
      * @param amount                    amount to refund
      * @param isInvoiceAdjusted         whether the refund should trigger an invoice or invoice item adjustment
      * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
-     * @param paymentCookieId           payment cookie id
+     * @param transactionExternalKey    refund transaction externalKey
      * @param context                   the call callcontext
      * @return the created invoice payment object associated with this refund
      * @throws InvoiceApiException
      */
     public InvoicePayment createRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts,
-                                       UUID paymentCookieId, InternalCallContext context) throws InvoiceApiException;
+                                       String transactionExternalKey, InternalCallContext context) throws InvoiceApiException;
+
+
+    public InvoicePayment createChargeback(UUID paymentId, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
 
     /**
      * Rebalance CBA for account which have credit and unpaid invoices
diff --git a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceService.java b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceService.java
index 9953104..60aba76 100644
--- a/api/src/main/java/org/killbill/billing/invoice/api/InvoiceService.java
+++ b/api/src/main/java/org/killbill/billing/invoice/api/InvoiceService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,8 +18,7 @@
 
 package org.killbill.billing.invoice.api;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 public interface InvoiceService extends KillbillService {
-
 }
diff --git a/api/src/main/java/org/killbill/billing/junction/BillingEventSet.java b/api/src/main/java/org/killbill/billing/junction/BillingEventSet.java
index 8cffd89..432ecb5 100644
--- a/api/src/main/java/org/killbill/billing/junction/BillingEventSet.java
+++ b/api/src/main/java/org/killbill/billing/junction/BillingEventSet.java
@@ -17,16 +17,20 @@
 package org.killbill.billing.junction;
 
 import java.util.List;
+import java.util.Map;
 import java.util.SortedSet;
 import java.util.UUID;
 
 import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.catalog.api.Usage;
 
 public interface BillingEventSet extends SortedSet<BillingEvent> {
 
-    public abstract boolean isAccountAutoInvoiceOff();
+    public boolean isAccountAutoInvoiceOff();
 
-    public abstract BillingMode getRecurringBillingMode();
+    public BillingMode getRecurringBillingMode();
 
-    public abstract List<UUID> getSubscriptionIdsWithAutoInvoiceOff();
+    public List<UUID> getSubscriptionIdsWithAutoInvoiceOff();
+
+    public Map<String, Usage> getUsages();
 }
diff --git a/api/src/main/java/org/killbill/billing/junction/BillingInternalApi.java b/api/src/main/java/org/killbill/billing/junction/BillingInternalApi.java
index 614b32f..f8cda97 100644
--- a/api/src/main/java/org/killbill/billing/junction/BillingInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/junction/BillingInternalApi.java
@@ -18,6 +18,9 @@ package org.killbill.billing.junction;
 
 import java.util.UUID;
 
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
 import org.killbill.billing.callcontext.InternalCallContext;
 
 public interface BillingInternalApi {
diff --git a/api/src/main/java/org/killbill/billing/overdue/OverdueService.java b/api/src/main/java/org/killbill/billing/overdue/OverdueService.java
index ef8267b..23e7fe9 100644
--- a/api/src/main/java/org/killbill/billing/overdue/OverdueService.java
+++ b/api/src/main/java/org/killbill/billing/overdue/OverdueService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,13 @@
 
 package org.killbill.billing.overdue;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 public interface OverdueService extends KillbillService {
+
     String OVERDUE_SERVICE_NAME = "overdue-service";
 
     public String getName();
 
     public OverdueUserApi getUserApi();
-
 }
diff --git a/api/src/main/java/org/killbill/billing/payment/api/PaymentInternalApi.java b/api/src/main/java/org/killbill/billing/payment/api/PaymentInternalApi.java
index a8b0c5d..96ec804 100644
--- a/api/src/main/java/org/killbill/billing/payment/api/PaymentInternalApi.java
+++ b/api/src/main/java/org/killbill/billing/payment/api/PaymentInternalApi.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,26 +18,30 @@
 
 package org.killbill.billing.payment.api;
 
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.killbill.billing.account.api.Account;
-import org.killbill.billing.payment.api.Payment;
-import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.PaymentMethod;
+import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 
 public interface PaymentInternalApi {
 
-    public Payment getPayment(UUID paymentId, InternalTenantContext context)
+    public Payment createPayment(final Account account, final UUID invoiceId,
+                                       @Nullable final BigDecimal amount, final Iterable<PluginProperty> properties, final InternalCallContext internalContext) throws PaymentApiException;
+
+    public Payment getPayment(UUID paymentId, Iterable<PluginProperty> properties, InternalTenantContext context)
             throws PaymentApiException;
 
-    public PaymentMethod getPaymentMethodById(UUID paymentMethodId, final boolean includedInactive, InternalTenantContext context)
+    public PaymentMethod getPaymentMethodById(UUID paymentMethodId, boolean includedInactive, Iterable<PluginProperty> properties, InternalTenantContext context)
             throws PaymentApiException;
 
     public List<Payment> getAccountPayments(UUID accountId, InternalTenantContext context)
             throws PaymentApiException;
 
-    public List<PaymentMethod> getPaymentMethods(Account account, InternalTenantContext context)
+    public List<PaymentMethod> getPaymentMethods(Account account, Iterable<PluginProperty> properties, InternalTenantContext context)
             throws PaymentApiException;
 }
diff --git a/api/src/main/java/org/killbill/billing/payment/api/PaymentService.java b/api/src/main/java/org/killbill/billing/payment/api/PaymentService.java
index 1d2b916..e6ce805 100644
--- a/api/src/main/java/org/killbill/billing/payment/api/PaymentService.java
+++ b/api/src/main/java/org/killbill/billing/payment/api/PaymentService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,12 +18,12 @@
 
 package org.killbill.billing.payment.api;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 public interface PaymentService extends KillbillService {
+
     @Override
     String getName();
 
     PaymentApi getPaymentApi();
-
 }
diff --git a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseService.java b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseService.java
index 106dd63..00dee8e 100644
--- a/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseService.java
+++ b/api/src/main/java/org/killbill/billing/subscription/api/SubscriptionBaseService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,7 +18,7 @@
 
 package org.killbill.billing.subscription.api;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 /**
  * The interface {@code SubscriptionBaseService} is a {@code KillbillService} required to handle subscription operations
diff --git a/api/src/main/java/org/killbill/billing/tenant/api/TenantService.java b/api/src/main/java/org/killbill/billing/tenant/api/TenantService.java
index 8f484c2..a581c66 100644
--- a/api/src/main/java/org/killbill/billing/tenant/api/TenantService.java
+++ b/api/src/main/java/org/killbill/billing/tenant/api/TenantService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,8 +18,7 @@
 
 package org.killbill.billing.tenant.api;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 public interface TenantService extends KillbillService {
-
 }

beatrix/pom.xml 60(+26 -34)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index b1b166c..d6f1f37 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>
@@ -126,37 +126,49 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
-            <artifactId>killbill-osgi</artifactId>
+            <artifactId>killbill-overdue</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
-            <artifactId>killbill-osgi-bundles-jruby</artifactId>
-            <scope>test</scope>
+            <artifactId>killbill-payment</artifactId>
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
-            <artifactId>killbill-osgi-bundles-test-beatrix</artifactId>
+            <artifactId>killbill-payment</artifactId>
+            <type>test-jar</type>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
-            <artifactId>killbill-osgi-bundles-test-payment</artifactId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
-            <artifactId>killbill-overdue</artifactId>
+            <artifactId>killbill-platform-lifecycle</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-osgi</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
-            <artifactId>killbill-payment</artifactId>
+            <artifactId>killbill-platform-osgi-api</artifactId>
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
-            <artifactId>killbill-payment</artifactId>
-            <type>test-jar</type>
+            <artifactId>killbill-platform-osgi-bundles-jruby</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -213,6 +225,10 @@
             <artifactId>killbill-queue</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-xmlloader</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-all</artifactId>
             <scope>test</scope>
@@ -232,28 +248,4 @@
             <scope>test</scope>
         </dependency>
     </dependencies>
-    <build>
-        <plugins>
-            <plugin>
-                <artifactId>maven-antrun-plugin</artifactId>
-                <version>1.4</version>
-                <executions>
-                    <execution>
-                        <id>copy</id>
-                        <phase>initialize</phase>
-                        <goals>
-                            <goal>run</goal>
-                        </goals>
-                        <configuration>
-                            <tasks>
-                                <copy file="${basedir}/../osgi-bundles/tests/beatrix/target/killbill-osgi-bundles-test-beatrix-${project.version}-jar-with-dependencies.jar" tofile="${basedir}/src/test/resources/killbill-osgi-bundles-test-beatrix-${project.version}-jar-with-dependencies.jar"></copy>
-                                <copy file="${basedir}/../osgi-bundles/tests/payment/target/killbill-osgi-bundles-test-payment-${project.version}-jar-with-dependencies.jar" tofile="${basedir}/src/test/resources/killbill-osgi-bundles-test-payment-${project.version}-jar-with-dependencies.jar"></copy>
-                                <copy file="${basedir}/../osgi-bundles/bundles/jruby/target/killbill-osgi-bundles-jruby-${project.version}.jar" tofile="${basedir}/src/test/resources/killbill-osgi-bundles-jruby-${project.version}.jar"></copy>
-                            </tasks>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-    </build>
 </project>
diff --git a/beatrix/src/main/java/org/killbill/billing/beatrix/DefaultBeatrixService.java b/beatrix/src/main/java/org/killbill/billing/beatrix/DefaultBeatrixService.java
index 5f27e34..7d071ea 100644
--- a/beatrix/src/main/java/org/killbill/billing/beatrix/DefaultBeatrixService.java
+++ b/beatrix/src/main/java/org/killbill/billing/beatrix/DefaultBeatrixService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,14 +19,12 @@
 package org.killbill.billing.beatrix;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 import org.killbill.billing.beatrix.bus.api.BeatrixService;
 import org.killbill.billing.beatrix.extbus.BeatrixListener;
-import org.killbill.billing.beatrix.glue.BeatrixModule;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
 import org.killbill.bus.api.PersistentBus;
-import org.killbill.billing.lifecycle.LifecycleHandlerType;
-import org.killbill.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
 
 public class DefaultBeatrixService implements BeatrixService {
 
@@ -32,12 +32,10 @@ public class DefaultBeatrixService implements BeatrixService {
 
     private final BeatrixListener beatrixListener;
     private final PersistentBus eventBus;
-    private final PersistentBus externalBus;
 
     @Inject
-    public DefaultBeatrixService(final PersistentBus eventBus, @Named(BeatrixModule.EXTERNAL_BUS) final PersistentBus externalBus, final BeatrixListener beatrixListener) {
+    public DefaultBeatrixService(final PersistentBus eventBus, final BeatrixListener beatrixListener) {
         this.eventBus = eventBus;
-        this.externalBus = externalBus;
         this.beatrixListener = beatrixListener;
     }
 
@@ -50,7 +48,7 @@ public class DefaultBeatrixService implements BeatrixService {
     public void registerForNotifications() {
         try {
             eventBus.register(beatrixListener);
-        } catch (PersistentBus.EventBusException e) {
+        } catch (final PersistentBus.EventBusException e) {
             throw new RuntimeException("Unable to register to the EventBus!", e);
         }
     }
@@ -59,18 +57,8 @@ public class DefaultBeatrixService implements BeatrixService {
     public void unregisterForNotifications() {
         try {
             eventBus.unregister(beatrixListener);
-        } catch (PersistentBus.EventBusException e) {
+        } catch (final PersistentBus.EventBusException e) {
             throw new RuntimeException("Unable to unregister to the EventBus!", e);
         }
     }
-
-    @LifecycleHandlerType(LifecycleLevel.INIT_BUS)
-    public void startBus() {
-        externalBus.start();
-    }
-
-    @LifecycleHandlerType(LifecycleLevel.STOP_BUS)
-    public void stopBus() {
-        externalBus.stop();
-    }
 }
diff --git a/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java b/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java
index 0648663..3840cd5 100644
--- a/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java
+++ b/beatrix/src/main/java/org/killbill/billing/beatrix/extbus/BeatrixListener.java
@@ -21,17 +21,8 @@ import java.util.UUID;
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ObjectType;
-import org.killbill.billing.account.api.Account;
-import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.billing.beatrix.glue.BeatrixModule;
-import org.killbill.bus.api.BusEvent;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.entitlement.EntitlementTransitionType;
 import org.killbill.billing.events.AccountChangeInternalEvent;
@@ -52,16 +43,25 @@ import org.killbill.billing.events.PaymentPluginErrorInternalEvent;
 import org.killbill.billing.events.SubscriptionInternalEvent;
 import org.killbill.billing.events.UserTagCreationInternalEvent;
 import org.killbill.billing.events.UserTagDeletionInternalEvent;
+import org.killbill.billing.lifecycle.glue.BusModule;
 import org.killbill.billing.notification.plugin.api.ExtBusEventType;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.CallOrigin;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.UserType;
 import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.bus.api.BusEvent;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.datatype.joda.JodaModule;
+import com.google.common.eventbus.AllowConcurrentEvents;
 import com.google.common.eventbus.Subscribe;
 
 public class BeatrixListener {
@@ -72,60 +72,62 @@ public class BeatrixListener {
     private final InternalCallContextFactory internalCallContextFactory;
     private final AccountInternalApi accountApi;
     private final NonEntityDao nonEntityDao;
+    private final CacheControllerDispatcher cacheControllerDispatcher;
 
     protected final ObjectMapper objectMapper;
 
     @Inject
-    public BeatrixListener(@Named(BeatrixModule.EXTERNAL_BUS) final PersistentBus externalBus,
+    public BeatrixListener(@Named(BusModule.EXTERNAL_BUS_NAMED) final PersistentBus externalBus,
                            final InternalCallContextFactory internalCallContextFactory,
                            final AccountInternalApi accountApi,
+                           final  CacheControllerDispatcher cacheControllerDispatcher,
                            final NonEntityDao nonEntityDao) {
         this.externalBus = externalBus;
         this.internalCallContextFactory = internalCallContextFactory;
         this.accountApi = accountApi;
         this.nonEntityDao = nonEntityDao;
+        this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.objectMapper = new ObjectMapper();
         objectMapper.registerModule(new JodaModule());
         objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
     }
 
+    @AllowConcurrentEvents
     @Subscribe
     public void handleAllInternalKillbillEvents(final BusInternalEvent event) {
-
         final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "BeatrixListener", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
         try {
             final BusEvent externalEvent = computeExtBusEventEntryFromBusInternalEvent(event, internalContext);
             if (externalEvent != null) {
                 externalBus.post(externalEvent);
             }
-        } catch (EventBusException e) {
+        } catch (final EventBusException e) {
             log.warn("Failed to dispatch external bus events", e);
         }
     }
 
     private BusEvent computeExtBusEventEntryFromBusInternalEvent(final BusInternalEvent event, final InternalCallContext context) {
-
         ObjectType objectType = null;
         UUID objectId = null;
         ExtBusEventType eventBusType = null;
 
         switch (event.getBusEventType()) {
             case ACCOUNT_CREATE:
-                AccountCreationInternalEvent realEventACR = (AccountCreationInternalEvent) event;
+                final AccountCreationInternalEvent realEventACR = (AccountCreationInternalEvent) event;
                 objectType = ObjectType.ACCOUNT;
                 objectId = realEventACR.getId();
                 eventBusType = ExtBusEventType.ACCOUNT_CREATION;
                 break;
 
             case ACCOUNT_CHANGE:
-                AccountChangeInternalEvent realEventACH = (AccountChangeInternalEvent) event;
+                final AccountChangeInternalEvent realEventACH = (AccountChangeInternalEvent) event;
                 objectType = ObjectType.ACCOUNT;
                 objectId = realEventACH.getAccountId();
                 eventBusType = ExtBusEventType.ACCOUNT_CHANGE;
                 break;
 
             case SUBSCRIPTION_TRANSITION:
-                SubscriptionInternalEvent realEventST = (SubscriptionInternalEvent) event;
+                final SubscriptionInternalEvent realEventST = (SubscriptionInternalEvent) event;
                 objectType = ObjectType.SUBSCRIPTION;
                 objectId = realEventST.getSubscriptionId();
                 if (realEventST.getTransitionType() == SubscriptionBaseTransitionType.CREATE ||
@@ -145,7 +147,7 @@ public class BeatrixListener {
                 break;
 
             case ENTITLEMENT_TRANSITION:
-                EntitlementInternalEvent realEventET = (EntitlementInternalEvent) event;
+                final EntitlementInternalEvent realEventET = (EntitlementInternalEvent) event;
                 objectType = ObjectType.BUNDLE;
                 objectId = realEventET.getBundleId();
                 if (realEventET.getTransitionType() == EntitlementTransitionType.BLOCK_BUNDLE) {
@@ -156,84 +158,84 @@ public class BeatrixListener {
                 break;
 
             case INVOICE_CREATION:
-                InvoiceCreationInternalEvent realEventInv = (InvoiceCreationInternalEvent) event;
+                final InvoiceCreationInternalEvent realEventInv = (InvoiceCreationInternalEvent) event;
                 objectType = ObjectType.INVOICE;
                 objectId = realEventInv.getInvoiceId();
                 eventBusType = ExtBusEventType.INVOICE_CREATION;
                 break;
 
             case INVOICE_ADJUSTMENT:
-                InvoiceAdjustmentInternalEvent realEventInvAdj = (InvoiceAdjustmentInternalEvent) event;
+                final InvoiceAdjustmentInternalEvent realEventInvAdj = (InvoiceAdjustmentInternalEvent) event;
                 objectType = ObjectType.INVOICE;
                 objectId = realEventInvAdj.getInvoiceId();
                 eventBusType = ExtBusEventType.INVOICE_ADJUSTMENT;
                 break;
 
             case PAYMENT_INFO:
-                PaymentInfoInternalEvent realEventPay = (PaymentInfoInternalEvent) event;
+                final PaymentInfoInternalEvent realEventPay = (PaymentInfoInternalEvent) event;
                 objectType = ObjectType.PAYMENT;
                 objectId = realEventPay.getPaymentId();
                 eventBusType = ExtBusEventType.PAYMENT_SUCCESS;
                 break;
 
             case PAYMENT_ERROR:
-                PaymentErrorInternalEvent realEventPayErr = (PaymentErrorInternalEvent) event;
+                final PaymentErrorInternalEvent realEventPayErr = (PaymentErrorInternalEvent) event;
                 objectType = ObjectType.PAYMENT;
                 objectId = realEventPayErr.getPaymentId();
                 eventBusType = ExtBusEventType.PAYMENT_FAILED;
                 break;
 
             case PAYMENT_PLUGIN_ERROR:
-                PaymentPluginErrorInternalEvent realEventPayPluginErr = (PaymentPluginErrorInternalEvent) event;
+                final PaymentPluginErrorInternalEvent realEventPayPluginErr = (PaymentPluginErrorInternalEvent) event;
                 objectType = ObjectType.PAYMENT;
                 objectId = realEventPayPluginErr.getPaymentId();
                 eventBusType = ExtBusEventType.PAYMENT_FAILED;
                 break;
 
             case OVERDUE_CHANGE:
-                OverdueChangeInternalEvent realEventOC = (OverdueChangeInternalEvent) event;
+                final OverdueChangeInternalEvent realEventOC = (OverdueChangeInternalEvent) event;
                 objectType = ObjectType.ACCOUNT;
                 objectId = realEventOC.getOverdueObjectId();
                 eventBusType = ExtBusEventType.OVERDUE_CHANGE;
                 break;
 
             case USER_TAG_CREATION:
-                UserTagCreationInternalEvent realUserTagEventCr = (UserTagCreationInternalEvent) event;
+                final UserTagCreationInternalEvent realUserTagEventCr = (UserTagCreationInternalEvent) event;
                 objectType = ObjectType.TAG;
                 objectId = realUserTagEventCr.getTagId();
                 eventBusType = ExtBusEventType.TAG_CREATION;
                 break;
 
             case CONTROL_TAG_CREATION:
-                ControlTagCreationInternalEvent realTagEventCr = (ControlTagCreationInternalEvent) event;
+                final ControlTagCreationInternalEvent realTagEventCr = (ControlTagCreationInternalEvent) event;
                 objectType = ObjectType.TAG;
                 objectId = realTagEventCr.getTagId();
                 eventBusType = ExtBusEventType.TAG_CREATION;
                 break;
 
             case USER_TAG_DELETION:
-                UserTagDeletionInternalEvent realUserTagEventDel = (UserTagDeletionInternalEvent) event;
+                final UserTagDeletionInternalEvent realUserTagEventDel = (UserTagDeletionInternalEvent) event;
                 objectType = ObjectType.TAG;
                 objectId = realUserTagEventDel.getObjectId();
                 eventBusType = ExtBusEventType.TAG_DELETION;
                 break;
 
             case CONTROL_TAG_DELETION:
-                ControlTagDeletionInternalEvent realTagEventDel = (ControlTagDeletionInternalEvent) event;
+                final ControlTagDeletionInternalEvent realTagEventDel = (ControlTagDeletionInternalEvent) event;
                 objectType = ObjectType.TAG;
                 objectId = realTagEventDel.getTagId();
                 eventBusType = ExtBusEventType.TAG_DELETION;
                 break;
 
             case CUSTOM_FIELD_CREATION:
-                CustomFieldCreationEvent realCustomEveventCr = (CustomFieldCreationEvent) event;
+                final CustomFieldCreationEvent realCustomEveventCr = (CustomFieldCreationEvent) event;
                 objectType = ObjectType.CUSTOM_FIELD;
                 objectId = realCustomEveventCr.getCustomFieldId();
                 eventBusType = ExtBusEventType.CUSTOM_FIELD_CREATION;
                 break;
 
             case CUSTOM_FIELD_DELETION:
-                CustomFieldDeletionEvent realCustomEveventDel = (CustomFieldDeletionEvent) event;
+                final CustomFieldDeletionEvent realCustomEveventDel = (CustomFieldDeletionEvent) event;
                 objectType = ObjectType.CUSTOM_FIELD;
                 objectId = realCustomEveventDel.getCustomFieldId();
                 eventBusType = ExtBusEventType.CUSTOM_FIELD_DELETION;
@@ -241,27 +243,19 @@ public class BeatrixListener {
 
             default:
         }
-        final UUID accountId = getAccountIdFromRecordId(event.getBusEventType(), objectId, context.getAccountRecordId(), context);
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
+        final UUID accountId = getAccountIdFromRecordId(event.getBusEventType(), objectId, context.getAccountRecordId());
+        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
 
         return eventBusType != null ?
                new DefaultBusExternalEvent(objectId, objectType, eventBusType, accountId, tenantId, context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()) :
                null;
     }
 
-    private final UUID getAccountIdFromRecordId(final BusInternalEventType eventType, final UUID objectId, final Long recordId, final InternalCallContext context) {
-
+    private UUID getAccountIdFromRecordId(final BusInternalEventType eventType, final UUID objectId, final Long recordId) {
         // accountRecord_id is not set for ACCOUNT_CREATE event as we are in the transaction and value is known yet
         if (eventType == BusInternalEventType.ACCOUNT_CREATE) {
             return objectId;
         }
-        try {
-            final Account account = accountApi.getAccountByRecordId(recordId, context);
-            return account.getId();
-        } catch (final AccountApiException e) {
-            log.warn("Failed to retrieve acount from recordId {}", recordId);
-            return null;
-        }
+        return nonEntityDao.retrieveIdFromObject(recordId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
     }
-
 }
diff --git a/beatrix/src/main/java/org/killbill/billing/beatrix/glue/BeatrixModule.java b/beatrix/src/main/java/org/killbill/billing/beatrix/glue/BeatrixModule.java
index 07be9d5..b271c5b 100644
--- a/beatrix/src/main/java/org/killbill/billing/beatrix/glue/BeatrixModule.java
+++ b/beatrix/src/main/java/org/killbill/billing/beatrix/glue/BeatrixModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,50 +18,27 @@
 
 package org.killbill.billing.beatrix.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.beatrix.DefaultBeatrixService;
 import org.killbill.billing.beatrix.bus.api.BeatrixService;
 import org.killbill.billing.beatrix.extbus.BeatrixListener;
-import org.killbill.billing.beatrix.lifecycle.DefaultLifecycle;
-import org.killbill.billing.beatrix.lifecycle.Lifecycle;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBusConfig;
-import org.killbill.billing.util.glue.BusProvider;
-
-import com.google.inject.AbstractModule;
-import com.google.inject.Key;
-import com.google.inject.name.Names;
-
-public class BeatrixModule extends AbstractModule {
-
-    public static final String EXTERNAL_BUS = "externalBus";
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 
-    private final ConfigSource configSource;
+public class BeatrixModule extends KillBillModule {
 
-    public BeatrixModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public BeatrixModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     @Override
     protected void configure() {
-        installLifecycle();
         installExternalBus();
     }
 
-    protected void installLifecycle() {
-        bind(Lifecycle.class).to(DefaultLifecycle.class).asEagerSingleton();
-    }
-
     protected void installExternalBus() {
         bind(BeatrixService.class).to(DefaultBeatrixService.class);
         bind(DefaultBeatrixService.class).asEagerSingleton();
 
-        final PersistentBusConfig extBusConfig = new ExternalPersistentBusConfig(configSource);
-
-        bind(BusProvider.class).annotatedWith(Names.named(EXTERNAL_BUS)).toInstance(new BusProvider(extBusConfig));
-        bind(PersistentBus.class).annotatedWith(Names.named(EXTERNAL_BUS)).toProvider(Key.get(BusProvider.class, Names.named(EXTERNAL_BUS))).asEagerSingleton();
-
         bind(BeatrixListener.class).asEagerSingleton();
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteWithEmbeddedDB.java b/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteWithEmbeddedDB.java
index 9f490cb..29350d9 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteWithEmbeddedDB.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/BeatrixTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,17 +18,13 @@
 
 package org.killbill.billing.beatrix;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
-
 import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
-import org.killbill.billing.TestKillbillConfigSource;
-import org.killbill.billing.util.KillbillConfigSource;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public abstract class BeatrixTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEmbeddedDB {
 
     @Override
-    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
-        return new TestKillbillConfigSource("/beatrix.properties");
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/beatrix.properties");
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
index 2c05cc1..9bb038e 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,115 +18,86 @@
 
 package org.killbill.billing.beatrix.integration;
 
-import java.util.Set;
-
 import org.killbill.billing.DBTestingHelper;
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
-import org.killbill.billing.account.api.AccountService;
 import org.killbill.billing.account.glue.DefaultAccountModule;
 import org.killbill.billing.api.TestApiListener;
-import org.killbill.billing.beatrix.DefaultBeatrixService;
 import org.killbill.billing.beatrix.glue.BeatrixModule;
 import org.killbill.billing.beatrix.integration.overdue.IntegrationTestOverdueModule;
-import org.killbill.billing.beatrix.lifecycle.DefaultLifecycle;
-import org.killbill.billing.beatrix.lifecycle.Lifecycle;
 import org.killbill.billing.beatrix.util.AccountChecker;
 import org.killbill.billing.beatrix.util.AuditChecker;
 import org.killbill.billing.beatrix.util.InvoiceChecker;
 import org.killbill.billing.beatrix.util.PaymentChecker;
 import org.killbill.billing.beatrix.util.RefundChecker;
 import org.killbill.billing.beatrix.util.SubscriptionChecker;
-import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.catalog.glue.CatalogModule;
 import org.killbill.billing.currency.glue.CurrencyModule;
-import org.killbill.billing.entitlement.EntitlementService;
 import org.killbill.billing.entitlement.glue.DefaultEntitlementModule;
-import org.killbill.billing.invoice.api.InvoiceService;
 import org.killbill.billing.invoice.generator.DefaultInvoiceGenerator;
 import org.killbill.billing.invoice.generator.InvoiceGenerator;
 import org.killbill.billing.invoice.glue.DefaultInvoiceModule;
 import org.killbill.billing.junction.glue.DefaultJunctionModule;
-import org.killbill.billing.lifecycle.KillbillService;
-import org.killbill.billing.osgi.DefaultOSGIService;
-import org.killbill.billing.osgi.glue.DefaultOSGIModule;
-import org.killbill.billing.overdue.OverdueService;
-import org.killbill.billing.payment.api.PaymentService;
 import org.killbill.billing.payment.glue.PaymentModule;
 import org.killbill.billing.payment.provider.MockPaymentProviderPluginModule;
-import org.killbill.billing.subscription.api.SubscriptionBaseService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.glue.DefaultSubscriptionModule;
 import org.killbill.billing.tenant.glue.TenantModule;
-import org.killbill.billing.usage.glue.TestUsageModule;
 import org.killbill.billing.usage.glue.UsageModule;
 import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.billing.util.email.EmailModule;
 import org.killbill.billing.util.email.templates.TemplateModule;
 import org.killbill.billing.util.glue.AuditModule;
-import org.killbill.billing.util.glue.BusModule;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
 import org.killbill.billing.util.glue.ExportModule;
 import org.killbill.billing.util.glue.GlobalLockerModule;
-import org.killbill.billing.util.glue.MetricsModule;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.killbill.billing.util.glue.KillBillShiroModule;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
-import org.killbill.billing.util.glue.NotificationQueueModule;
 import org.killbill.billing.util.glue.RecordIdModule;
+import org.killbill.billing.util.glue.SecurityModule;
 import org.killbill.billing.util.glue.TagStoreModule;
-import org.killbill.billing.util.svcsapi.bus.BusService;
-import org.skife.config.ConfigSource;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.inject.AbstractModule;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
 
-public class BeatrixIntegrationModule extends AbstractModule {
+public class BeatrixIntegrationModule extends KillBillModule {
 
     public static final String NON_OSGI_PLUGIN_NAME = "yoyo";
 
     // Same name the osgi-payment-test plugin uses to register its service
     public static final String OSGI_PLUGIN_NAME = "osgi-payment-plugin";
 
-    private final ConfigSource configSource;
-
-    public BeatrixIntegrationModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public BeatrixIntegrationModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     @Override
     protected void configure() {
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
-        install(new GlobalLockerModule(DBTestingHelper.get().getDBEngine()));
+        install(new GuicyKillbillTestWithEmbeddedDBModule(true, configSource));
+        install(new GlobalLockerModule(DBTestingHelper.get().getInstance().getDBEngine(), configSource));
         install(new CacheModule(configSource));
         install(new EmailModule(configSource));
-        install(new CallContextModule());
-        install(new MetricsModule());
-        install(new BusModule(configSource));
-        install(new NotificationQueueModule(configSource));
-        install(new TagStoreModule());
-        install(new CustomFieldModule());
+        install(new CallContextModule(configSource));
+        install(new TagStoreModule(configSource));
+        install(new CustomFieldModule(configSource));
         install(new DefaultAccountModule(configSource));
         install(new CatalogModule(configSource));
         install(new DefaultSubscriptionModule(configSource));
         install(new DefaultEntitlementModule(configSource));
         install(new DefaultInvoiceModuleWithSwitchRepairLogic(configSource));
-        install(new TemplateModule());
+        install(new TemplateModule(configSource));
         install(new PaymentPluginMockModule(configSource));
         install(new DefaultJunctionModule(configSource));
         install(new IntegrationTestOverdueModule(configSource));
-        install(new AuditModule());
+        install(new AuditModule(configSource));
         install(new CurrencyModule(configSource));
         install(new TenantModule(configSource));
-        install(new ExportModule());
-        install(new DefaultOSGIModule(configSource));
-        install(new NonEntityDaoModule());
-        install(new RecordIdModule());
-        install(new BeatrixModuleWithSubsetLifecycle(configSource));
-
-
-        // STEPH_USAGE is that really what we want.
-        install(new TestUsageModule(configSource));
+        install(new ExportModule(configSource));
+        install(new NonEntityDaoModule(configSource));
+        install(new RecordIdModule(configSource));
+        install(new UsageModule(configSource));
+        install(new SecurityModule(configSource));
+        install(new KillBillShiroModule(configSource));
+        install(new BeatrixModule(configSource));
 
         bind(AccountChecker.class).asEagerSingleton();
         bind(SubscriptionChecker.class).asEagerSingleton();
@@ -138,7 +111,7 @@ public class BeatrixIntegrationModule extends AbstractModule {
 
     private static final class DefaultInvoiceModuleWithSwitchRepairLogic extends DefaultInvoiceModule {
 
-        public DefaultInvoiceModuleWithSwitchRepairLogic(final ConfigSource configSource) {
+        private DefaultInvoiceModuleWithSwitchRepairLogic(final KillbillConfigSource configSource) {
             super(configSource);
         }
 
@@ -149,48 +122,13 @@ public class BeatrixIntegrationModule extends AbstractModule {
 
     private static final class PaymentPluginMockModule extends PaymentModule {
 
-        public PaymentPluginMockModule(final ConfigSource configSource) {
+        private PaymentPluginMockModule(final KillbillConfigSource configSource) {
             super(configSource);
         }
 
         @Override
         protected void installPaymentProviderPlugins(final PaymentConfig config) {
-            install(new MockPaymentProviderPluginModule(NON_OSGI_PLUGIN_NAME, TestIntegrationBase.getClock()));
-        }
-    }
-
-    private static final class SubsetDefaultLifecycle extends DefaultLifecycle {
-
-        @Inject
-        public SubsetDefaultLifecycle(final Injector injector) {
-            super(injector);
-        }
-
-        @Override
-        protected Set<? extends KillbillService> findServices() {
-            return new ImmutableSet.Builder<KillbillService>().add(injector.getInstance(AccountService.class))
-                                                              .add(injector.getInstance(BusService.class))
-                                                              .add(injector.getInstance(CatalogService.class))
-                                                              .add(injector.getInstance(SubscriptionBaseService.class))
-                                                              .add(injector.getInstance(EntitlementService.class))
-                                                              .add(injector.getInstance(InvoiceService.class))
-                                                              .add(injector.getInstance(PaymentService.class))
-                                                              .add(injector.getInstance(OverdueService.class))
-                                                              .add(injector.getInstance(DefaultBeatrixService.class))
-                                                              .add(injector.getInstance(DefaultOSGIService.class))
-                                                              .build();
-        }
-    }
-
-    private static final class BeatrixModuleWithSubsetLifecycle extends BeatrixModule {
-
-        public BeatrixModuleWithSubsetLifecycle(final ConfigSource configSource) {
-            super(configSource);
-        }
-
-        @Override
-        protected void installLifecycle() {
-            bind(Lifecycle.class).to(SubsetDefaultLifecycle.class).asEagerSingleton();
+            install(new MockPaymentProviderPluginModule(NON_OSGI_PLUGIN_NAME, TestIntegrationBase.getClock(), configSource));
         }
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/IntegrationTestOverdueModule.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/IntegrationTestOverdueModule.java
index 8cbeba1..edee13b 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/IntegrationTestOverdueModule.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/IntegrationTestOverdueModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,13 @@
 
 package org.killbill.billing.beatrix.integration.overdue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.overdue.OverdueService;
 import org.killbill.billing.overdue.glue.DefaultOverdueModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class IntegrationTestOverdueModule extends DefaultOverdueModule {
 
-    public IntegrationTestOverdueModule(final ConfigSource configSource) {
+    public IntegrationTestOverdueModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/MockOverdueService.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/MockOverdueService.java
index d054751..aece71d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/MockOverdueService.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/MockOverdueService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -18,6 +20,7 @@ package org.killbill.billing.beatrix.integration.overdue;
 
 import javax.inject.Named;
 
+import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.overdue.OverdueProperties;
 import org.killbill.billing.overdue.OverdueUserApi;
 import org.killbill.billing.overdue.glue.DefaultOverdueModule;
@@ -25,7 +28,6 @@ import org.killbill.billing.overdue.listener.OverdueListener;
 import org.killbill.billing.overdue.notification.OverdueNotifier;
 import org.killbill.billing.overdue.service.DefaultOverdueService;
 import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
-import org.killbill.billing.util.svcsapi.bus.BusService;
 
 import com.google.inject.Inject;
 
@@ -40,7 +42,5 @@ public class MockOverdueService extends DefaultOverdueService {
     }
 
     public synchronized void loadConfig() throws ServiceException {
-
     }
-
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
index ec57bce..613cd98 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueBase.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -18,11 +20,9 @@ package org.killbill.billing.beatrix.integration.overdue;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
+import java.util.UUID;
 import java.util.concurrent.Callable;
 
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.beatrix.integration.BeatrixIntegrationModule;
 import org.killbill.billing.beatrix.integration.TestIntegrationBase;
@@ -30,10 +30,13 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.api.SubscriptionBundle;
 import org.killbill.billing.overdue.OverdueService;
+import org.killbill.billing.overdue.api.DefaultOverdueUserApi;
 import org.killbill.billing.overdue.config.OverdueConfig;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
 import org.killbill.billing.payment.api.TestPaymentMethodPluginBase;
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.XMLLoader;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
 
 import static com.jayway.awaitility.Awaitility.await;
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -58,11 +61,13 @@ public abstract class TestOverdueBase extends TestIntegrationBase {
         final InputStream is = new ByteArrayInputStream(configXml.getBytes());
         final OverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, OverdueConfig.class);
         overdueWrapperFactory.setOverdueConfig(config);
+        overdueListener.setOverdueConfig(config);
+        ((DefaultOverdueUserApi) overdueUserApi).setOverdueConfig(config);
 
         account = createAccountWithNonOsgiPaymentMethod(getAccountData(0));
         assertNotNull(account);
 
-        paymentApi.addPaymentMethod(BeatrixIntegrationModule.NON_OSGI_PLUGIN_NAME, account, true, paymentMethodPlugin, callContext);
+        paymentApi.addPaymentMethod(account, UUID.randomUUID().toString(), BeatrixIntegrationModule.NON_OSGI_PLUGIN_NAME, true, paymentMethodPlugin, PLUGIN_PROPERTIES, callContext);
         productName = "Shotgun";
         term = BillingPeriod.MONTHLY;
         paymentPlugin.clear();
@@ -81,7 +86,7 @@ public abstract class TestOverdueBase extends TestIntegrationBase {
                     return expected.equals(blockingApi.getBlockingStateForService(account.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext).getStateName());
                 }
             });
-        } catch (Exception e) {
+        } catch (final Exception e) {
             Assert.assertEquals(blockingApi.getBlockingStateForService(account.getId(), BlockingStateType.ACCOUNT, OverdueService.OVERDUE_SERVICE_NAME, internalCallContext).getStateName(), expected, "Got exception: " + e.toString());
         }
     }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index c4469f1..29202ee 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -22,11 +24,10 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.beatrix.integration.BeatrixIntegrationModule;
@@ -40,10 +41,16 @@ import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.entitlement.api.Entitlement;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
 import org.killbill.billing.junction.DefaultBlockingState;
 import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
@@ -113,7 +120,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         return configXml;
     }
 
-    @Test(groups = "slow", description = "Test overdue stages and return to clear prior to CTD")
+    @Test(groups = "slow", description = "Test overdue stages and return to clear prior to CTD", enabled=false)
     public void testOverdueStages1() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
@@ -198,7 +205,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
     }
 
-    @Test(groups = "slow", description = "Test overdue stages and return to clear on CTD")
+    @Test(groups = "slow", description = "Test overdue stages and return to clear on CTD", enabled=false)
     public void testOverdueStages2() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
@@ -279,7 +286,6 @@ public class TestOverdueIntegration extends TestOverdueBase {
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 10), new LocalDate(2012, 7, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-169.32")),
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 7, 31), InvoiceItemType.CBA_ADJ, new BigDecimal("169.32")));
 
-
         invoiceChecker.checkInvoice(account.getId(), 4, callContext,
                                     // New invoice for the partial period since we unblocked on the 1st and so are missing the 31 july
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
@@ -296,7 +302,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(BigDecimal.ZERO), 0);
     }
 
-    @Test(groups = "slow", description = "Test overdue stages and return to clear after CTD")
+    @Test(groups = "slow", description = "Test overdue stages and return to clear after CTD", enabled=false)
     public void testOverdueStages3() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
@@ -372,7 +378,6 @@ public class TestOverdueIntegration extends TestOverdueBase {
         // 2012, 8, 1 => Nothing should have happened
         addDaysAndCheckForCompletion(1);
 
-
         allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(true);
 
         invoiceChecker.checkInvoice(account.getId(), 3, callContext,
@@ -401,7 +406,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
     // This test is similar to the previous one except that instead of moving the clock to check we will get the next invoice
     // at the end, we carry a change of plan.
     //
-    @Test(groups = "slow", description = "Test overdue stages and follow with an immediate change of plan")
+    @Test(groups = "slow", description = "Test overdue stages and follow with an immediate change of plan", enabled=false)
     public void testOverdueStagesFollowedWithImmediateChange1() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
@@ -490,7 +495,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-14.49")), 0);
     }
 
-    @Test(groups = "slow", description = "Test overdue stages and follow with an immediate change of plan and use of credit", enabled=false)
+    @Test(groups = "slow", description = "Test overdue stages and follow with an immediate change of plan and use of credit", enabled = false)
     public void testOverdueStagesFollowedWithImmediateChange2() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
@@ -525,10 +530,9 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState("OD1");
 
         // 2012, 7, 10 => Retry P0
-        addDaysAndCheckForCompletion(8, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.TAG     );
+        addDaysAndCheckForCompletion(8, NextEvent.BLOCK, NextEvent.PAYMENT_ERROR, NextEvent.TAG);
         checkODState("OD2");
 
-
         // 2012, 7, 18 => Retry P0
         addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR);
         checkODState("OD2");
@@ -539,7 +543,6 @@ public class TestOverdueIntegration extends TestOverdueBase {
 
         allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(false);
 
-
         invoiceChecker.checkInvoice(account.getId(), 2, callContext,
                                     // New invoice for the part that was unblocked up to the BCD
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2013, 5, 31), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
@@ -547,7 +550,6 @@ public class TestOverdueIntegration extends TestOverdueBase {
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2013, 5, 31), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-1998.9012")),
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 23), new LocalDate(2012, 7, 23), InvoiceItemType.CBA_ADJ, new BigDecimal("2084.36")));
 
-
         // Move to 2012, 7, 31 and Make a change of plan
         addDaysAndCheckForCompletion(8, NextEvent.INVOICE, NextEvent.PAYMENT);
 
@@ -571,7 +573,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-1800")), 0);
     }
 
-    @Test(groups = "slow", description = "Test overdue stages with missing payment method")
+    @Test(groups = "slow", description = "Test overdue stages with missing payment method", enabled=false)
     public void testOverdueStateIfNoPaymentMethod() throws Exception {
         // This test is similar to the previous one - but there is no default payment method on the account, so there
         // won't be any payment retry
@@ -636,7 +638,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkChangePlanWithOverdueState(baseEntitlement, true, true);
 
         // Add a payment method and set it as default
-        paymentApi.addPaymentMethod(BeatrixIntegrationModule.NON_OSGI_PLUGIN_NAME, account, true, paymentMethodPlugin, callContext);
+        paymentApi.addPaymentMethod(account, UUID.randomUUID().toString(), BeatrixIntegrationModule.NON_OSGI_PLUGIN_NAME, true, paymentMethodPlugin, PLUGIN_PROPERTIES, callContext);
 
         allowPaymentsAndResetOverdueToClearByPayingAllUnpaidInvoices(false);
 
@@ -646,8 +648,6 @@ public class TestOverdueIntegration extends TestOverdueBase {
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 15), new LocalDate(2012, 7, 25), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-80.63")),
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 25), new LocalDate(2012, 7, 25), InvoiceItemType.CBA_ADJ, new BigDecimal("80.63")));
 
-
-
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 7, 31), callContext);
 
         checkChangePlanWithOverdueState(baseEntitlement, false, false);
@@ -668,7 +668,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         assertEquals(invoiceUserApi.getAccountBalance(account.getId(), callContext).compareTo(new BigDecimal("-12.89")), 0);
     }
 
-    @Test(groups = "slow", description = "Test overdue from non paid external charge")
+    @Test(groups = "slow", description = "Test overdue from non paid external charge", enabled=false)
     public void testShouldBeInOverdueAfterExternalCharge() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
@@ -682,7 +682,8 @@ public class TestOverdueIntegration extends TestOverdueBase {
         // Create an external charge on a new invoice
         addDaysAndCheckForCompletion(5);
         busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
-        invoiceUserApi.insertExternalChargeForBundle(account.getId(), bundle.getId(), BigDecimal.TEN, "For overdue", new LocalDate(2012, 5, 6), Currency.USD, callContext);
+        final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), bundle.getId(), "For overdue", new LocalDate(2012, 5, 6), BigDecimal.TEN, Currency.USD);
+        invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), callContext).get(0);
         assertListenerStatus();
         invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 6), null, InvoiceItemType.EXTERNAL_CHARGE, BigDecimal.TEN));
 
@@ -711,7 +712,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
     }
 
-    @Test(groups = "slow", description = "Test overdue after refund with no adjustment")
+    @Test(groups = "slow", description = "Test overdue after refund with no adjustment", enabled=false)
     public void testShouldBeInOverdueAfterRefundWithoutAdjustment() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
@@ -747,14 +748,14 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
 
         // Now, refund the second (first non-zero dollar) invoice
-        final Payment payment = paymentApi.getPayment(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).get(1).getPayments().get(0).getPaymentId(), false, callContext);
+        final Payment payment = paymentApi.getPayment(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).get(1).getPayments().get(0).getPaymentId(), false, PLUGIN_PROPERTIES, callContext);
         refundPaymentAndCheckForCompletion(account, payment, NextEvent.BLOCK, NextEvent.INVOICE_ADJUSTMENT);
         // We should now be in OD1
         checkODState("OD1");
         checkChangePlanWithOverdueState(baseEntitlement, true, true);
     }
 
-    @Test(groups = "slow", description = "Test overdue after chargeback")
+    @Test(groups = "slow", description = "Test overdue after chargeback", enabled=false)
     public void testShouldBeInOverdueAfterChargeback() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
@@ -790,14 +791,15 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
 
         // Now, create a chargeback for the second (first non-zero dollar) invoice
-        final InvoicePayment payment = invoicePaymentApi.getInvoicePayments(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).get(1).getPayments().get(0).getPaymentId(), callContext).get(0);
-        createChargeBackAndCheckForCompletion(payment, NextEvent.BLOCK, NextEvent.INVOICE_ADJUSTMENT);
+        final InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayments(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).get(1).getPayments().get(0).getPaymentId(), callContext).get(0);
+        final Payment payment = paymentApi.getPayment(invoicePayment.getPaymentId(), false, ImmutableList.<PluginProperty>of(), callContext);
+        createChargeBackAndCheckForCompletion(account, payment, NextEvent.BLOCK, NextEvent.INVOICE_ADJUSTMENT);
         // We should now be in OD1
         checkODState("OD1");
         checkChangePlanWithOverdueState(baseEntitlement, true, true);
     }
 
-    @Test(groups = "slow", description = "Test overdue clear after external payment")
+    @Test(groups = "slow", description = "Test overdue clear after external payment", enabled=false)
     public void testOverdueStateShouldClearAfterExternalPayment() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
@@ -843,7 +845,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
     }
 
-    @Test(groups = "slow", description = "Test overdue clear after item adjustment")
+    @Test(groups = "slow", description = "Test overdue clear after item adjustment", enabled=false)
     public void testOverdueStateShouldClearAfterCreditOrInvoiceItemAdjustment() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
 
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java
index ec7a677..29efba3 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestBundleTransfer.java
@@ -23,9 +23,6 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
@@ -41,7 +38,9 @@ import org.killbill.billing.entitlement.api.Subscription;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
-import org.killbill.billing.payment.api.PaymentStatus;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
 
@@ -99,6 +98,9 @@ public class TestBundleTransfer extends TestIntegrationBase {
         assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012, 5, 11)) == 0);
         assertTrue(theItem.getEndDate().compareTo(new LocalDate(2013, 5, 11)) == 0);
         assertTrue(theItem.getAmount().compareTo(new BigDecimal("2399.9500")) == 0);
+
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 
     @Test(groups = "slow")
@@ -157,6 +159,9 @@ public class TestBundleTransfer extends TestIntegrationBase {
         assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012, 5, 3)) == 0);
         assertTrue(theItem.getEndDate().compareTo(new LocalDate(2012, 6, 3)) == 0);
         assertTrue(theItem.getAmount().compareTo(new BigDecimal("249.95")) == 0);
+
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 
     @Test(groups = "slow")
@@ -219,6 +224,9 @@ public class TestBundleTransfer extends TestIntegrationBase {
         toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 3), new LocalDate(2012, 5, 15), InvoiceItemType.RECURRING, new BigDecimal("99.98")));
         invoiceChecker.checkInvoice(invoices.get(0).getId(), callContext, toBeChecked);
+
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 
     @Test(groups = "slow", description = "Test entitlement-level transfer with add-on")
@@ -245,7 +253,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
         final DefaultEntitlement aoEntitlement = addAOEntitlementAndCheckForCompletion(bpEntitlement.getBundleId(), aoProductName, ProductCategory.ADD_ON, term,
                                                                                        NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
         final Invoice secondInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("399.95")));
-        paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 4, 1), new BigDecimal("399.95"), PaymentStatus.SUCCESS, secondInvoice.getId(), Currency.USD));
+        paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 4, 1), new BigDecimal("399.95"), TransactionStatus.SUCCESS, secondInvoice.getId(), Currency.USD));
 
         // Move past the phase for simplicity
         busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
@@ -254,7 +262,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
         final Invoice thirdInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext,
                                                                  new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("999.95")),
                                                                  new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
-        paymentChecker.checkPayment(account.getId(), 2, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 1), new BigDecimal("1249.90"), PaymentStatus.SUCCESS, thirdInvoice.getId(), Currency.USD));
+        paymentChecker.checkPayment(account.getId(), 2, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 5, 1), new BigDecimal("1249.90"), TransactionStatus.SUCCESS, thirdInvoice.getId(), Currency.USD));
 
         // Align the transfer on the BCD to make pro-rations easier
         busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
@@ -264,7 +272,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
         final Invoice fourthInvoice = invoiceChecker.checkInvoice(account.getId(), 4, callContext,
                                                                   new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.RECURRING, new BigDecimal("999.95")),
                                                                   new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
-        paymentChecker.checkPayment(account.getId(), 3, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 6, 1), new BigDecimal("1249.90"), PaymentStatus.SUCCESS, fourthInvoice.getId(), Currency.USD));
+        paymentChecker.checkPayment(account.getId(), 3, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 6, 1), new BigDecimal("1249.90"), TransactionStatus.SUCCESS, fourthInvoice.getId(), Currency.USD));
 
         final DateTime now = clock.getUTCNow();
         final LocalDate transferDay = now.toLocalDate();
@@ -285,7 +293,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
         final Invoice firstInvoiceNewAccount = invoiceChecker.checkInvoice(newAccount.getId(), 1, callContext,
                                                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.RECURRING, new BigDecimal("999.95")),
                                                                            new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
-        paymentChecker.checkPayment(newAccount.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 6, 1), new BigDecimal("1249.90"), PaymentStatus.SUCCESS, firstInvoiceNewAccount.getId(), Currency.USD));
+        paymentChecker.checkPayment(newAccount.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 6, 1), new BigDecimal("1249.90"), TransactionStatus.SUCCESS, firstInvoiceNewAccount.getId(), Currency.USD));
 
         // Check entitlements and subscriptions on the old account
         final List<Entitlement> oldEntitlements = entitlementApi.getAllEntitlementsForBundle(bpEntitlement.getBundleId(), callContext);
@@ -308,5 +316,8 @@ public class TestBundleTransfer extends TestIntegrationBase {
             Assert.assertEquals(subscription.getBillingStartDate(), transferDay);
             Assert.assertNull(subscription.getBillingEndDate());
         }
+
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index d3e7445..885a8e0 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -23,9 +23,6 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.joda.time.Interval;
 import org.joda.time.LocalDate;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.api.TestApiListener.NextEvent;
@@ -42,8 +39,10 @@ import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
 import org.killbill.billing.entitlement.api.SubscriptionBundle;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItemType;
-import org.killbill.billing.payment.api.PaymentStatus;
+import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotEquals;
@@ -74,8 +73,9 @@ public class TestIntegration extends TestIntegrationBase {
         // ADD ADD_ON ON THE SAME DAY
         //
         addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT);
+
         Invoice invoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("399.95")));
-        paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 4, 1), new BigDecimal("399.95"), PaymentStatus.SUCCESS, invoice.getId(), Currency.USD));
+        paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 4, 1), new BigDecimal("399.95"), TransactionStatus.SUCCESS, invoice.getId(), Currency.USD));
 
         //
         // CANCEL BP ON THE SAME DAY (we should have two cancellations, BP and AO)
@@ -88,6 +88,7 @@ public class TestIntegration extends TestIntegrationBase {
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-399.95")),
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 4, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("399.95")));
 
+        checkNoMoreInvoiceToGenerate(account);
     }
 
     @Test(groups = "slow")
@@ -169,6 +170,8 @@ public class TestIntegration extends TestIntegrationBase {
         addDaysAndCheckForCompletion(31, NextEvent.CANCEL);
         invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 7, 31), callContext);
 
+        checkNoMoreInvoiceToGenerate(account);
+
         log.info("TEST PASSED !");
     }
 
@@ -250,6 +253,9 @@ public class TestIntegration extends TestIntegrationBase {
         // MOVE AFTER CANCEL DATE AND EXPECT EVENT : NextEvent.CANCEL
         addDaysAndCheckForCompletion(31, NextEvent.CANCEL);
         invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 8, 2), callContext);
+
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 
     @Test(groups = "slow")
@@ -338,6 +344,8 @@ public class TestIntegration extends TestIntegrationBase {
         addDaysAndCheckForCompletion(31, NextEvent.CANCEL);
         invoiceChecker.checkChargedThroughDate(subscription.getId(), new LocalDate(2012, 8, 3), callContext);
 
+        checkNoMoreInvoiceToGenerate(account);
+
         log.info("TEST PASSED !");
 
     }
@@ -439,6 +447,9 @@ public class TestIntegration extends TestIntegrationBase {
         log.info("Moving clock from" + clock.getUTCNow() + " to " + clock.getUTCNow().plusDays(3));
         clock.addDays(3);
         assertListenerStatus();
+
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 
     @Test(groups = "slow")
@@ -473,6 +484,9 @@ public class TestIntegration extends TestIntegrationBase {
 
         assertEquals(refreshedBseEntitlement.getState(), EntitlementState.CANCELLED);
         assertEquals(newBaseEntitlement.getState(), EntitlementState.ACTIVE);
+
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 
     @Test(groups = "slow")
@@ -544,6 +558,8 @@ public class TestIntegration extends TestIntegrationBase {
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 5), new LocalDate(2012, 5, 2), InvoiceItemType.RECURRING, new BigDecimal("224.96")),
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 5), new LocalDate(2012, 4, 5), InvoiceItemType.CBA_ADJ, new BigDecimal("-224.96")));
 
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 
     @Test(groups = "slow")
@@ -606,5 +622,8 @@ public class TestIntegration extends TestIntegrationBase {
         assertEquals(invoices.size(), 14);
 
         assertListenerStatus();
+
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index 3a14ae9..ada01fe 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,7 +21,7 @@ package org.killbill.billing.beatrix.integration;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -29,15 +31,8 @@ import javax.inject.Named;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.killbill.billing.usage.api.UsageUserApi;
-import org.skife.jdbi.v2.IDBI;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.account.api.AccountInternalApi;
@@ -46,15 +41,11 @@ import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.beatrix.BeatrixTestSuiteWithEmbeddedDB;
-import org.killbill.billing.beatrix.glue.BeatrixModule;
-import org.killbill.billing.beatrix.lifecycle.Lifecycle;
-import org.killbill.billing.beatrix.osgi.SetupBundleWithAssertion;
 import org.killbill.billing.beatrix.util.AccountChecker;
 import org.killbill.billing.beatrix.util.InvoiceChecker;
 import org.killbill.billing.beatrix.util.PaymentChecker;
 import org.killbill.billing.beatrix.util.RefundChecker;
 import org.killbill.billing.beatrix.util.SubscriptionChecker;
-import org.killbill.bus.api.PersistentBus;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
@@ -73,33 +64,52 @@ import org.killbill.billing.invoice.api.InvoicePaymentApi;
 import org.killbill.billing.invoice.api.InvoiceService;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
 import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.lifecycle.api.BusService;
+import org.killbill.billing.lifecycle.api.Lifecycle;
+import org.killbill.billing.lifecycle.glue.BusModule;
 import org.killbill.billing.mock.MockAccountBuilder;
+import org.killbill.billing.osgi.config.OSGIConfig;
 import org.killbill.billing.overdue.OverdueUserApi;
+import org.killbill.billing.overdue.listener.OverdueListener;
 import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.PaymentMethodKVInfo;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
+import org.killbill.billing.payment.api.PaymentOptions;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TestPaymentMethodPluginBase;
+import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseService;
 import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimelineApi;
 import org.killbill.billing.subscription.api.transfer.SubscriptionBaseTransferApi;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.killbill.billing.usage.api.UsageUserApi;
 import org.killbill.billing.util.api.RecordIdApi;
+import org.killbill.billing.util.api.TagApiException;
+import org.killbill.billing.util.api.TagDefinitionApiException;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
-import org.killbill.billing.util.config.OSGIConfig;
-import org.killbill.billing.util.svcsapi.bus.BusService;
+import org.killbill.billing.util.tag.ControlTagType;
+import org.killbill.billing.util.tag.Tag;
+import org.killbill.bus.api.PersistentBus;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
@@ -111,6 +121,32 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     protected static final Logger log = LoggerFactory.getLogger(TestIntegrationBase.class);
     protected static long AT_LEAST_ONE_MONTH_MS = 32L * 24L * 3600L * 1000L;
 
+    protected final static PaymentOptions PAYMENT_OPTIONS = new PaymentOptions() {
+        @Override
+        public boolean isExternalPayment() {
+            return false;
+        }
+
+        @Override
+        public String getPaymentControlPluginName() {
+            return InvoicePaymentControlPluginApi.PLUGIN_NAME;
+        }
+    };
+
+    protected final static PaymentOptions EXTERNAL_PAYMENT_OPTIONS = new PaymentOptions() {
+        @Override
+        public boolean isExternalPayment() {
+            return true;
+        }
+
+        @Override
+        public String getPaymentControlPluginName() {
+            return InvoicePaymentControlPluginApi.PLUGIN_NAME;
+        }
+    };
+
+    protected final Iterable<PluginProperty> PLUGIN_PROPERTIES = ImmutableList.<PluginProperty>of();
+
     @Inject
     protected Lifecycle lifecycle;
 
@@ -161,6 +197,9 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     protected OverdueWrapperFactory overdueWrapperFactory;
 
     @Inject
+    protected OverdueListener overdueListener;
+
+    @Inject
     protected AccountUserApi accountUserApi;
 
     @Inject
@@ -176,7 +215,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     protected AccountChecker accountChecker;
 
     @Inject
-    @Named(BeatrixModule.EXTERNAL_BUS)
+    @Named(BusModule.EXTERNAL_BUS_NAMED)
     protected PersistentBus externalBus;
 
     @Inject
@@ -214,9 +253,6 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     public void beforeClass() throws Exception {
         final Injector g = Guice.createInjector(Stage.PRODUCTION, new BeatrixIntegrationModule(configSource));
         g.injectMembers(this);
-
-        SetupBundleWithAssertion setupTest = new SetupBundleWithAssertion("whatever", osgiConfig, "whatever");
-        setupTest.cleanBundleInstallDir();
     }
 
     @BeforeMethod(groups = "slow")
@@ -258,6 +294,15 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         log.debug("DONE WITH TEST");
     }
 
+    protected void checkNoMoreInvoiceToGenerate(final Account account) {
+        try {
+            invoiceUserApi.triggerInvoiceGeneration(account.getId(), clock.getUTCToday(), false, callContext);
+            fail("Should not have generated an extra invoice");
+        } catch (final InvoiceApiException e) {
+            assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
+        }
+    }
+
     protected void verifyTestResult(final UUID accountId, final UUID subscriptionId,
                                     final DateTime startDate, @Nullable final DateTime endDate,
                                     final BigDecimal amount, final DateTime chargeThroughDate,
@@ -292,16 +337,16 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
 
         final PaymentMethodPlugin info = createPaymentMethodPlugin();
 
-        paymentApi.addPaymentMethod(paymentPluginName, account, true, info, callContext);
+        paymentApi.addPaymentMethod(account, UUID.randomUUID().toString(), paymentPluginName, true, info, PLUGIN_PROPERTIES, callContext);
         return accountUserApi.getAccountById(account.getId(), callContext);
     }
 
     private class TestPaymentMethodPlugin extends TestPaymentMethodPluginBase {
 
         @Override
-        public List<PaymentMethodKVInfo> getProperties() {
-            PaymentMethodKVInfo prop = new PaymentMethodKVInfo("whatever", "cool", Boolean.TRUE);
-            List<PaymentMethodKVInfo> res = new ArrayList<PaymentMethodKVInfo>();
+        public List<PluginProperty> getProperties() {
+            final PluginProperty prop = new PluginProperty("whatever", "cool", Boolean.TRUE);
+            final List<PluginProperty> res = new ArrayList<PluginProperty>();
             res.add(prop);
             return res;
         }
@@ -362,84 +407,130 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         }, events);
     }
 
-    protected void createPaymentAndCheckForCompletion(final Account account, final Invoice invoice, final NextEvent... events) {
-        doCallAndCheckForCompletion(new Function<Void, Void>() {
+    protected Payment createPaymentAndCheckForCompletion(final Account account, final Invoice invoice, final BigDecimal amount, final Currency currency,  final NextEvent... events) {
+        return doCallAndCheckForCompletion(new Function<Void, Payment>() {
             @Override
-            public Void apply(@Nullable final Void input) {
+            public Payment apply(@Nullable final Void input) {
                 try {
-                    paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
-                } catch (PaymentApiException e) {
+
+                    final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+                    final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
+                    properties.add(prop1);
+                    return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, amount, currency, UUID.randomUUID().toString(),
+                                                                       UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
+                } catch (final PaymentApiException e) {
                     fail(e.toString());
+                    return null;
                 }
-                return null;
             }
         }, events);
     }
 
-    protected void createExternalPaymentAndCheckForCompletion(final Account account, final Invoice invoice, final NextEvent... events) {
-        doCallAndCheckForCompletion(new Function<Void, Void>() {
+    protected Payment createPaymentAndCheckForCompletion(final Account account, final Invoice invoice, final NextEvent... events) {
+        return doCallAndCheckForCompletion(new Function<Void, Payment>() {
             @Override
-            public Void apply(@Nullable final Void input) {
+            public Payment apply(@Nullable final Void input) {
                 try {
-                    paymentApi.createExternalPayment(account, invoice.getId(), invoice.getBalance(), callContext);
-                } catch (PaymentApiException e) {
+                    final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+                    final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
+                    properties.add(prop1);
+
+                    return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, invoice.getBalance(), invoice.getCurrency(),  UUID.randomUUID().toString(),
+                                                                       UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
+                } catch (final PaymentApiException e) {
+                    fail(e.toString());
+                    return null;
+                }
+            }
+        }, events);
+    }
+
+    protected Payment createExternalPaymentAndCheckForCompletion(final Account account, final Invoice invoice, final NextEvent... events) {
+        return doCallAndCheckForCompletion(new Function<Void, Payment>() {
+            @Override
+            public Payment apply(@Nullable final Void input) {
+                try {
+
+                    final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+                    final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
+                    properties.add(prop1);
+
+                    return paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, invoice.getBalance(), invoice.getCurrency(), UUID.randomUUID().toString(),
+                                                                       UUID.randomUUID().toString(), properties, EXTERNAL_PAYMENT_OPTIONS, callContext);
+                } catch (final PaymentApiException e) {
                     fail(e.toString());
+                    return null;
                 }
-                return null;
             }
         }, events);
     }
 
-    protected void refundPaymentAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
-        doCallAndCheckForCompletion(new Function<Void, Void>() {
+    protected Payment refundPaymentAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
+        return doCallAndCheckForCompletion(new Function<Void, Payment>() {
             @Override
-            public Void apply(@Nullable final Void input) {
+            public Payment apply(@Nullable final Void input) {
                 try {
-                    paymentApi.createRefund(account, payment.getId(), payment.getPaidAmount(), callContext);
-                } catch (PaymentApiException e) {
+                    return paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
+                                                                     PLUGIN_PROPERTIES, PAYMENT_OPTIONS, callContext);
+                } catch (final PaymentApiException e) {
                     fail(e.toString());
+                    return null;
                 }
-                return null;
             }
         }, events);
     }
 
-    protected void refundPaymentWithAdjustmenttAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
-        doCallAndCheckForCompletion(new Function<Void, Void>() {
+    protected Payment refundPaymentWithAdjustmentAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
+        return doCallAndCheckForCompletion(new Function<Void, Payment>() {
             @Override
-            public Void apply(@Nullable final Void input) {
+            public Payment apply(@Nullable final Void input) {
+
+                final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+                final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_WITH_ADJUSTMENTS, "true", false);
+                properties.add(prop1);
                 try {
-                    paymentApi.createRefundWithAdjustment(account, payment.getId(), payment.getPaidAmount(), callContext);
-                } catch (PaymentApiException e) {
+                    return paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
+                                                                     properties, PAYMENT_OPTIONS, callContext);
+                } catch (final PaymentApiException e) {
                     fail(e.toString());
+                    return null;
                 }
-                return null;
             }
         }, events);
     }
 
-    protected void refundPaymentWithInvoiceItemAdjAndCheckForCompletion(final Account account, final Payment payment, final Set<UUID> invoiceItems, final NextEvent... events) {
-        doCallAndCheckForCompletion(new Function<Void, Void>() {
+    protected Payment refundPaymentWithInvoiceItemAdjAndCheckForCompletion(final Account account, final Payment payment, final Map<UUID, BigDecimal> iias, final NextEvent... events) {
+        return doCallAndCheckForCompletion(new Function<Void, Payment>() {
             @Override
-            public Void apply(@Nullable final Void input) {
+            public Payment apply(@Nullable final Void input) {
+
+                final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+                final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_WITH_ADJUSTMENTS, "true", false);
+                properties.add(prop1);
+                final PluginProperty prop2 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY, iias, false);
+                properties.add(prop2);
+
                 try {
-                    paymentApi.createRefundWithItemsAdjustments(account, payment.getId(), invoiceItems, callContext);
-                } catch (PaymentApiException e) {
+                    return paymentApi.createRefundWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
+                                                                     properties, PAYMENT_OPTIONS, callContext);
+                } catch (final PaymentApiException e) {
                     fail(e.toString());
+                    return null;
                 }
-                return null;
             }
         }, events);
     }
 
-    protected void createChargeBackAndCheckForCompletion(final InvoicePayment payment, final NextEvent... events) {
+    protected void createChargeBackAndCheckForCompletion(final Account account, final Payment payment, final NextEvent... events) {
         doCallAndCheckForCompletion(new Function<Void, Void>() {
             @Override
             public Void apply(@Nullable final Void input) {
                 try {
-                    invoicePaymentApi.createChargeback(payment.getId(), payment.getAmount(), callContext);
-                } catch (InvoiceApiException e) {
+                    paymentApi.createChargebackWithPaymentControl(account, payment.getId(), payment.getPurchasedAmount(), payment.getCurrency(), UUID.randomUUID().toString(),
+                                                                  PAYMENT_OPTIONS, callContext);
+                } catch (PaymentApiException e) {
                     fail(e.toString());
+                    return null;
                 }
                 return null;
             }
@@ -465,7 +556,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                     final Entitlement entitlement = entitlementApi.createBaseEntitlement(accountId, spec, bundleExternalKey, effectiveDate, callContext);
                     assertNotNull(entitlement);
                     return entitlement;
-                } catch (EntitlementApiException e) {
+                } catch (final EntitlementApiException e) {
                     fail();
                     return null;
                 }
@@ -491,7 +582,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                     final Entitlement entitlement = entitlementApi.addEntitlement(bundleId, spec, effectiveDate, callContext);
                     assertNotNull(entitlement);
                     return entitlement;
-                } catch (EntitlementApiException e) {
+                } catch (final EntitlementApiException e) {
                     fail(e.getMessage());
                     return null;
                 }
@@ -502,6 +593,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
     protected DefaultEntitlement changeEntitlementAndCheckForCompletion(final Entitlement entitlement,
                                                                         final String productName,
                                                                         final BillingPeriod billingPeriod,
+                                                                        final String priceList,
                                                                         final BillingActionPolicy billingPolicy,
                                                                         final NextEvent... events) {
         return (DefaultEntitlement) doCallAndCheckForCompletion(new Function<Void, Entitlement>() {
@@ -511,12 +603,12 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                     // Need to fetch again to get latest CTD updated from the system
                     Entitlement refreshedEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
                     if (billingPolicy == null) {
-                        refreshedEntitlement = refreshedEntitlement.changePlan(productName, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, callContext);
+                        refreshedEntitlement = refreshedEntitlement.changePlan(productName, billingPeriod, priceList, callContext);
                     } else {
-                        refreshedEntitlement = refreshedEntitlement.changePlanOverrideBillingPolicy(productName, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow().toLocalDate(), billingPolicy, callContext);
+                        refreshedEntitlement = refreshedEntitlement.changePlanOverrideBillingPolicy(productName, billingPeriod, priceList, clock.getUTCNow().toLocalDate(), billingPolicy, callContext);
                     }
                     return refreshedEntitlement;
-                } catch (EntitlementApiException e) {
+                } catch (final EntitlementApiException e) {
                     fail(e.getMessage());
                     return null;
                 }
@@ -524,6 +616,14 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         }, events);
     }
 
+    protected DefaultEntitlement changeEntitlementAndCheckForCompletion(final Entitlement entitlement,
+                                                                        final String productName,
+                                                                        final BillingPeriod billingPeriod,
+                                                                        final BillingActionPolicy billingPolicy,
+                                                                        final NextEvent... events) {
+        return changeEntitlementAndCheckForCompletion(entitlement, productName, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, billingPolicy, events);
+    }
+
     protected DefaultEntitlement cancelEntitlementAndCheckForCompletion(final Entitlement entitlement,
                                                                         final DateTime requestedDate,
                                                                         final NextEvent... events) {
@@ -535,7 +635,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                     Entitlement refreshedEntitlement = entitlementApi.getEntitlementForId(entitlement.getId(), callContext);
                     refreshedEntitlement = refreshedEntitlement.cancelEntitlementWithDate(requestedDate.toLocalDate(), false, callContext);
                     return refreshedEntitlement;
-                } catch (EntitlementApiException e) {
+                } catch (final EntitlementApiException e) {
                     fail(e.getMessage());
                     return null;
                 }
@@ -550,7 +650,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                 try {
                     invoiceUserApi.insertCreditForInvoice(account.getId(), invoice.getId(), invoice.getBalance(), invoice.getInvoiceDate(),
                                                           account.getCurrency(), callContext);
-                } catch (InvoiceApiException e) {
+                } catch (final InvoiceApiException e) {
                     fail(e.toString());
                 }
                 return null;
@@ -565,7 +665,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                 try {
                     invoiceUserApi.insertInvoiceItemAdjustment(account.getId(), invoice.getId(), invoice.getInvoiceItems().get(itemNb - 1).getId(),
                                                                invoice.getInvoiceDate(), callContext);
-                } catch (InvoiceApiException e) {
+                } catch (final InvoiceApiException e) {
                     fail(e.toString());
                 }
                 return null;
@@ -573,8 +673,25 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
         }, events);
     }
 
-    private <T> T doCallAndCheckForCompletion(Function<Void, T> f, final NextEvent... events) {
-        Joiner joiner = Joiner.on(", ");
+    protected void add_AUTO_PAY_OFF_Tag(final UUID id, final ObjectType type) throws TagDefinitionApiException, TagApiException {
+        busHandler.pushExpectedEvent(NextEvent.TAG);
+        tagUserApi.addTag(id, type, ControlTagType.AUTO_PAY_OFF.getId(), callContext);
+        assertListenerStatus();
+
+        final List<Tag> tags = tagUserApi.getTagsForObject(id, type, false, callContext);
+        assertEquals(tags.size(), 1);
+    }
+
+
+    protected void remove_AUTO_PAY_OFF_Tag(final UUID id, final ObjectType type, final NextEvent...additionalEvents) throws TagDefinitionApiException, TagApiException {
+        busHandler.pushExpectedEvent(NextEvent.TAG);
+        busHandler.pushExpectedEvents(additionalEvents);
+        tagUserApi.removeTag(id, type, ControlTagType.AUTO_PAY_OFF.getId(), callContext);
+        assertListenerStatus();
+    }
+
+    private <T> T doCallAndCheckForCompletion(final Function<Void, T> f, final NextEvent... events) {
+        final Joiner joiner = Joiner.on(", ");
         log.debug("            ************    STARTING BUS HANDLER CHECK : {} ********************", joiner.join(events));
 
         busHandler.pushExpectedEvents(events);
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
index bc1f0f4..d721b15 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoiceWithRepairLogic.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -24,11 +26,6 @@ import java.util.UUID;
 
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.Test;
-
-import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
@@ -41,10 +38,12 @@ import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
 import org.killbill.billing.invoice.api.Invoice;
-import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.payment.api.Payment;
-import org.killbill.billing.payment.api.PaymentStatus;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
 
@@ -151,6 +150,8 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 2), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("9.63")),
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 2), new LocalDate(2012, 5, 2), InvoiceItemType.CBA_ADJ, new BigDecimal("-9.63")));
         invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
+
+        checkNoMoreInvoiceToGenerate(account);
     }
 
     @Test(groups = "slow")
@@ -374,6 +375,8 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 1), new LocalDate(2012, 8, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 8), new LocalDate(2012, 7, 8), InvoiceItemType.CBA_ADJ, new BigDecimal("-29.95")));
         invoiceChecker.checkInvoice(invoices.get(6).getId(), callContext, toBeChecked);
+
+        checkNoMoreInvoiceToGenerate(account);
     }
 
     @Test(groups = "slow")
@@ -482,6 +485,8 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 1), new LocalDate(2012, 8, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 1), new LocalDate(2012, 7, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-249.95")));
         invoiceChecker.checkInvoice(invoices.get(4).getId(), callContext, toBeChecked);
+
+        checkNoMoreInvoiceToGenerate(account);
     }
 
     @Test(groups = "slow")
@@ -500,7 +505,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
         //
         // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
         //
-        DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
+        final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
         assertNotNull(bpEntitlement);
         assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
 
@@ -535,22 +540,14 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
         // ITEM ADJUSTMENT PRIOR TO DOING THE REPAIR
         //
         final Invoice invoice1 = invoices.get(1);
-        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), callContext);
-        final ExpectedPaymentCheck expectedPaymentCheck = new ExpectedPaymentCheck(clock.getUTCNow().toLocalDate(), new BigDecimal("2399.95"), PaymentStatus.SUCCESS, invoice1.getId(), Currency.USD);
+        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+        final ExpectedPaymentCheck expectedPaymentCheck = new ExpectedPaymentCheck(clock.getUTCNow().toLocalDate(), new BigDecimal("2399.95"), TransactionStatus.SUCCESS, invoice1.getId(), Currency.USD);
         final Payment payment1 = payments.get(0);
 
         final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
         iias.put(invoice1.getInvoiceItems().get(0).getId(), new BigDecimal("197.26"));
-        busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
-        paymentApi.createRefundWithItemsAdjustments(account, payment1.getId(), iias, callContext);
-        assertListenerStatus();
-
-        try {
-            invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(clock.getUTCToday()), false, callContext);
-            Assert.fail("Should not gnenerated an new invoice");
-        } catch (InvoiceApiException e) {
-            Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
-        }
+        refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, iias, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+        checkNoMoreInvoiceToGenerate(account);
     }
 
     //
@@ -576,7 +573,7 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
         //
         // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
         //
-        DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
+        final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
         assertNotNull(bpEntitlement);
         assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
 
@@ -611,22 +608,13 @@ public class TestIntegrationInvoiceWithRepairLogic extends TestIntegrationBase {
         // ITEM ADJUSTMENT PRIOR TO DOING THE REPAIR
         //
         final Invoice invoice1 = invoices.get(1);
-        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), callContext);
-        final ExpectedPaymentCheck expectedPaymentCheck = new ExpectedPaymentCheck(clock.getUTCNow().toLocalDate(), new BigDecimal("2399.95"), PaymentStatus.SUCCESS, invoice1.getId(), Currency.USD);
+        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+        final ExpectedPaymentCheck expectedPaymentCheck = new ExpectedPaymentCheck(clock.getUTCNow().toLocalDate(), new BigDecimal("2399.95"), TransactionStatus.SUCCESS, invoice1.getId(), Currency.USD);
         final Payment payment1 = payments.get(0);
 
         final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
         iias.put(invoice1.getInvoiceItems().get(0).getId(), new BigDecimal("100.00"));
-        busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
-        paymentApi.createRefundWithItemsAdjustments(account, payment1.getId(), iias, callContext);
-        assertListenerStatus();
-
-        try {
-            invoiceUserApi.triggerInvoiceGeneration(account.getId(), new LocalDate(clock.getUTCToday()), false, callContext);
-            Assert.fail("Should not gnenerated an new invoice");
-        } catch (InvoiceApiException e) {
-            Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
-        }
+        refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment1, iias, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+        checkNoMoreInvoiceToGenerate(account);
     }
-
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
index de0c16b..007b7bc 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithAutoPayOff.java
@@ -18,8 +18,6 @@ package org.killbill.billing.beatrix.integration;
 
 import java.math.BigDecimal;
 import java.util.Collection;
-import java.util.List;
-import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.testng.annotations.BeforeMethod;
@@ -35,12 +33,7 @@ import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
-import org.killbill.billing.util.api.TagApiException;
-import org.killbill.billing.util.api.TagDefinitionApiException;
-import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.config.PaymentConfig;
-import org.killbill.billing.util.tag.ControlTagType;
-import org.killbill.billing.util.tag.Tag;
 
 import com.google.inject.Inject;
 
@@ -51,12 +44,6 @@ import static org.testng.Assert.assertTrue;
 public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
 
     @Inject
-    private InvoiceUserApi invoiceApi;
-
-    @Inject
-    private TagUserApi tagApi;
-
-    @Inject
     private PaymentConfig paymentConfig;
 
     private Account account;
@@ -84,7 +71,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
         final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
         assertNotNull(bpEntitlement);
 
-        Collection<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 1);
 
         busHandler.pushExpectedEvents(NextEvent.PHASE);
@@ -93,7 +80,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
 
         assertListenerStatus();
 
-        invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 2);
         for (Invoice cur : invoices) {
             if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -102,12 +89,10 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
             assertEquals(cur.getBalance(), cur.getChargedAmount());
         }
 
-        busHandler.pushExpectedEvents(NextEvent.PAYMENT);
-        remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
-        assertListenerStatus();
+        remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT);
         addDelayBceauseOfLackOfCorrectSynchro();
 
-        invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 2);
         for (Invoice cur : invoices) {
             if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -127,7 +112,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
         final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
         assertNotNull(bpEntitlement);
 
-        Collection<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 1);
 
         busHandler.pushExpectedEvents(NextEvent.PHASE);
@@ -136,7 +121,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
 
         assertListenerStatus();
 
-        invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 2);
         for (Invoice cur : invoices) {
             if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -146,12 +131,10 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
         }
 
         paymentPlugin.makeNextPaymentFailWithError();
-        busHandler.pushExpectedEvents(NextEvent.PAYMENT_ERROR);
-        remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
-        assertListenerStatus();
+        remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT_ERROR);
         addDelayBceauseOfLackOfCorrectSynchro();
 
-        invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 2);
         for (Invoice cur : invoices) {
             if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -168,7 +151,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
         clock.addDays(nbDaysBeforeRetry + 1);
         assertListenerStatus();
 
-        invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         for (Invoice cur : invoices) {
             if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
                 continue;
@@ -188,7 +171,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
         final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
         assertNotNull(bpEntitlement);
 
-        Collection<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        Collection<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 1);
 
         // CREATE FIRST NON NULL INVOICE + FIRST PAYMENT/ATTEMPT -> AUTO_PAY_OFF
@@ -197,7 +180,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
         clock.addDays(31); // After trial
         assertListenerStatus();
 
-        invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 2);
         for (Invoice cur : invoices) {
             if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -208,12 +191,10 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
 
         // NOW SET PLUGIN TO THROW FAILURES
         paymentPlugin.makeNextPaymentFailWithError();
-        busHandler.pushExpectedEvents(NextEvent.PAYMENT_ERROR);
-        remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
-        assertListenerStatus();
+        remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT_ERROR);
         addDelayBceauseOfLackOfCorrectSynchro();
 
-        invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 2);
         for (Invoice cur : invoices) {
             if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -231,7 +212,7 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
         clock.addDays(nbDaysBeforeRetry + 1);
         assertListenerStatus();
 
-        invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         assertEquals(invoices.size(), 2);
         for (Invoice cur : invoices) {
             if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
@@ -244,16 +225,10 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
 
         // REMOVE AUTO_PAY_OFF -> WILL SCHEDULE A PAYMENT_RETRY
         paymentPlugin.clear();
-        remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
-        assertListenerStatus();
+        remove_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT, NextEvent.PAYMENT);
         addDelayBceauseOfLackOfCorrectSynchro();
 
-        //
-        busHandler.pushExpectedEvents(NextEvent.PAYMENT);
-        clock.addDays(nbDaysBeforeRetry + 1);
-        assertListenerStatus();
-
-        invoices = invoiceApi.getInvoicesByAccount(account.getId(), callContext);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
         for (Invoice cur : invoices) {
             if (cur.getChargedAmount().compareTo(BigDecimal.ZERO) == 0) {
                 continue;
@@ -265,20 +240,6 @@ public class TestIntegrationWithAutoPayOff extends TestIntegrationBase {
 
     }
 
-    private void add_AUTO_PAY_OFF_Tag(final UUID id, final ObjectType type) throws TagDefinitionApiException, TagApiException {
-        busHandler.pushExpectedEvent(NextEvent.TAG);
-        tagApi.addTag(id, type, ControlTagType.AUTO_PAY_OFF.getId(), callContext);
-        assertListenerStatus();
-
-        final List<Tag> tags = tagApi.getTagsForObject(id, type, false, callContext);
-        assertEquals(tags.size(), 1);
-    }
-
-    private void remove_AUTO_PAY_OFF_Tag(final UUID id, final ObjectType type) throws TagDefinitionApiException, TagApiException {
-        busHandler.pushExpectedEvent(NextEvent.TAG);
-        tagApi.removeTag(id, type, ControlTagType.AUTO_PAY_OFF.getId(), callContext);
-        assertListenerStatus();
-    }
 
     private void addDelayBceauseOfLackOfCorrectSynchro() {
         // TODO When removing the tag, the payment system will schedule retries for payments that are in non terminal state
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
index 74bfe42..5334bfb 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationWithDifferentBillingPeriods.java
@@ -95,6 +95,8 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2327.62")),
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 12), new LocalDate(2012, 5, 12), InvoiceItemType.CBA_ADJ, new BigDecimal("-161.26")));
         invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
+
+        checkNoMoreInvoiceToGenerate(account);
     }
 
     @Test(groups = "slow")
@@ -164,6 +166,8 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 8, 1), new LocalDate(2012, 11, 1), InvoiceItemType.RECURRING, new BigDecimal("69.95")));
         invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
 
+        checkNoMoreInvoiceToGenerate(account);
+
     }
 
     @Test(groups = "slow")
@@ -234,6 +238,8 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
         toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
                 new ExpectedInvoiceItemCheck(new LocalDate(2013, 6, 1), new LocalDate(2014, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
         invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
+
+        checkNoMoreInvoiceToGenerate(account);
     }
 
     @Test(groups = "slow")
@@ -311,5 +317,7 @@ public class TestIntegrationWithDifferentBillingPeriods extends TestIntegrationB
         toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
                 new ExpectedInvoiceItemCheck(new LocalDate(2013, 6, 1), new LocalDate(2014, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
         invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
+
+        checkNoMoreInvoiceToGenerate(account);
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPayment.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPayment.java
new file mode 100644
index 0000000..dc3cd13
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPayment.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.beatrix.integration;
+
+import java.math.BigDecimal;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
+import org.killbill.billing.payment.api.Payment;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertTrue;
+
+public class TestPayment extends TestIntegrationBase {
+
+    @Test(groups = "slow")
+    public void testPartialPayments() throws Exception {
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        add_AUTO_PAY_OFF_Tag(account.getId(), ObjectType.ACCOUNT);
+
+        clock.setDay(new LocalDate(2012, 4, 1));
+
+        busHandler.pushExpectedEvents(NextEvent.INVOICE_ADJUSTMENT);
+        final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, account.getId(), null, "Initial external charge", clock.getUTCToday(), BigDecimal.TEN, Currency.USD);
+        final InvoiceItem item1 = invoiceUserApi.insertExternalCharges(account.getId(), clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), callContext).get(0);
+        assertListenerStatus();
+
+        final Invoice invoice = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
+        final Payment payment1 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("4.00"), account.getCurrency(),  NextEvent.PAYMENT);
+
+        Invoice invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
+        assertTrue(invoice1.getBalance().compareTo(new BigDecimal("6.00")) == 0);
+        assertTrue(invoice1.getPaidAmount().compareTo(new BigDecimal("4.00")) == 0);
+        assertTrue(invoice1.getChargedAmount().compareTo(BigDecimal.TEN) == 0);
+
+        BigDecimal accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+        assertTrue(accountBalance.compareTo(new BigDecimal("6.00")) == 0);
+
+        final Payment payment2 = createPaymentAndCheckForCompletion(account, invoice, new BigDecimal("6.00"), account.getCurrency(),  NextEvent.PAYMENT);
+
+        invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
+        assertTrue(invoice1.getBalance().compareTo(BigDecimal.ZERO) == 0);
+        assertTrue(invoice1.getPaidAmount().compareTo(BigDecimal.TEN) == 0);
+        assertTrue(invoice1.getChargedAmount().compareTo(BigDecimal.TEN) == 0);
+
+        accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+        assertTrue(accountBalance.compareTo(BigDecimal.ZERO) == 0);
+
+/*
+        This does not work since item is paid across multiple payments and so the mount is bigger than the payment.
+
+        // Now, issue refund with item adjustment on first invoice/item
+        paymentApi.createRefundWithItemsAdjustments(account, payment1.getId(), Sets.<UUID>newHashSet(item1.getId()), callContext);
+
+        invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
+        assertTrue(invoice1.getBalance().compareTo(BigDecimal.ZERO) == 0);
+
+        accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+        assertTrue(accountBalance.compareTo(BigDecimal.ZERO) == 0);
+
+*/
+
+        refundPaymentAndCheckForCompletion(account, payment1, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
+
+        invoice1 = invoiceUserApi.getInvoice(item1.getInvoiceId(), callContext);
+        assertTrue(invoice1.getBalance().compareTo(new BigDecimal("4.00")) == 0);
+
+        accountBalance = invoiceUserApi.getAccountBalance(account.getId(), callContext);
+        assertTrue(accountBalance.compareTo(new BigDecimal("4.00")) == 0);
+
+    }
+}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
index d3c8485..8bae9e0 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPaymentRefund.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,7 +19,9 @@
 package org.killbill.billing.beatrix.integration;
 
 import java.math.BigDecimal;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
@@ -25,9 +29,6 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
@@ -41,7 +42,9 @@ import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.payment.api.Payment;
-import org.killbill.billing.payment.api.PaymentStatus;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
@@ -70,29 +73,33 @@ public class TestPaymentRefund extends TestIntegrationBase {
     @Test(groups = "slow")
     public void testRefundWithNoAdjustments() throws Exception {
         // Although we don't adjust the invoice, the invoicing system sends an event because invoice balance changes and overdue system-- in particular-- needs to know about it.
-        refundPaymentAndCheckForCompletion(account, payment, NextEvent.INVOICE_ADJUSTMENT);
+        refundPaymentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
         refundChecker.checkRefund(payment.getId(), callContext, new ExpectedRefundCheck(payment.getId(), false, new BigDecimal("233.82"), Currency.USD, initialCreationDate.toLocalDate()));
     }
 
     @Test(groups = "slow")
     public void testRefundWithInvoiceItemAdjustemts() throws Exception {
-        refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment, invoiceItems, NextEvent.INVOICE_ADJUSTMENT);
+
+        final Map<UUID, BigDecimal> iias = new HashMap<UUID, BigDecimal>();
+        iias.put(invoice.getInvoiceItems().get(0).getId(), null);
+        refundPaymentWithInvoiceItemAdjAndCheckForCompletion(account, payment, iias, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
         refundChecker.checkRefund(payment.getId(), callContext, new ExpectedRefundCheck(payment.getId(), true, new BigDecimal("233.82"), Currency.USD, initialCreationDate.toLocalDate()));
         invoice = invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext,
                                               new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
                                                                            new LocalDate(2012, 3, 31), InvoiceItemType.RECURRING, new BigDecimal("233.82")),
-                                              new ExpectedInvoiceItemCheck(InvoiceItemType.ITEM_ADJ, new BigDecimal("-233.82")));
+                                              new ExpectedInvoiceItemCheck(InvoiceItemType.ITEM_ADJ, new BigDecimal("-233.82"))
+                                             );
     }
 
     @Test(groups = "slow")
     public void testRefundWithInvoiceAdjustment() throws Exception {
-        refundPaymentWithAdjustmenttAndCheckForCompletion(account, payment, NextEvent.INVOICE_ADJUSTMENT);
+        refundPaymentWithAdjustmentAndCheckForCompletion(account, payment, NextEvent.PAYMENT, NextEvent.INVOICE_ADJUSTMENT);
         refundChecker.checkRefund(payment.getId(), callContext, new ExpectedRefundCheck(payment.getId(), true, new BigDecimal("233.82"), Currency.USD, initialCreationDate.toLocalDate()));
         invoice = invoiceChecker.checkInvoice(account.getId(), invoiceItemCount++, callContext,
                                               new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
                                                                            new LocalDate(2012, 3, 31), InvoiceItemType.RECURRING, new BigDecimal("233.82")),
-                                              new ExpectedInvoiceItemCheck(InvoiceItemType.REFUND_ADJ, new BigDecimal("-233.82")));
-
+                                              new ExpectedInvoiceItemCheck(InvoiceItemType.REFUND_ADJ, new BigDecimal("-233.82"))
+                                             );
     }
 
     private void setupRefundTest() throws Exception {
@@ -119,7 +126,7 @@ public class TestPaymentRefund extends TestIntegrationBase {
         setDateAndCheckForCompletion(new DateTime(2012, 3, 2, 23, 59, 59, 0, testTimeZone), NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
         invoice = invoiceChecker.checkInvoice(account.getId(), ++invoiceItemCount, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 3, 2),
                                                                                                                              new LocalDate(2012, 3, 31), InvoiceItemType.RECURRING, new BigDecimal("233.82")));
-        payment = paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 3, 2), new BigDecimal("233.82"), PaymentStatus.SUCCESS, invoice.getId(), Currency.USD));
+        payment = paymentChecker.checkPayment(account.getId(), 1, callContext, new ExpectedPaymentCheck(new LocalDate(2012, 3, 2), new BigDecimal("233.82"), TransactionStatus.SUCCESS, invoice.getId(), Currency.USD));
 
         // Filter and extract UUId from all Recuring invoices
         invoiceItems = new HashSet<UUID>(Collections2.transform(Collections2.filter(invoice.getInvoiceItems(), new Predicate<InvoiceItem>() {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
index 8a1f453..3ac628b 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestPublicBus.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -21,9 +23,6 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.joda.time.DateTime;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.api.TestApiListener.NextEvent;
 import org.killbill.billing.catalog.api.BillingPeriod;
@@ -31,6 +30,8 @@ import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
 import org.killbill.billing.notification.plugin.api.ExtBusEvent;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
 
 import com.google.common.eventbus.Subscribe;
 
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestRepairIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestRepairIntegration.java
index 2802896..3a681d2 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestRepairIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestRepairIntegration.java
@@ -148,6 +148,8 @@ public class TestRepairIntegration extends TestIntegrationBase {
 
             assertListenerStatus();
         }
+
+        checkNoMoreInvoiceToGenerate(account);
     }
 
     protected SubscriptionBaseTimeline createSubscriptionReapir(final UUID id, final List<DeletedEvent> deletedEvents, final List<NewEvent> newEvents) {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
index da39b52..c48e416 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestSubscription.java
@@ -1,5 +1,6 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, 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
@@ -113,5 +114,62 @@ public class TestSubscription extends TestIntegrationBase {
                 new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 5, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("-2334.20")));
         invoiceChecker.checkInvoice(invoices.get(3).getId(), callContext, toBeChecked);
 
+        checkNoMoreInvoiceToGenerate(account);
+
+    }
+
+    @Test(groups = "slow")
+    public void testChangeOfPlan() throws Exception {
+        // We take april as it has 30 days (easier to play with BCD)
+        final LocalDate today = new LocalDate(2012, 4, 1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(1));
+
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDeltaFromReality(today.toDateTimeAtCurrentTime(DateTimeZone.UTC).getMillis() - clock.getUTCNow().getMillis());
+
+        final String productName = "Shotgun";
+        final BillingPeriod term = BillingPeriod.ANNUAL;
+        final String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
+        //
+
+        final DefaultEntitlement bpEntitlement = createBaseEntitlementAndCheckForCompletion(account.getId(), "externalKey", productName, ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.INVOICE);
+        assertNotNull(bpEntitlement);
+        assertEquals(invoiceUserApi.getInvoicesByAccount(account.getId(), callContext).size(), 1);
+
+        assertEquals(bpEntitlement.getSubscriptionBase().getCurrentPlan().getRecurringBillingPeriod(), BillingPeriod.ANNUAL);
+
+        // Move out of trials for interesting invoices adjustments
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.addDays(40);
+        assertListenerStatus();
+
+        List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 2);
+        ImmutableList<ExpectedInvoiceItemCheck> toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+        //
+        // FORCE AN IMMEDIATE CHANGE TO TEST THE CHANGE_OF_PLAN ALIGNMENT
+        // (Note that, the catalog is configured to use  CHANGE_OF_PLAN when moving to that plan and Not CHANGE_OF_PRICELIST which has not been implement;
+        // this is a bit misleading since we are changing pricelist, but in that case pricelist change has no effect)
+        changeEntitlementAndCheckForCompletion(bpEntitlement, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue", BillingActionPolicy.IMMEDIATE, NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_ADJUSTMENT, NextEvent.PAYMENT);
+        invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
+        assertEquals(invoices.size(), 3);
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2013, 5, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2334.20")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 5, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("2334.20")));
+        invoiceChecker.checkInvoice(invoices.get(1).getId(), callContext, toBeChecked);
+
+        toBeChecked = ImmutableList.<ExpectedInvoiceItemCheck>of(
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("5835.57")),
+                new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 11), new LocalDate(2012, 5, 11), InvoiceItemType.CBA_ADJ, new BigDecimal("-2334.20")));
+        invoiceChecker.checkInvoice(invoices.get(2).getId(), callContext, toBeChecked);
+
+        checkNoMoreInvoiceToGenerate(account);
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
new file mode 100644
index 0000000..43a8a9b
--- /dev/null
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTaxItems.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.beatrix.integration;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.inject.Inject;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.api.TestApiListener.NextEvent;
+import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.invoice.model.TaxInvoiceItem;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.util.api.TagApiException;
+import org.killbill.billing.util.api.TagDefinitionApiException;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.tag.ControlTagType;
+import org.killbill.billing.util.tag.Tag;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestWithTaxItems extends TestIntegrationBase {
+
+    @Inject
+    private OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
+
+    private TestInvoicePluginApi testInvoicePluginApi;
+
+    @BeforeClass(groups = "slow")
+    public void beforeClass() throws Exception {
+        super.beforeClass();
+
+        this.testInvoicePluginApi = new TestInvoicePluginApi();
+        pluginRegistry.registerService(new OSGIServiceDescriptor() {
+            @Override
+            public String getPluginSymbolicName() {
+                return "TaxInvoicePluginApi";
+            }
+
+            @Override
+            public String getRegistrationName() {
+                return "TaxInvoicePluginApi";
+            }
+        }, testInvoicePluginApi);
+    }
+
+    private void add_AUTO_INVOICING_OFF_Tag(final UUID id) throws TagDefinitionApiException, TagApiException {
+        busHandler.pushExpectedEvent(NextEvent.TAG);
+        tagUserApi.addTag(id, ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
+        assertListenerStatus();
+        final List<Tag> tags = tagUserApi.getTagsForObject(id, ObjectType.ACCOUNT, false, callContext);
+        assertEquals(tags.size(), 1);
+    }
+
+    private void remove_AUTO_INVOICING_OFF_Tag(final UUID id, final NextEvent... events) throws TagDefinitionApiException, TagApiException {
+        final NextEvent[] allEvents = new NextEvent[events.length + 1];
+        allEvents[0] = NextEvent.TAG;
+        int i = 1;
+        for (NextEvent cur : events) {
+            allEvents[i++] = cur;
+        }
+        busHandler.pushExpectedEvents(allEvents);
+        tagUserApi.removeTag(id, ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), callContext);
+        assertListenerStatus();
+    }
+
+    @Test(groups = "slow")
+    public void testXXX() throws Exception {
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        // We take april as it has 30 days (easier to play with BCD)
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2012, 4, 1));
+
+        //
+        // Create original subscription (Trial PHASE) -> $0 invoice.
+        final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+        subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+
+        // Move to Evergreen PHASE, but add AUTO_INVOICING_OFF => No invoice
+        add_AUTO_INVOICING_OFF_Tag(account.getId());
+        busHandler.pushExpectedEvent(NextEvent.PHASE);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        // Add Cleaning ADD_ON => No Invoice
+        busHandler.pushExpectedEvent(NextEvent.CREATE);
+        addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Cleaning", ProductCategory.ADD_ON, BillingPeriod.MONTHLY);
+        assertListenerStatus();
+
+        // Make sure TestInvoicePluginApi will return an additional TAX item
+        testInvoicePluginApi.addTaxItem();
+
+        // Remove AUTO_INVOICING_OFF => Invoice + Payment
+        remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE, NextEvent.PAYMENT);
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
+
+        // Add AUTO_INVOICING_OFF and change to a higher plan on the same day that already include the 'Cleaning' ADD_ON, so it gets cancelled
+        add_AUTO_INVOICING_OFF_Tag(account.getId());
+        busHandler.pushExpectedEvents(NextEvent.CHANGE, NextEvent.CANCEL, NextEvent.BLOCK);
+        changeEntitlementAndCheckForCompletion(bpSubscription, "Shotgun", BillingPeriod.MONTHLY, BillingActionPolicy.IMMEDIATE);
+        assertListenerStatus();
+
+        // Make sure TestInvoicePluginApi will return an additional TAX item
+        testInvoicePluginApi.addTaxItem();
+
+        // Remove AUTO_INVOICING_OFF => Invoice + Payment
+        remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE_ADJUSTMENT, NextEvent.INVOICE, NextEvent.PAYMENT);
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("29.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-29.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("2.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-2.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("32.90")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
+
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-32.90")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
+
+        // Add AUTO_INVOICING_OFF and change to a higher plan on the same day
+        add_AUTO_INVOICING_OFF_Tag(account.getId());
+        busHandler.pushExpectedEvent(NextEvent.CHANGE);
+        changeEntitlementAndCheckForCompletion(bpSubscription, "Assault-Rifle", BillingPeriod.MONTHLY, BillingActionPolicy.IMMEDIATE);
+        assertListenerStatus();
+
+        // Make sure TestInvoicePluginApi will return an additional TAX item
+        testInvoicePluginApi.addTaxItem();
+
+        // Remove AUTO_INVOICING_OFF => Invoice + Payment
+        remove_AUTO_INVOICING_OFF_Tag(account.getId(), NextEvent.INVOICE_ADJUSTMENT, NextEvent.INVOICE, NextEvent.PAYMENT);
+
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.REPAIR_ADJ, new BigDecimal("-249.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-32.90")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("249.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
+
+        invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("599.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 5, 1), InvoiceItemType.CBA_ADJ, new BigDecimal("-249.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.TAX, new BigDecimal("1.0")));
+
+    }
+
+    public class TestInvoicePluginApi implements InvoicePluginApi {
+
+        AtomicBoolean addTaxItem;
+
+        public TestInvoicePluginApi() {
+            this.addTaxItem = new AtomicBoolean();
+        }
+
+        @Override
+        public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice invoice, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) {
+            return addTaxItem.compareAndSet(true, false) ? ImmutableList.<InvoiceItem>of(createTaxInvoiceItem(invoice)) : ImmutableList.<InvoiceItem>of();
+        }
+
+        private InvoiceItem createTaxInvoiceItem(final Invoice invoice) {
+            return new TaxInvoiceItem(invoice.getId(), invoice.getAccountId(), null, "Tax Item", clock.getUTCNow().toLocalDate(), BigDecimal.ONE, invoice.getCurrency());
+        }
+
+        public void addTaxItem() {
+            this.addTaxItem.set(true);
+        }
+    }
+}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
index 4cce354..84f8329 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
@@ -38,6 +38,7 @@ import org.killbill.billing.usage.api.RolledUpUsage;
 import org.killbill.billing.usage.api.UsageUserApi;
 import org.killbill.billing.usage.api.user.DefaultRolledUpUsage;
 import org.killbill.billing.usage.api.user.MockUsageUserApi;
+import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.mockito.Mockito;
 import org.testng.annotations.BeforeMethod;
@@ -73,7 +74,7 @@ public class TestConsumableInArrear extends TestIntegrationBase {
         //
         // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE NextEvent.INVOICE
         //
-        final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+        final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.INVOICE);
         // Check bundle after BP got created otherwise we get an error from auditApi.
         subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
         invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
@@ -82,28 +83,42 @@ public class TestConsumableInArrear extends TestIntegrationBase {
         //
         // ADD ADD_ON ON THE SAME DAY
         //
-        setUsage();
-        addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Bullets", ProductCategory.ADD_ON, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE);
+        final DefaultEntitlement aoSubscription = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Bullets", ProductCategory.ADD_ON, BillingPeriod.NO_BILLING_PERIOD, NextEvent.CREATE);
 
-        final RolledUpUsage usage = new DefaultRolledUpUsage(UUID.randomUUID(), "bullets", new LocalDate(2012, 4, 1).toDateTimeAtStartOfDay(DateTimeZone.UTC), new LocalDate(2012, 5, 1).toDateTimeAtStartOfDay(DateTimeZone.UTC), new BigDecimal("199"));
-        setUsage(usage);
+        setUsage(aoSubscription.getId(), "bullets", new DateTime(2012, 4, 1, 1, 1, DateTimeZone.UTC), new DateTime(2012, 4, 15, 0, 0, DateTimeZone.UTC), new BigDecimal("99"), callContext);
+        setUsage(aoSubscription.getId(), "bullets", new DateTime(2012, 4, 15, 0, 0, DateTimeZone.UTC), new DateTime(2012, 4, 20, 1, 1, DateTimeZone.UTC), new BigDecimal("100"), callContext);
 
         busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT);
         clock.setDay(new LocalDate(2012, 5, 1));
         assertListenerStatus();
 
         invoiceChecker.checkInvoice(account.getId(), 2, callContext,
-                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("249.95")),
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
                                     new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")));
 
 
+        busHandler.pushExpectedEvents(NextEvent.INVOICE);
+        clock.setDay(new LocalDate(2012, 6, 1));
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2012, 6, 1), InvoiceItemType.USAGE, BigDecimal.ZERO));
+
+
+        setUsage(aoSubscription.getId(), "bullets", new DateTime(2012, 6, 1, 1, 1, DateTimeZone.UTC), new DateTime(2012, 6, 15, 1, 1, DateTimeZone.UTC), new BigDecimal("50"), callContext);
+        setUsage(aoSubscription.getId(), "bullets", new DateTime(2012, 6, 16, 1, 1, DateTimeZone.UTC), new DateTime(2012, 6, 20, 1, 1, DateTimeZone.UTC), new BigDecimal("300"), callContext);
+
+
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT);
+        clock.setDay(new LocalDate(2012, 7, 1));
+        assertListenerStatus();
+
+        invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+                                    new ExpectedInvoiceItemCheck(new LocalDate(2012, 6, 1), new LocalDate(2012, 7, 1), InvoiceItemType.USAGE, new BigDecimal("11.80")));
+
     }
 
-    private void setUsage(final RolledUpUsage...usages) {
-        final List<RolledUpUsage> usageList = new ArrayList<RolledUpUsage>();
-        for (RolledUpUsage usage : usages) {
-            usageList.add(usage);
-        }
-        ((MockUsageUserApi) usageUserApi).setAllUsageForSubscription(usageList);
+    private void setUsage(final UUID subscriptionId, final String unitType, final DateTime startTime, final DateTime endTime, final BigDecimal amount, final CallContext context) {
+        usageUserApi.recordRolledUpUsage(subscriptionId, unitType, startTime, endTime, amount, context);
     }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/AuditChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/AuditChecker.java
index 4a75f23..aa0398a 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/AuditChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/AuditChecker.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -22,13 +24,6 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
-import org.skife.jdbi.v2.Handle;
-import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.tweak.HandleCallback;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.dao.AccountSqlDao;
 import org.killbill.billing.entitlement.api.SubscriptionApi;
@@ -39,7 +34,9 @@ import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.dao.InvoiceItemSqlDao;
 import org.killbill.billing.invoice.dao.InvoiceSqlDao;
 import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.dao.PaymentSqlDao;
+import org.killbill.billing.payment.dao.TransactionSqlDao;
 import org.killbill.billing.subscription.engine.dao.BundleSqlDao;
 import org.killbill.billing.subscription.engine.dao.SubscriptionEventSqlDao;
 import org.killbill.billing.subscription.engine.dao.SubscriptionSqlDao;
@@ -55,6 +52,12 @@ import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.entity.Entity;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.tweak.HandleCallback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 
 import com.google.inject.Inject;
 
@@ -87,14 +90,6 @@ public class AuditChecker {
         checkAuditLog(ChangeType.UPDATE, context, result.getAuditLogsForAccount().get(1), account.getId(), AccountSqlDao.class, true, true);
     }
 
-    public void checkPaymentCreated(final Payment payment, final CallContext context) {
-        final AccountAuditLogs result = auditUserApi.getAccountAuditLogs(payment.getAccountId(), AuditLevel.FULL, context);
-        final List<AuditLog> paymentLogs = result.getAuditLogsForPayment(payment.getId());
-        Assert.assertEquals(paymentLogs.size(), 2);
-        checkAuditLog(ChangeType.INSERT, context, paymentLogs.get(0), payment.getId(), PaymentSqlDao.class, true, false);
-        checkAuditLog(ChangeType.UPDATE, context, paymentLogs.get(1), payment.getId(), PaymentSqlDao.class, true, false);
-    }
-
     /**
      * ********************************************  BUNDLE *******************************************************
      */
@@ -167,6 +162,24 @@ public class AuditChecker {
         }
     }
 
+    /**
+     * ********************************************  PAYMENT *******************************************************
+     */
+
+    public void checkPaymentCreated(final Payment payment, final CallContext context) {
+        final List<AuditLog> paymentLogs = getAuditLogForPayment(payment, context);
+        Assert.assertEquals(paymentLogs.size(), 2);
+        checkAuditLog(ChangeType.INSERT, context, paymentLogs.get(0), payment.getId(), PaymentSqlDao.class, true, false);
+        checkAuditLog(ChangeType.UPDATE, context, paymentLogs.get(1), payment.getId(), PaymentSqlDao.class, true, false);
+
+        for (PaymentTransaction cur : payment.getTransactions()) {
+            final List<AuditLog> auditLogs = getAuditLogForPaymentTransaction(payment, cur, context);
+            Assert.assertEquals(auditLogs.size(), 2);
+            checkAuditLog(ChangeType.INSERT, context, auditLogs.get(0), cur.getId(), TransactionSqlDao.class, true, false);
+            checkAuditLog(ChangeType.UPDATE, context, auditLogs.get(1), cur.getId(), TransactionSqlDao.class, true, false);
+        }
+    }
+
     private List<AuditLog> getAuditLogsForBundle(final UUID bundleId, final TenantContext context) {
         try {
             final SubscriptionBundle bundle = subscriptionApi.getSubscriptionBundle(bundleId, context);
@@ -205,6 +218,14 @@ public class AuditChecker {
         return auditUserApi.getAccountAuditLogs(invoiceItem.getAccountId(), AuditLevel.FULL, context).getAuditLogsForInvoiceItem(invoiceItem.getId());
     }
 
+    private List<AuditLog> getAuditLogForPayment(final Payment payment, final TenantContext context) {
+        return auditUserApi.getAccountAuditLogs(payment.getAccountId(), AuditLevel.FULL, context).getAuditLogsForPayment(payment.getId());
+    }
+
+    private List<AuditLog> getAuditLogForPaymentTransaction(final Payment payment, final PaymentTransaction paymentTransaction, final TenantContext context) {
+        return auditUserApi.getAccountAuditLogs(payment.getAccountId(), AuditLevel.FULL, context).getAuditLogsForPaymentTransaction(paymentTransaction.getId());
+    }
+
     private void checkAuditLog(final ChangeType insert, final AuditLog auditLog) {
         checkAuditLog(insert, null, auditLog, null, EntitySqlDao.class, false, false);
     }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java
index fb1105c..69cfe82 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/PaymentChecker.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -21,17 +23,22 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.PaymentStatus;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.callcontext.CallContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
 public class PaymentChecker {
@@ -48,31 +55,41 @@ public class PaymentChecker {
     }
 
     public Payment checkPayment(final UUID accountId, final int paymentOrderingNumber, final CallContext context, ExpectedPaymentCheck expected) throws PaymentApiException {
-        final List<Payment> payments = paymentApi.getAccountPayments(accountId, context);
+        final List<Payment> payments = paymentApi.getAccountPayments(accountId, false, ImmutableList.<PluginProperty>of(), context);
         Assert.assertEquals(payments.size(), paymentOrderingNumber);
         final Payment payment = payments.get(paymentOrderingNumber - 1);
-        if (payment.getPaymentStatus() == PaymentStatus.UNKNOWN) {
-            checkPaymentNoAuditForRuntimeException(accountId, payment, context, expected);
+        final PaymentTransaction transaction = getPurchaseTransaction(payment);
+        if (transaction.getTransactionStatus() == TransactionStatus.UNKNOWN) {
+            checkPaymentNoAuditForRuntimeException(accountId, payment, expected);
         } else {
             checkPayment(accountId, payment, context, expected);
         }
         return payment;
     }
 
+    private PaymentTransaction getPurchaseTransaction(final Payment payment) {
+        return Iterables.tryFind(payment.getTransactions(), new Predicate<PaymentTransaction>() {
+            @Override
+            public boolean apply(final PaymentTransaction input) {
+                return input.getTransactionType() == TransactionType.PURCHASE;
+            }
+        }).get();
+    }
+
     private void checkPayment(final UUID accountId, final Payment payment, final CallContext context, final ExpectedPaymentCheck expected) {
         Assert.assertEquals(payment.getAccountId(), accountId);
-        Assert.assertTrue(payment.getAmount().compareTo(expected.getAmount()) == 0);
-        Assert.assertEquals(payment.getPaymentStatus(), expected.getStatus());
-        Assert.assertEquals(payment.getInvoiceId(), expected.getInvoiceId());
+        final PaymentTransaction transaction = getPurchaseTransaction(payment);
+        Assert.assertTrue(transaction.getAmount().compareTo(expected.getAmount()) == 0);
+        Assert.assertEquals(transaction.getTransactionStatus(), expected.getStatus());
         Assert.assertEquals(payment.getCurrency(), expected.getCurrency());
         auditChecker.checkPaymentCreated(payment, context);
     }
 
-    private void checkPaymentNoAuditForRuntimeException(final UUID accountId, final Payment payment, final CallContext context, final ExpectedPaymentCheck expected) {
+    private void checkPaymentNoAuditForRuntimeException(final UUID accountId, final Payment payment, final ExpectedPaymentCheck expected) {
         Assert.assertEquals(payment.getAccountId(), accountId);
-        Assert.assertTrue(payment.getAmount().compareTo(expected.getAmount()) == 0);
-        Assert.assertEquals(payment.getPaymentStatus(), expected.getStatus());
-        Assert.assertEquals(payment.getInvoiceId(), expected.getInvoiceId());
+        final PaymentTransaction transaction = getPurchaseTransaction(payment);
+        Assert.assertTrue(transaction.getAmount().compareTo(expected.getAmount()) == 0);
+        Assert.assertEquals(transaction.getTransactionStatus(), expected.getStatus());
         Assert.assertEquals(payment.getCurrency(), expected.getCurrency());
     }
 
@@ -80,11 +97,11 @@ public class PaymentChecker {
 
         private final LocalDate paymentDate;
         private final BigDecimal amount;
-        private final PaymentStatus status;
+        private final TransactionStatus status;
         private final UUID invoiceId;
         private final Currency currency;
 
-        public ExpectedPaymentCheck(final LocalDate paymentDate, final BigDecimal amount, final PaymentStatus status, final UUID invoiceId, final Currency currency) {
+        public ExpectedPaymentCheck(final LocalDate paymentDate, final BigDecimal amount, final TransactionStatus status, final UUID invoiceId, final Currency currency) {
             this.paymentDate = paymentDate;
             this.amount = amount;
             this.status = status;
@@ -104,7 +121,7 @@ public class PaymentChecker {
             return amount;
         }
 
-        public PaymentStatus getStatus() {
+        public TransactionStatus getStatus() {
             return status;
         }
 
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java
index 87c9439..6eb8afd 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -24,22 +26,26 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.LocalDate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentApi;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
+import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.payment.api.PaymentTransaction;
 import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.Refund;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.callcontext.CallContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
 public class RefundChecker {
@@ -59,23 +65,28 @@ public class RefundChecker {
         this.invoiceUserApi = invoiceApi;
     }
 
-    public Refund checkRefund(final UUID paymentId, final CallContext context, ExpectedRefundCheck expected) throws PaymentApiException {
+    public PaymentTransaction checkRefund(final UUID paymentId, final CallContext context, ExpectedRefundCheck expected) throws PaymentApiException {
 
-        final List<Refund> refunds = paymentApi.getPaymentRefunds(paymentId, context);
-        Assert.assertEquals(refunds.size(), 1);
+        final Payment payment = paymentApi.getPayment(paymentId, false, ImmutableList.<PluginProperty>of(), context);
+        final PaymentTransaction refund = Iterables.tryFind(payment.getTransactions(), new Predicate<PaymentTransaction>() {
+            @Override
+            public boolean apply(final PaymentTransaction input) {
+                return input.getTransactionType() == TransactionType.REFUND;
+            }
+        }).orNull();
+
+        Assert.assertNotNull(refund);
 
         final InvoicePayment refundInvoicePayment = getInvoicePaymentEntry(paymentId, InvoicePaymentType.REFUND, context);
         final InvoicePayment invoicePayment = getInvoicePaymentEntry(paymentId, InvoicePaymentType.ATTEMPT, context);
 
-        final Refund refund = refunds.get(0);
         Assert.assertEquals(refund.getPaymentId(), expected.getPaymentId());
         Assert.assertEquals(refund.getCurrency(), expected.getCurrency());
-        Assert.assertEquals(refund.isAdjusted(), expected.isAdjusted);
-        Assert.assertEquals(refund.getRefundAmount().compareTo(expected.getRefundAmount()), 0);
+        Assert.assertEquals(refund.getAmount().compareTo(expected.getRefundAmount()), 0);
 
         Assert.assertEquals(refundInvoicePayment.getPaymentId(), paymentId);
         Assert.assertEquals(refundInvoicePayment.getLinkedInvoicePaymentId(), invoicePayment.getId());
-        Assert.assertEquals(refundInvoicePayment.getPaymentCookieId(), refund.getId());
+        Assert.assertEquals(refundInvoicePayment.getPaymentCookieId(), refund.getExternalKey());
         Assert.assertEquals(refundInvoicePayment.getInvoiceId(), invoicePayment.getInvoiceId());
         Assert.assertEquals(refundInvoicePayment.getAmount().compareTo(expected.getRefundAmount().negate()), 0);
         Assert.assertEquals(refundInvoicePayment.getCurrency(), expected.getCurrency());
@@ -83,6 +94,15 @@ public class RefundChecker {
         return refund;
     }
 
+    private PaymentTransaction getRefundTransaction(final Payment payment) {
+        return Iterables.tryFind(payment.getTransactions(), new Predicate<PaymentTransaction>() {
+            @Override
+            public boolean apply(final PaymentTransaction input) {
+                return input.getTransactionType() == TransactionType.REFUND;
+            }
+        }).get();
+    }
+
     private InvoicePayment getInvoicePaymentEntry(final UUID paymentId, final InvoicePaymentType type, final CallContext context) {
         final List<InvoicePayment> invoicePayments = invoicePaymentApi.getInvoicePayments(paymentId, context);
         final Collection<InvoicePayment> refundInvoicePayments = Collections2.filter(invoicePayments, new Predicate<InvoicePayment>() {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java
index 2c0391b..1b0c6c9 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/SubscriptionChecker.java
@@ -21,6 +21,8 @@ import java.util.UUID;
 
 import javax.inject.Inject;
 
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -44,12 +46,14 @@ public class SubscriptionChecker {
     private final SubscriptionBaseInternalApi subscriptionApi;
     private final AuditChecker auditChecker;
     private final NonEntityDao nonEntityDao;
+    private final CacheControllerDispatcher cacheControllerDispatcher;
 
     @Inject
-    public SubscriptionChecker(final SubscriptionBaseInternalApi subscriptionApi, final AuditChecker auditChecker, final NonEntityDao nonEntityDao) {
+    public SubscriptionChecker(final SubscriptionBaseInternalApi subscriptionApi, final AuditChecker auditChecker, final NonEntityDao nonEntityDao, final CacheControllerDispatcher cacheControllerDispatcher) {
         this.subscriptionApi = subscriptionApi;
         this.auditChecker = auditChecker;
         this.nonEntityDao = nonEntityDao;
+        this.cacheControllerDispatcher = cacheControllerDispatcher;
     }
 
     public SubscriptionBaseBundle checkBundleNoAudits(final UUID bundleId, final UUID expectedAccountId, final String expectedKey, final InternalTenantContext context) throws SubscriptionBaseApiException {
@@ -61,7 +65,7 @@ public class SubscriptionChecker {
     }
 
     public SubscriptionBase checkSubscriptionCreated(final UUID subscriptionId, final InternalCallContext context) throws SubscriptionBaseApiException {
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
+        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
         final CallContext callContext = context.toCallContext(tenantId);
 
         final SubscriptionBase subscription = subscriptionApi.getSubscriptionFromId(subscriptionId, context);

bin/db-helper 2(+1 -1)

diff --git a/bin/db-helper b/bin/db-helper
index d995243..d17568b 100755
--- a/bin/db-helper
+++ b/bin/db-helper
@@ -37,7 +37,7 @@ DDL_FILE=
 CLEAN_FILE=
 
 # Egrep like for skipping some modules until they are ready
-SKIP="(usage|server)"
+SKIP="(server)"
 
 function usage() {
     echo -n "./db_helper "

bin/start-server 3(+2 -1)

diff --git a/bin/start-server b/bin/start-server
index 438560f..755c267 100755
--- a/bin/start-server
+++ b/bin/start-server
@@ -21,7 +21,8 @@
 
 HERE=`cd \`dirname $0\`; pwd`
 TOP=$HERE/..
-SERVER=$TOP/server
+# Assume killbill profile by default for now
+SERVER=$TOP/profiles/killbill
 
 PROPERTIES=${PROPERTIES-"$SERVER/src/main/resources/killbill-server.properties"}
 

catalog/pom.xml 35(+34 -1)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 9d94dc3..129e5df 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>
@@ -41,6 +41,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
@@ -54,6 +59,20 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
@@ -83,6 +102,16 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-queue</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-xmlloader</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-all</artifactId>
             <scope>test</scope>
@@ -93,6 +122,10 @@
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/CreateCatalogSchema.java b/catalog/src/main/java/org/killbill/billing/catalog/CreateCatalogSchema.java
index 8221446..c45c809 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/CreateCatalogSchema.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/CreateCatalogSchema.java
@@ -20,7 +20,7 @@ import java.io.File;
 import java.io.FileWriter;
 import java.io.Writer;
 
-import org.killbill.billing.util.config.catalog.XMLSchemaGenerator;
+import org.killbill.xmlloader.XMLSchemaGenerator;
 
 public class CreateCatalogSchema {
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
index 14a73ec..f5130db 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultBlock.java
@@ -28,9 +28,9 @@ import org.killbill.billing.catalog.api.BlockType;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.InternationalPrice;
 import org.killbill.billing.catalog.api.Unit;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultBlock extends ValidatingConfig<StandaloneCatalog> implements Block {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultCatalogService.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultCatalogService.java
index 4fcb325..c9650fe 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultCatalogService.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultCatalogService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -20,9 +22,9 @@ import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.billing.catalog.io.VersionedCatalogLoader;
-import org.killbill.billing.lifecycle.KillbillService;
-import org.killbill.billing.lifecycle.LifecycleHandlerType;
-import org.killbill.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.billing.platform.api.KillbillService;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
 import org.killbill.billing.util.config.CatalogConfig;
 
 import com.google.inject.Inject;
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
index b769ec2..a049cc6 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultDuration.java
@@ -25,9 +25,9 @@ import org.joda.time.Period;
 
 import org.killbill.billing.catalog.api.Duration;
 import org.killbill.billing.catalog.api.TimeUnit;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> implements Duration {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
index 3d1b237..8480115 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultFixed.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
 package org.killbill.billing.catalog;
 
 import java.net.URI;
@@ -11,12 +27,13 @@ import org.killbill.billing.catalog.api.Fixed;
 import org.killbill.billing.catalog.api.FixedType;
 import org.killbill.billing.catalog.api.InternationalPrice;
 import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultFixed extends ValidatingConfig<StandaloneCatalog> implements Fixed {
 
+
     @XmlAttribute(required = false)
     private FixedType type = FixedType.ONE_TIME;
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
index ed425c7..2607cfd 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultInternationalPrice.java
@@ -28,8 +28,8 @@ import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.CurrencyValueNull;
 import org.killbill.billing.catalog.api.InternationalPrice;
 import org.killbill.billing.catalog.api.Price;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultInternationalPrice extends ValidatingConfig<StandaloneCatalog> implements InternationalPrice {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
index 4f3cd2d..343202c 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultLimit.java
@@ -22,9 +22,9 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlIDREF;
 
 import org.killbill.billing.catalog.api.Limit;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultLimit extends ValidatingConfig<StandaloneCatalog> implements Limit {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index 0c4071b..087ff2c 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -40,9 +40,9 @@ import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.Recurring;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements Plan {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
index ea16bd8..d9b2d5b 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlanPhase.java
@@ -33,9 +33,9 @@ import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.Recurring;
 import org.killbill.billing.catalog.api.Usage;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultPlanPhase extends ValidatingConfig<StandaloneCatalog> implements PlanPhase {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java
index cc5374c..38f4009 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPrice.java
@@ -24,8 +24,8 @@ import java.math.BigDecimal;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.CurrencyValueNull;
 import org.killbill.billing.catalog.api.Price;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultPrice extends ValidatingConfig<StandaloneCatalog> implements Price {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
index ad4b4ba..b1ad2ce 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceList.java
@@ -27,9 +27,9 @@ import javax.xml.bind.annotation.XmlIDREF;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.Product;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultPriceList extends ValidatingConfig<StandaloneCatalog> implements PriceList {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java
index f90e569..d10a58c 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPriceListSet.java
@@ -28,9 +28,9 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.PriceListSet;
 import org.killbill.billing.catalog.api.Product;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultPriceListSet extends ValidatingConfig<StandaloneCatalog> {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java
index fabd692..2a94272 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultProduct.java
@@ -30,8 +30,8 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Limit;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultProduct extends ValidatingConfig<StandaloneCatalog> implements Product {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
index 3ed196f..c9b6ae8 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultRecurring.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
 package org.killbill.billing.catalog;
 
 import java.net.URI;
@@ -9,9 +25,9 @@ import javax.xml.bind.annotation.XmlElement;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.Recurring;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultRecurring extends ValidatingConfig<StandaloneCatalog> implements Recurring {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
index 2d430cb..2b4841b 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultTier.java
@@ -27,9 +27,9 @@ import org.killbill.billing.catalog.api.InternationalPrice;
 import org.killbill.billing.catalog.api.Tier;
 import org.killbill.billing.catalog.api.TieredBlock;
 import org.killbill.billing.catalog.api.UsageType;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultTier extends ValidatingConfig<StandaloneCatalog> implements Tier {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
index 4616e05..54aeff2 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUnit.java
@@ -22,8 +22,8 @@ import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlID;
 
 import org.killbill.billing.catalog.api.Unit;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultUnit extends ValidatingConfig<StandaloneCatalog> implements Unit {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
index 9b7c328..0a15162 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultUsage.java
@@ -34,9 +34,9 @@ import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.Tier;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.catalog.api.UsageType;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultUsage extends ValidatingConfig<StandaloneCatalog> implements Usage {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
index 4312cba..3326932 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/glue/CatalogModule.java
@@ -1,5 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
  * 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
@@ -16,29 +18,25 @@
 
 package org.killbill.billing.catalog.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
 import org.killbill.billing.catalog.DefaultCatalogService;
 import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.catalog.api.CatalogUserApi;
 import org.killbill.billing.catalog.api.user.DefaultCatalogUserApi;
 import org.killbill.billing.catalog.io.ICatalogLoader;
 import org.killbill.billing.catalog.io.VersionedCatalogLoader;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.config.CatalogConfig;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.AbstractModule;
-
-public class CatalogModule extends AbstractModule {
-
-    protected final ConfigSource configSource;
+public class CatalogModule extends KillBillModule {
 
-    public CatalogModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public CatalogModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     protected void installConfig() {
-        final CatalogConfig config = new ConfigurationObjectFactory(configSource).build(CatalogConfig.class);
+        final CatalogConfig config = new ConfigurationObjectFactory(skifeConfigSource).build(CatalogConfig.class);
         bind(CatalogConfig.class).toInstance(config);
     }
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/io/ICatalogLoader.java b/catalog/src/main/java/org/killbill/billing/catalog/io/ICatalogLoader.java
index 33ed459..995d55f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/io/ICatalogLoader.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/io/ICatalogLoader.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,11 +19,10 @@
 package org.killbill.billing.catalog.io;
 
 import org.killbill.billing.catalog.VersionedCatalog;
-import org.killbill.billing.lifecycle.KillbillService.ServiceException;
+import org.killbill.billing.platform.api.KillbillService.ServiceException;
 
 public interface ICatalogLoader {
 
     public abstract VersionedCatalog load(String urlString)
             throws ServiceException;
-
 }
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
index af9cda1..e2087fc 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/io/VersionedCatalogLoader.java
@@ -26,10 +26,10 @@ import com.google.common.io.Resources;
 import com.google.inject.Inject;
 import org.killbill.billing.catalog.StandaloneCatalog;
 import org.killbill.billing.catalog.VersionedCatalog;
-import org.killbill.billing.lifecycle.KillbillService.ServiceException;
+import org.killbill.billing.platform.api.KillbillService.ServiceException;
 import org.killbill.clock.Clock;
-import org.killbill.billing.util.config.catalog.UriAccessor;
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.UriAccessor;
+import org.killbill.xmlloader.XMLLoader;
 
 public class VersionedCatalogLoader implements ICatalogLoader {
     private static final Object PROTOCOL_FOR_FILE = "file";
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/LoadCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/LoadCatalog.java
index 6deb26f..825e5d4 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/LoadCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/LoadCatalog.java
@@ -18,7 +18,7 @@ package org.killbill.billing.catalog;
 
 import java.io.File;
 
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.XMLLoader;
 
 public class LoadCatalog {
     public static void main(final String[] args) throws Exception {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java b/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java
index ba2359f..14f54b2 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/PriceListDefault.java
@@ -20,8 +20,8 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 
 import org.killbill.billing.catalog.api.PriceListSet;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class PriceListDefault extends DefaultPriceList {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/Case.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/Case.java
index 0b6ed2b..c68e71f 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/Case.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/Case.java
@@ -24,8 +24,8 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 public abstract class Case<T> extends ValidatingConfig<StandaloneCatalog> {
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/CaseChange.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/CaseChange.java
index 5812ae2..fa1c948 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/CaseChange.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/CaseChange.java
@@ -30,8 +30,8 @@ import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public abstract class CaseChange<T> extends ValidatingConfig<StandaloneCatalog> {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/CasePhase.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/CasePhase.java
index c2e2121..eaa05e4 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/CasePhase.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/CasePhase.java
@@ -23,7 +23,7 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidationErrors;
 
 public abstract class CasePhase<T> extends CaseStandardNaming<T> {
 
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/rules/PlanRules.java b/catalog/src/main/java/org/killbill/billing/catalog/rules/PlanRules.java
index 3108899..7a00f50 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/rules/PlanRules.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/rules/PlanRules.java
@@ -32,8 +32,8 @@ import org.killbill.billing.catalog.api.PlanAlignmentCreate;
 import org.killbill.billing.catalog.api.PlanChangeResult;
 import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.PlanSpecifier;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class PlanRules extends ValidatingConfig<StandaloneCatalog> {
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
index 42297ee..36ddcf8 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/StandaloneCatalog.java
@@ -47,8 +47,8 @@ import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.billing.catalog.rules.PlanRules;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlRootElement(name = "catalog")
 @XmlAccessorType(XmlAccessType.NONE)
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index 81affbf..cfc87ad 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -13,6 +15,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package org.killbill.billing.catalog;
 
 import java.net.URI;
@@ -29,7 +32,6 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingAlignment;
@@ -51,9 +53,8 @@ import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.StaticCatalog;
 import org.killbill.billing.catalog.api.Unit;
 import org.killbill.clock.Clock;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
-
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlRootElement(name = "catalog")
 @XmlAccessorType(XmlAccessType.NONE)
@@ -97,6 +98,7 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
     }
 
     private class PlanRequestWrapper {
+
         String name;
         String productName;
         BillingPeriod bp;
@@ -162,7 +164,6 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
         throw new CatalogApiException(ErrorCode.CAT_NO_CATALOG_FOR_GIVEN_DATE, requestedDate.toDate().toString());
     }
 
-
     //
     // Public methods not exposed in interface
     //
@@ -266,7 +267,6 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
         return versionForDate(requestedDate).findCurrentProduct(name);
     }
 
-
     //
     // Find a phase
     //
@@ -280,7 +280,6 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
         return plan.findPhase(phaseName);
     }
 
-
     //
     // Find a price list
     //
@@ -290,13 +289,12 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
         return versionForDate(requestedDate).findCurrentPriceList(name);
     }
 
-
     //
     // Rules
     //
     @Override
     public BillingActionPolicy planChangePolicy(final PlanPhaseSpecifier from,
-                                         final PlanSpecifier to, final DateTime requestedDate) throws CatalogApiException {
+                                                final PlanSpecifier to, final DateTime requestedDate) throws CatalogApiException {
         return versionForDate(requestedDate).planChangePolicy(from, to);
     }
 
@@ -316,7 +314,6 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
         return versionForDate(requestedDate).planCreateAlignment(specifier);
     }
 
-
     @Override
     public BillingAlignment billingAlignment(final PlanPhaseSpecifier planPhase, final DateTime requestedDate) throws CatalogApiException {
         return versionForDate(requestedDate).billingAlignment(planPhase);
@@ -412,7 +409,6 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
         return versionForDate(clock.getUTCNow()).findCurrentPhase(name);
     }
 
-
     @Override
     public PriceList findCurrentPricelist(final String name)
             throws CatalogApiException {
@@ -421,7 +417,7 @@ public class VersionedCatalog extends ValidatingConfig<StandaloneCatalog> implem
 
     @Override
     public BillingActionPolicy planChangePolicy(final PlanPhaseSpecifier from,
-                                         final PlanSpecifier to) throws CatalogApiException {
+                                                final PlanSpecifier to) throws CatalogApiException {
         return versionForDate(clock.getUTCNow()).planChangePolicy(from, to);
     }
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java
index 77e157c..590ca2c 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,19 +18,18 @@
 
 package org.killbill.billing.catalog.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestCatalogModule extends CatalogModule {
 
-    public TestCatalogModule(final ConfigSource configSource) {
+    public TestCatalogModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
     @Override
     public void configure() {
         super.configure();
-        install(new GuicyKillbillTestNoDBModule());
+        install(new GuicyKillbillTestNoDBModule(configSource));
     }
 }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleNoDB.java b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleNoDB.java
index 269271e..16639e2 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleNoDB.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/glue/TestCatalogModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,11 +18,11 @@
 
 package org.killbill.billing.catalog.glue;
 
-import org.skife.config.ConfigSource;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestCatalogModuleNoDB extends TestCatalogModule {
 
-    public TestCatalogModuleNoDB(final ConfigSource configSource) {
+    public TestCatalogModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 }
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java
index 77c3abe..fb024c4 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestVersionedCatalogLoader.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -28,15 +30,14 @@ import javax.xml.bind.JAXBException;
 import javax.xml.transform.TransformerException;
 
 import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-import org.xml.sax.SAXException;
-
 import org.killbill.billing.catalog.CatalogTestSuiteNoDB;
 import org.killbill.billing.catalog.StandaloneCatalog;
 import org.killbill.billing.catalog.VersionedCatalog;
 import org.killbill.billing.catalog.api.InvalidConfigException;
-import org.killbill.billing.lifecycle.KillbillService.ServiceException;
+import org.killbill.billing.platform.api.KillbillService.ServiceException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.xml.sax.SAXException;
 
 import com.google.common.io.Resources;
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLReader.java b/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLReader.java
index f05c686..ed4a141 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLReader.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/io/TestXMLReader.java
@@ -29,7 +29,7 @@ import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.catalog.api.UsageType;
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.XMLLoader;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalogModule.java b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalogModule.java
index f2585a1..ada9dd4 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockCatalogModule.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockCatalogModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,17 @@
 
 package org.killbill.billing.catalog;
 
-import org.mockito.Mockito;
-
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.mockito.Mockito;
 
-import com.google.inject.AbstractModule;
+public class MockCatalogModule extends KillBillModule {
 
-public class MockCatalogModule extends AbstractModule {
+    public MockCatalogModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockFixed.java b/catalog/src/test/java/org/killbill/billing/catalog/MockFixed.java
index 311a353..15fd2a2 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockFixed.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockFixed.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
 package org.killbill.billing.catalog;
 
 import org.killbill.billing.catalog.api.FixedType;
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/MockRecurring.java b/catalog/src/test/java/org/killbill/billing/catalog/MockRecurring.java
index 30c4a97..ac95f85 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/MockRecurring.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/MockRecurring.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
 package org.killbill.billing.catalog;
 
 import org.killbill.billing.catalog.api.BillingPeriod;
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestLoadRules.java b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestLoadRules.java
index 9c25a3e..470d88f 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/rules/TestLoadRules.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/rules/TestLoadRules.java
@@ -27,7 +27,7 @@ import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.PlanAlignmentCreate;
 import org.killbill.billing.catalog.api.PlanSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.XMLLoader;
 
 import com.google.common.io.Resources;
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogService.java b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogService.java
index 9c34586..3845f7b 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogService.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestCatalogService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,12 @@
 
 package org.killbill.billing.catalog;
 
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.io.VersionedCatalogLoader;
-import org.killbill.billing.lifecycle.KillbillService.ServiceException;
-import org.killbill.clock.DefaultClock;
+import org.killbill.billing.platform.api.KillbillService.ServiceException;
 import org.killbill.billing.util.config.CatalogConfig;
+import org.killbill.clock.DefaultClock;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestCatalogService extends CatalogTestSuiteNoDB {
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestInternationalPrice.java b/catalog/src/test/java/org/killbill/billing/catalog/TestInternationalPrice.java
index 9fb17df..a3803e2 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestInternationalPrice.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestInternationalPrice.java
@@ -25,7 +25,7 @@ import org.testng.annotations.Test;
 
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidationErrors;
 
 public class TestInternationalPrice extends CatalogTestSuiteNoDB {
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestPlan.java b/catalog/src/test/java/org/killbill/billing/catalog/TestPlan.java
index 67c2404..466b1c4 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestPlan.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestPlan.java
@@ -23,7 +23,7 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidationErrors;
 
 public class TestPlan extends CatalogTestSuiteNoDB {
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestPlanPhase.java b/catalog/src/test/java/org/killbill/billing/catalog/TestPlanPhase.java
index e68244d..dfa049b 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestPlanPhase.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestPlanPhase.java
@@ -24,7 +24,7 @@ import org.testng.annotations.Test;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.PhaseType;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidationErrors;
 
 public class TestPlanPhase extends CatalogTestSuiteNoDB {
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
index da2cfc2..b004cd8 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/TestVersionedCatalog.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -25,17 +27,16 @@ import javax.xml.bind.JAXBException;
 import javax.xml.transform.TransformerException;
 
 import org.joda.time.DateTime;
-import org.testng.Assert;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-import org.xml.sax.SAXException;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.InvalidConfigException;
 import org.killbill.billing.catalog.api.Plan;
-import org.killbill.billing.lifecycle.KillbillService.ServiceException;
+import org.killbill.billing.platform.api.KillbillService.ServiceException;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.xml.sax.SAXException;
 
 import com.google.common.io.Resources;
 
diff --git a/catalog/src/test/java/org/killbill/billing/catalog/util/CreateCatalogSchema.java b/catalog/src/test/java/org/killbill/billing/catalog/util/CreateCatalogSchema.java
index fc9eece..19e41cd 100644
--- a/catalog/src/test/java/org/killbill/billing/catalog/util/CreateCatalogSchema.java
+++ b/catalog/src/test/java/org/killbill/billing/catalog/util/CreateCatalogSchema.java
@@ -21,7 +21,7 @@ import java.io.FileWriter;
 import java.io.Writer;
 
 import org.killbill.billing.catalog.StandaloneCatalog;
-import org.killbill.billing.util.config.catalog.XMLSchemaGenerator;
+import org.killbill.xmlloader.XMLSchemaGenerator;
 
 // Tool to print the catalog XML Schema (XSD)
 public class CreateCatalogSchema {
diff --git a/catalog/src/test/resources/catalogTest.xml b/catalog/src/test/resources/catalogTest.xml
index ca07fe2..b45b587 100644
--- a/catalog/src/test/resources/catalogTest.xml
+++ b/catalog/src/test/resources/catalogTest.xml
@@ -48,11 +48,15 @@
         <product name="Pistol">
             <category>BASE</category>
             <available>
+                <addonProduct>Cleaning</addonProduct>
                 <addonProduct>Bullets</addonProduct>
             </available>
         </product>
         <product name="Shotgun">
             <category>BASE</category>
+            <included>
+                <addonProduct>Cleaning</addonProduct>
+            </included>
             <available>
                 <addonProduct>Telescopic-Scope</addonProduct>
                 <addonProduct>Laser-Scope</addonProduct>
@@ -64,12 +68,16 @@
             <category>BASE</category>
             <included>
                 <addonProduct>Telescopic-Scope</addonProduct>
+                <addonProduct>Cleaning</addonProduct>
             </included>
             <available>
                 <addonProduct>Laser-Scope</addonProduct>
                 <addonProduct>Bullets</addonProduct>
             </available>
         </product>
+        <product name="Cleaning">
+            <category>ADD_ON</category>
+        </product>
         <product name="Telescopic-Scope">
             <category>ADD_ON</category>
         </product>
@@ -732,6 +740,31 @@
                 </recurring>
             </finalPhase>
         </plan>
+        <plan name="cleaning-monthly">
+            <product>Cleaning</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>USD</currency>
+                            <value>2.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>1.95</value>
+                        </price>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>0.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
         <plan name="telescopic-scope-monthly">
             <product>Telescopic-Scope</product>
             <initialPhases>
@@ -1052,6 +1085,7 @@
                 <plan>assault-rifle-annual</plan>
                 <plan>laser-scope-monthly</plan>
                 <plan>telescopic-scope-monthly</plan>
+                <plan>cleaning-monthly</plan>
                 <plan>extra-ammo-monthly</plan>
                 <plan>holster-monthly-regular</plan>
                 <plan>refurbish-maintenance</plan>
diff --git a/catalog/src/test/resources/SpyCarAdvanced.xml b/catalog/src/test/resources/SpyCarAdvanced.xml
index 99eecee..be7c2c6 100644
--- a/catalog/src/test/resources/SpyCarAdvanced.xml
+++ b/catalog/src/test/resources/SpyCarAdvanced.xml
@@ -31,6 +31,11 @@
         <currency>BTC</currency>
     </currencies>
 
+    <units>
+        <unit name="gallons"/>
+    </units>
+
+
     <products>
         <product name="Standard">
             <category>BASE</category>
@@ -40,6 +45,7 @@
             <available>
                 <addonProduct>OilSlick</addonProduct>
                 <addonProduct>RemoteControl</addonProduct>
+                <addonProduct>Gas</addonProduct>
             </available>
         </product>
         <product name="Super">
@@ -49,6 +55,7 @@
             </included>
             <available>
                 <addonProduct>RemoteControl</addonProduct>
+                <addonProduct>Gas</addonProduct>
             </available>
         </product>
         <product name="OilSlick">
@@ -57,6 +64,9 @@
         <product name="RemoteControl">
             <category>ADD_ON</category>
         </product>
+        <product name="Gas">
+            <category>ADD_ON</category>
+        </product>
     </products>
 
     <rules>
@@ -683,6 +693,52 @@
                 </recurring>
             </finalPhase>
         </plan>
+        <plan name="gas-monthly">
+            <product>Gas</product>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <usages>
+                    <usage name="gas-monthly-in-arrear" billingMode="IN_ARREAR" usageType="CONSUMABLE">
+                        <billingPeriod>MONTHLY</billingPeriod>
+                        <tiers>
+                            <tier>
+                                <blocks>
+                                    <tieredBlock>
+                                        <unit>gallons</unit>
+                                        <size>1</size>
+                                        <prices>
+                                            <price>
+                                                <currency>GBP</currency>
+                                                <value>1.95</value>
+                                            </price>
+                                            <price>
+                                                <currency>EUR</currency>
+                                                <value>2.95</value>
+                                            </price>
+                                            <price>
+                                                <currency>USD</currency>
+                                                <value>3.95</value>
+                                            </price>
+                                            <price>
+                                                <currency>JPY</currency>
+                                                <value>0.95</value>
+                                            </price>
+                                            <price>
+                                                <currency>BTC</currency>
+                                                <value>0.001</value>
+                                            </price>
+                                        </prices>
+                                        <max>100</max>
+                                    </tieredBlock>
+                                </blocks>
+                            </tier>
+                        </tiers>
+                    </usage>
+                </usages>
+            </finalPhase>
+        </plan>
     </plans>
     <priceLists>
         <defaultPriceList name="DEFAULT">
@@ -693,6 +749,7 @@
                 <plan>super-monthly</plan>
                 <plan>remotecontrol-monthly</plan>
                 <plan>oilslick-monthly</plan>
+                <plan>gas-monthly</plan>
             </plans>
         </defaultPriceList>
         <childPriceList name="SpecialDiscount">

currency/pom.xml 20(+18 -2)

diff --git a/currency/pom.xml b/currency/pom.xml
index 6b1e267..1e5edc2 100644
--- a/currency/pom.xml
+++ b/currency/pom.xml
@@ -19,10 +19,9 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
-
     <artifactId>killbill-currency</artifactId>
     <packaging>jar</packaging>
     <name>killbill-currency</name>
@@ -53,6 +52,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
@@ -66,6 +70,14 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-osgi-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
@@ -73,6 +85,10 @@
             <artifactId>killbill-plugin-api-currency</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.skife.config</groupId>
+            <artifactId>config-magic</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
diff --git a/currency/src/main/java/org/killbill/billing/currency/glue/CurrencyModule.java b/currency/src/main/java/org/killbill/billing/currency/glue/CurrencyModule.java
index 48287c8..3865eef 100644
--- a/currency/src/main/java/org/killbill/billing/currency/glue/CurrencyModule.java
+++ b/currency/src/main/java/org/killbill/billing/currency/glue/CurrencyModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,33 +18,28 @@
 
 package org.killbill.billing.currency.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
 import org.killbill.billing.currency.DefaultCurrencyService;
 import org.killbill.billing.currency.api.CurrencyConversionApi;
 import org.killbill.billing.currency.api.CurrencyService;
 import org.killbill.billing.currency.api.DefaultCurrencyConversionApi;
 import org.killbill.billing.currency.plugin.api.CurrencyPluginApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.config.CurrencyConfig;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.AbstractModule;
 import com.google.inject.TypeLiteral;
 
-public class CurrencyModule extends AbstractModule {
+public class CurrencyModule extends KillBillModule {
 
-
-    protected ConfigSource configSource;
-
-    public CurrencyModule(ConfigSource configSource) {
-        this.configSource = configSource;
+    public CurrencyModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     @Override
     protected void configure() {
-
-        final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(configSource);
+        final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(skifeConfigSource);
         final CurrencyConfig currencyConfig = factory.build(CurrencyConfig.class);
         bind(CurrencyConfig.class).toInstance(currencyConfig);
 

entitlement/pom.xml 21(+20 -1)

diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index 429070b..30db7f4 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>
@@ -92,6 +92,25 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-lifecycle</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-subscription</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
index 3c96566..a96d33a 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionApi.java
@@ -103,7 +103,7 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
     @Override
     public Subscription getSubscriptionForEntitlementId(final UUID entitlementId, final TenantContext context) throws SubscriptionApiException {
         final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(entitlementId, ObjectType.SUBSCRIPTION, cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID));
-        final UUID accountId = nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT);
+        final UUID accountId = nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
 
         // Retrieve entitlements
         final AccountEntitlements accountEntitlements;
@@ -128,7 +128,7 @@ public class DefaultSubscriptionApi implements SubscriptionApi {
     @Override
     public SubscriptionBundle getSubscriptionBundle(final UUID bundleId, final TenantContext context) throws SubscriptionApiException {
         final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(bundleId, ObjectType.BUNDLE, cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID));
-        final UUID accountId = nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT);
+        final UUID accountId = nonEntityDao.retrieveIdFromObject(accountRecordId, ObjectType.ACCOUNT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
 
         final Optional<SubscriptionBundle> bundleOptional = Iterables.<SubscriptionBundle>tryFind(getSubscriptionBundlesForAccount(accountId, context),
                                                                                                   new Predicate<SubscriptionBundle>() {
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateModelDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateModelDao.java
index 6305dd1..f69435a 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateModelDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateModelDao.java
@@ -27,8 +27,9 @@ import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.junction.DefaultBlockingState;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class BlockingStateModelDao extends EntityBase implements EntityModelDao<BlockingState>{
+public class BlockingStateModelDao extends EntityModelDaoBase implements EntityModelDao<BlockingState>{
 
     private UUID blockableId;
     private BlockingStateType type;
@@ -40,6 +41,8 @@ public class BlockingStateModelDao extends EntityBase implements EntityModelDao<
     private DateTime effectiveDate;
     private boolean isActive;
 
+    public BlockingStateModelDao() { /* For the DAO mapper */ }
+
     public BlockingStateModelDao(final UUID id, final UUID blockableId, final BlockingStateType blockingStateType, final String state, final String service, final Boolean blockChange, final Boolean blockEntitlement,
                                  final Boolean blockBilling, final DateTime effectiveDate, final boolean isActive, final DateTime createDate, final DateTime updateDate) {
         super(id, createDate, updateDate);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
index bdc1179..c753ccd 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/dao/BlockingStateSqlDao.java
@@ -16,34 +16,24 @@
 
 package org.killbill.billing.entitlement.dao;
 
-import java.sql.ResultSet;
-import java.sql.SQLException;
 import java.util.Date;
 import java.util.List;
 import java.util.UUID;
 
-import org.joda.time.DateTime;
-import org.skife.jdbi.v2.StatementContext;
-import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
-import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
-import org.skife.jdbi.v2.tweak.ResultSetMapper;
-
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.entitlement.api.BlockingState;
-import org.killbill.billing.entitlement.api.BlockingStateType;
-import org.killbill.billing.junction.DefaultBlockingState;
 import org.killbill.billing.util.audit.ChangeType;
-import org.killbill.billing.util.dao.MapperBase;
 import org.killbill.billing.util.entity.dao.Audited;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 
 @EntitySqlDaoStringTemplate
-@RegisterMapper(BlockingStateSqlDao.BlockingHistorySqlMapper.class)
 public interface BlockingStateSqlDao extends EntitySqlDao<BlockingStateModelDao, BlockingState> {
 
     @SqlQuery
@@ -66,37 +56,4 @@ public interface BlockingStateSqlDao extends EntitySqlDao<BlockingStateModelDao,
     @Audited(ChangeType.UPDATE)
     public void unactiveEvent(@Bind("id") String id,
                               @BindBean final InternalCallContext context);
-
-    public class BlockingHistorySqlMapper extends MapperBase implements ResultSetMapper<BlockingStateModelDao> {
-
-        @Override
-        public BlockingStateModelDao map(final int index, final ResultSet r, final StatementContext ctx)
-                throws SQLException {
-
-            final UUID id;
-            final UUID blockableId;
-            final String stateName;
-            final String service;
-            final boolean blockChange;
-            final boolean blockEntitlement;
-            final boolean blockBilling;
-            final boolean isActive;
-            final DateTime effectiveDate;
-            final DateTime createdDate;
-            final BlockingStateType type;
-
-            id = UUID.fromString(r.getString("id"));
-            blockableId = UUID.fromString(r.getString("blockable_id"));
-            stateName = r.getString("state") == null ? DefaultBlockingState.CLEAR_STATE_NAME : r.getString("state");
-            service = r.getString("service");
-            type = BlockingStateType.valueOf(r.getString("type"));
-            blockChange = r.getBoolean("block_change");
-            blockEntitlement = r.getBoolean("block_entitlement");
-            blockBilling = r.getBoolean("block_billing");
-            isActive = r.getBoolean("is_active");
-            effectiveDate = getDateTime(r, "effective_date");
-            createdDate = getDateTime(r, "created_date");
-            return new BlockingStateModelDao(id, blockableId, type, stateName, service, blockChange, blockEntitlement, blockBilling, effectiveDate, isActive, createdDate, createdDate);
-        }
-    }
 }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java b/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
index ba848b3..ca86524 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,13 +21,7 @@ package org.killbill.billing.entitlement;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ObjectType;
-import org.killbill.bus.api.BusEvent;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.entitlement.api.DefaultBlockingTransitionInternalEvent;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
@@ -36,18 +32,25 @@ import org.killbill.billing.entitlement.dao.BlockingStateDao;
 import org.killbill.billing.entitlement.engine.core.BlockingTransitionNotificationKey;
 import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKey;
 import org.killbill.billing.entitlement.engine.core.EntitlementNotificationKeyAction;
-import org.killbill.billing.lifecycle.LifecycleHandlerType;
-import org.killbill.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallOrigin;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.bus.api.BusEvent;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.notificationq.api.NotificationEvent;
 import org.killbill.notificationq.api.NotificationQueue;
 import org.killbill.notificationq.api.NotificationQueueService;
 import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
 import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
 import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueHandler;
-import org.killbill.billing.util.callcontext.CallOrigin;
-import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.callcontext.UserType;
-import org.killbill.billing.util.dao.NonEntityDao;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 
@@ -63,6 +66,7 @@ public class DefaultEntitlementService implements EntitlementService {
     private final PersistentBus eventBus;
     private final NotificationQueueService notificationQueueService;
     private final InternalCallContextFactory internalCallContextFactory;
+    private final CacheControllerDispatcher controllerDispatcher;
 
     private NotificationQueue entitlementEventQueue;
 
@@ -72,13 +76,15 @@ public class DefaultEntitlementService implements EntitlementService {
                                      final NonEntityDao nonEntityDao,
                                      final PersistentBus eventBus,
                                      final NotificationQueueService notificationQueueService,
-                                     final InternalCallContextFactory internalCallContextFactory) {
+                                     final InternalCallContextFactory internalCallContextFactory,
+                                     final CacheControllerDispatcher controllerDispatcher) {
         this.entitlementApi = entitlementApi;
         this.blockingStateDao = blockingStateDao;
         this.nonEntityDao = nonEntityDao;
         this.eventBus = eventBus;
         this.notificationQueueService = notificationQueueService;
         this.internalCallContextFactory = internalCallContextFactory;
+        this.controllerDispatcher = controllerDispatcher;
     }
 
     @Override
@@ -95,7 +101,7 @@ public class DefaultEntitlementService implements EntitlementService {
                     final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "EntitlementQueue", CallOrigin.INTERNAL, UserType.SYSTEM, fromNotificationQueueUserToken);
 
                     if (inputKey instanceof EntitlementNotificationKey) {
-                        final UUID tenantId = nonEntityDao.retrieveIdFromObject(tenantRecordId, ObjectType.TENANT);
+                        final UUID tenantId = nonEntityDao.retrieveIdFromObject(tenantRecordId, ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID));
                         processEntitlementNotification((EntitlementNotificationKey) inputKey, tenantId, internalCallContext);
                     } else if (inputKey instanceof BlockingTransitionNotificationKey) {
                         processBlockingNotification((BlockingTransitionNotificationKey) inputKey, internalCallContext);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index 03b8ba9..192d301 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -27,9 +27,9 @@ import javax.annotation.Nullable;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.Product;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.EntitlementService;
@@ -331,18 +331,22 @@ public class DefaultEventsStream implements EventsStream {
                                                                                                        new Predicate<SubscriptionBase>() {
                                                                                                            @Override
                                                                                                            public boolean apply(final SubscriptionBase subscription) {
-                                                                                                               return ProductCategory.ADD_ON.equals(subscription.getCategory()) &&
-                                                                                                                      // Check the entitlement for that add-on hasn't been cancelled yet
-                                                                                                                      getEntitlementCancellationEvent(subscription.getId()) == null &&
-                                                                                                                      (
-                                                                                                                              // Base subscription cancelled
-                                                                                                                              baseTransitionTriggerNextProduct == null ||
-                                                                                                                              (
-                                                                                                                                      // Change plan - check which add-ons to cancel
-                                                                                                                                      includedAddonsForProduct.contains(subscription.getLastActivePlan().getProduct().getName()) ||
-                                                                                                                                      !availableAddonsForProduct.contains(subscription.getLastActivePlan().getProduct().getName())
-                                                                                                                              )
-                                                                                                                      );
+                                                                                                               final Plan lastActivePlan = subscription.getLastActivePlan();
+                                                                                                               final boolean result = ProductCategory.ADD_ON.equals(subscription.getCategory()) &&
+                                                                                                                                      // Check the subscription started, if not we don't want it, and that way we avoid doing NPE a few lines below.
+                                                                                                                                      lastActivePlan != null &&
+                                                                                                                                      // Check the entitlement for that add-on hasn't been cancelled yet
+                                                                                                                                      getEntitlementCancellationEvent(subscription.getId()) == null &&
+                                                                                                                                      (
+                                                                                                                                              // Base subscription cancelled
+                                                                                                                                              baseTransitionTriggerNextProduct == null ||
+                                                                                                                                              (
+                                                                                                                                                      // Change plan - check which add-ons to cancel
+                                                                                                                                                      includedAddonsForProduct.contains(lastActivePlan.getProduct().getName()) ||
+                                                                                                                                                      !availableAddonsForProduct.contains(subscription.getLastActivePlan().getProduct().getName())
+                                                                                                                                              )
+                                                                                                                                      );
+                                                                                                               return result;
                                                                                                            }
                                                                                                        });
 
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementModule.java b/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementModule.java
index 10d08ba..420073f 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementModule.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/glue/DefaultEntitlementModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,8 +18,6 @@
 
 package org.killbill.billing.entitlement.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.entitlement.DefaultEntitlementService;
 import org.killbill.billing.entitlement.EntitlementInternalApi;
 import org.killbill.billing.entitlement.EntitlementService;
@@ -35,12 +35,13 @@ import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
 import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
 import org.killbill.billing.glue.EntitlementModule;
 import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 
-import com.google.inject.AbstractModule;
-
-public class DefaultEntitlementModule extends AbstractModule implements EntitlementModule {
+public class DefaultEntitlementModule extends KillBillModule implements EntitlementModule {
 
-    public DefaultEntitlementModule(final ConfigSource configSource) {
+    public DefaultEntitlementModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     @Override
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java
index d820e60..b4f0792 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,8 @@
 
 package org.killbill.billing.entitlement;
 
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-
 import org.killbill.billing.GuicyKillbillTestSuiteNoDB;
 import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.bus.api.PersistentBus;
 import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.entitlement.block.BlockingChecker;
 import org.killbill.billing.entitlement.dao.BlockingStateDao;
@@ -31,6 +28,10 @@ import org.killbill.billing.junction.BlockingInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.tag.dao.TagDao;
+import org.killbill.bus.api.PersistentBus;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 
 import com.google.inject.Guice;
 import com.google.inject.Inject;
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
index 333d540..75db4db 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,11 @@
 
 package org.killbill.billing.entitlement;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
-import org.killbill.billing.TestKillbillConfigSource;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.account.api.AccountUserApi;
@@ -39,15 +38,15 @@ import org.killbill.billing.entitlement.engine.core.EntitlementUtils;
 import org.killbill.billing.entitlement.engine.core.EventsStreamBuilder;
 import org.killbill.billing.entitlement.glue.TestEntitlementModuleWithEmbeddedDB;
 import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.mock.MockAccountBuilder;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseService;
 import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseService;
 import org.killbill.billing.tag.TagInternalApi;
-import org.killbill.billing.util.KillbillConfigSource;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.svcsapi.bus.BusService;
 import org.killbill.billing.util.tag.dao.TagDao;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.ClockMock;
@@ -110,8 +109,8 @@ public class EntitlementTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWi
     protected Catalog catalog;
 
     @Override
-    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
-        return new TestKillbillConfigSource("/entitlement.properties");
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/entitlement.properties");
     }
 
     @BeforeClass(groups = "slow")
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModule.java b/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModule.java
index 3a303b1..d607de1 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModule.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,16 +18,15 @@
 
 package org.killbill.billing.entitlement.glue;
 
-import org.skife.config.ConfigSource;
-
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
 
 public class TestEntitlementModule extends DefaultEntitlementModule {
 
-    final protected ConfigSource configSource;
+    protected final KillbillConfigSource configSource;
 
-    public TestEntitlementModule(final ConfigSource configSource) {
+    public TestEntitlementModule(final KillbillConfigSource configSource) {
         super(configSource);
         this.configSource = configSource;
     }
@@ -34,6 +35,6 @@ public class TestEntitlementModule extends DefaultEntitlementModule {
     protected void configure() {
         super.configure();
         install(new CacheModule(configSource));
-        install(new CallContextModule());
+        install(new CallContextModule(configSource));
     }
 }
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModuleNoDB.java b/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModuleNoDB.java
index 2277f26..63fc5b7 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModuleNoDB.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,9 +18,6 @@
 
 package org.killbill.billing.entitlement.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
 import org.killbill.billing.catalog.MockCatalogModule;
 import org.killbill.billing.entitlement.dao.BlockingStateDao;
@@ -27,45 +26,27 @@ import org.killbill.billing.mock.glue.MockAccountModule;
 import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
 import org.killbill.billing.mock.glue.MockSubscriptionModule;
 import org.killbill.billing.mock.glue.MockTagModule;
-import org.killbill.notificationq.MockNotificationQueueService;
-import org.killbill.notificationq.api.NotificationQueueConfig;
-import org.killbill.notificationq.api.NotificationQueueService;
-import org.killbill.billing.util.bus.InMemoryBusModule;
-
-import com.google.common.collect.ImmutableMap;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestEntitlementModuleNoDB extends TestEntitlementModule {
 
-    public TestEntitlementModuleNoDB(final ConfigSource configSource) {
+    public TestEntitlementModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
     @Override
     protected void configure() {
         super.configure();
-        install(new GuicyKillbillTestNoDBModule());
-        install(new MockNonEntityDaoModule());
-        install(new InMemoryBusModule(configSource));
-        install(new MockTagModule());
-        install(new MockSubscriptionModule());
-        install(new MockCatalogModule());
-        install(new MockAccountModule());
-        installNotificationQueue();
+        install(new GuicyKillbillTestNoDBModule(configSource));
+        install(new MockNonEntityDaoModule(configSource));
+        install(new MockTagModule(configSource));
+        install(new MockSubscriptionModule(configSource));
+        install(new MockCatalogModule(configSource));
+        install(new MockAccountModule(configSource));
     }
 
     @Override
     public void installBlockingStateDao() {
         bind(BlockingStateDao.class).to(MockBlockingStateDao.class).asEagerSingleton();
     }
-
-    private void installNotificationQueue() {
-        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
-        configureNotificationQueueConfig();
-    }
-
-    protected void configureNotificationQueueConfig() {
-        final NotificationQueueConfig config = new ConfigurationObjectFactory(configSource).buildWithReplacements(NotificationQueueConfig.class,
-                                                                                                                  ImmutableMap.<String, String>of("instanceName", "main"));
-        bind(NotificationQueueConfig.class).toInstance(config);
-    }
 }
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModuleWithEmbeddedDB.java b/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModuleWithEmbeddedDB.java
index 0413ada..3188699 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModuleWithEmbeddedDB.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/glue/TestEntitlementModuleWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,23 +18,19 @@
 
 package org.killbill.billing.entitlement.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
 import org.killbill.billing.account.glue.DefaultAccountModule;
 import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.catalog.glue.CatalogModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.glue.DefaultSubscriptionModule;
 import org.killbill.billing.util.glue.AuditModule;
-import org.killbill.billing.util.glue.BusModule;
-import org.killbill.billing.util.glue.MetricsModule;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
-import org.killbill.billing.util.glue.NotificationQueueModule;
 import org.killbill.billing.util.glue.TagStoreModule;
 
 public class TestEntitlementModuleWithEmbeddedDB extends TestEntitlementModule {
 
-    public TestEntitlementModuleWithEmbeddedDB(final ConfigSource configSource) {
+    public TestEntitlementModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -40,15 +38,12 @@ public class TestEntitlementModuleWithEmbeddedDB extends TestEntitlementModule {
     protected void configure() {
         super.configure();
         install(new DefaultAccountModule(configSource));
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
-        install(new NonEntityDaoModule());
-        install(new MetricsModule());
-        install(new BusModule(configSource));
-        install(new TagStoreModule());
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
+        install(new NonEntityDaoModule(configSource));
+        install(new TagStoreModule(configSource));
         install(new CatalogModule(configSource));
-        install(new NotificationQueueModule(configSource));
         install(new DefaultSubscriptionModule(configSource));
-        install(new AuditModule());
+        install(new AuditModule(configSource));
 
         bind(TestApiListener.class).asEagerSingleton();
     }

invoice/pom.xml 47(+46 -1)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index dd3b8a2..d68dee7 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>
@@ -31,6 +31,10 @@
             <artifactId>jackson-annotations</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
             <scope>provided</scope>
@@ -94,6 +98,29 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-lifecycle</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-osgi-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-usage</artifactId>
             <scope>test</scope>
         </dependency>
@@ -108,6 +135,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.billing.plugin</groupId>
+            <artifactId>killbill-plugin-api-invoice</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-clock</artifactId>
         </dependency>
@@ -129,7 +160,21 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-jdbi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-locker</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-queue</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-queue</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java
index 0be1fc0..961d3e7 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/DefaultInvoiceService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -20,8 +22,8 @@ import org.killbill.bus.api.PersistentBus;
 import org.killbill.billing.invoice.InvoiceListener;
 import org.killbill.billing.invoice.InvoiceTagHandler;
 import org.killbill.billing.invoice.notification.NextBillingDateNotifier;
-import org.killbill.billing.lifecycle.LifecycleHandlerType;
-import org.killbill.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
 import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
 import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
index 7949310..fc0cbb8 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/invoice/DefaultInvoicePaymentApi.java
@@ -16,27 +16,19 @@
 
 package org.killbill.billing.invoice.api.invoice;
 
-import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
 import org.killbill.billing.ObjectType;
-import org.killbill.billing.invoice.api.Invoice;
-import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentApi;
-import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.invoice.dao.InvoiceDao;
-import org.killbill.billing.invoice.dao.InvoiceModelDao;
 import org.killbill.billing.invoice.dao.InvoicePaymentModelDao;
-import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.invoice.model.DefaultInvoicePayment;
-import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
 
 import com.google.common.base.Function;
-import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
@@ -53,22 +45,6 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     }
 
     @Override
-    public List<Invoice> getAllInvoicesByAccount(final UUID accountId, final TenantContext context) {
-        return ImmutableList.<Invoice>copyOf(Collections2.transform(dao.getAllInvoicesByAccount(internalCallContextFactory.createInternalTenantContext(accountId, context)),
-                                                                    new Function<InvoiceModelDao, Invoice>() {
-                                                                        @Override
-                                                                        public Invoice apply(final InvoiceModelDao input) {
-                                                                            return new DefaultInvoice(input);
-                                                                        }
-                                                                    }));
-    }
-
-    @Override
-    public Invoice getInvoice(final UUID invoiceId, final TenantContext context) throws InvoiceApiException {
-        return new DefaultInvoice(dao.getById(invoiceId, internalCallContextFactory.createInternalTenantContext(invoiceId, ObjectType.INVOICE, context)));
-    }
-
-    @Override
     public List<InvoicePayment> getInvoicePayments(final UUID paymentId, final TenantContext context) {
         return ImmutableList.<InvoicePayment>copyOf(Collections2.transform(dao.getInvoicePayments(paymentId, internalCallContextFactory.createInternalTenantContext(paymentId, ObjectType.PAYMENT, context)),
                                                                            new Function<InvoicePaymentModelDao, InvoicePayment>() {
@@ -76,67 +52,19 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
                                                                                public InvoicePayment apply(final InvoicePaymentModelDao input) {
                                                                                    return new DefaultInvoicePayment(input);
                                                                                }
-                                                                           }));
-    }
-
-    @Override
-    public InvoicePayment getInvoicePaymentForAttempt(final UUID paymentId, final TenantContext context) {
-        final List<InvoicePayment> invoicePayments = getInvoicePayments(paymentId, context);
-        if (invoicePayments.size() == 0) {
-            return null;
-        }
-        return Collections2.filter(invoicePayments, new Predicate<InvoicePayment>() {
-            @Override
-            public boolean apply(final InvoicePayment input) {
-                return input.getType() == InvoicePaymentType.ATTEMPT;
-            }
-        }).iterator().next();
-    }
-
-    @Override
-    public BigDecimal getRemainingAmountPaid(final UUID invoicePaymentId, final TenantContext context) {
-        return dao.getRemainingAmountPaid(invoicePaymentId, internalCallContextFactory.createInternalTenantContext(invoicePaymentId, ObjectType.INVOICE_PAYMENT, context));
-    }
-
-    @Override
-    public List<InvoicePayment> getChargebacksByAccountId(final UUID accountId, final TenantContext context) {
-        return ImmutableList.<InvoicePayment>copyOf(Collections2.transform(dao.getChargebacksByAccountId(accountId, internalCallContextFactory.createInternalTenantContext(accountId, context)),
-                                                                           new Function<InvoicePaymentModelDao, InvoicePayment>() {
-                                                                               @Override
-                                                                               public InvoicePayment apply(final InvoicePaymentModelDao input) {
-                                                                                   return new DefaultInvoicePayment(input);
-                                                                               }
-                                                                           }));
+                                                                           }
+                                                                          ));
     }
 
     @Override
-    public List<InvoicePayment> getChargebacksByPaymentId(final UUID paymentId, final TenantContext context) {
-        return ImmutableList.<InvoicePayment>copyOf(Collections2.transform(dao.getChargebacksByPaymentId(paymentId, internalCallContextFactory.createInternalTenantContext(paymentId, ObjectType.PAYMENT, context)),
+    public List<InvoicePayment> getInvoicePaymentsByAccount(final UUID accountId, final TenantContext context) {
+        return ImmutableList.<InvoicePayment>copyOf(Collections2.transform(dao.getInvoicePaymentsByAccount(internalCallContextFactory.createInternalTenantContext(accountId, ObjectType.ACCOUNT, context)),
                                                                            new Function<InvoicePaymentModelDao, InvoicePayment>() {
                                                                                @Override
                                                                                public InvoicePayment apply(final InvoicePaymentModelDao input) {
                                                                                    return new DefaultInvoicePayment(input);
                                                                                }
-                                                                           }));
-    }
-
-    @Override
-    public InvoicePayment getChargebackById(final UUID chargebackId, final TenantContext context) throws InvoiceApiException {
-        return new DefaultInvoicePayment(dao.getChargebackById(chargebackId, internalCallContextFactory.createInternalTenantContext(chargebackId, ObjectType.INVOICE_PAYMENT, context)));
-    }
-
-    @Override
-    public UUID getAccountIdFromInvoicePaymentId(final UUID invoicePaymentId, final TenantContext context) throws InvoiceApiException {
-        return dao.getAccountIdFromInvoicePaymentId(invoicePaymentId, internalCallContextFactory.createInternalTenantContext(invoicePaymentId, ObjectType.INVOICE_PAYMENT, context));
-    }
-
-    @Override
-    public InvoicePayment createChargeback(final UUID invoicePaymentId, final CallContext context) throws InvoiceApiException {
-        return createChargeback(invoicePaymentId, null, context);
-    }
-
-    @Override
-    public InvoicePayment createChargeback(final UUID invoicePaymentId, final BigDecimal amount, final CallContext context) throws InvoiceApiException {
-        return new DefaultInvoicePayment(dao.postChargeback(invoicePaymentId, amount, internalCallContextFactory.createInternalCallContext(invoicePaymentId, ObjectType.INVOICE_PAYMENT, context)));
+                                                                           }
+                                                                          ));
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java
index a9a8a42..57ef684 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/migration/DefaultInvoiceMigrationApi.java
@@ -34,7 +34,6 @@ import org.killbill.billing.invoice.api.InvoiceMigrationApi;
 import org.killbill.billing.invoice.dao.DefaultInvoiceDao;
 import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
 import org.killbill.billing.invoice.dao.InvoiceModelDao;
-import org.killbill.billing.invoice.dao.InvoicePaymentModelDao;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.account.api.AccountInternalApi;
@@ -74,10 +73,10 @@ public class DefaultInvoiceMigrationApi implements InvoiceMigrationApi {
 
         final InvoiceModelDao migrationInvoice = new InvoiceModelDao(accountId, clock.getUTCToday(), targetDate, currency, true);
         final InvoiceItemModelDao migrationInvoiceItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.FIXED, migrationInvoice.getId(), accountId, null, null,
-                                                                                 MigrationPlan.MIGRATION_PLAN_NAME, MigrationPlan.MIGRATION_PLAN_PHASE_NAME, null,
+                                                                                 null, MigrationPlan.MIGRATION_PLAN_NAME, MigrationPlan.MIGRATION_PLAN_PHASE_NAME, null,
                                                                                  targetDate, null, balance, null, currency, null);
         dao.createInvoice(migrationInvoice, ImmutableList.<InvoiceItemModelDao>of(migrationInvoiceItem),
-                          ImmutableList.<InvoicePaymentModelDao>of(), true, ImmutableMap.<UUID, List<DateTime>>of(), internalCallContextFactory.createInternalCallContext(accountId, context));
+                          true, ImmutableMap.<UUID, List<DateTime>>of(), internalCallContextFactory.createInternalCallContext(accountId, context));
 
         return migrationInvoice.getId();
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
index 611b9d4..3ac0eb6 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/svcs/DefaultInvoiceInternalApi.java
@@ -56,6 +56,7 @@ import org.killbill.billing.util.timezone.DateAndTimeZoneContext;
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
 
 public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
 
@@ -109,21 +110,17 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
 
     @Override
     public InvoicePayment getInvoicePaymentForAttempt(final UUID paymentId, final InternalTenantContext context) throws InvoiceApiException {
-        final Collection<InvoicePayment> invoicePayments = Collections2.transform(dao.getInvoicePayments(paymentId, context), new Function<InvoicePaymentModelDao, InvoicePayment>() {
-            @Override
-            public InvoicePayment apply(final InvoicePaymentModelDao input) {
-                return new DefaultInvoicePayment(input);
-            }
-        });
-        if (invoicePayments.size() == 0) {
-            return null;
-        }
-        return Collections2.filter(invoicePayments, new Predicate<InvoicePayment>() {
-            @Override
-            public boolean apply(final InvoicePayment input) {
-                return input.getType() == InvoicePaymentType.ATTEMPT;
-            }
-        }).iterator().next();
+        return getInvoicePayment(paymentId, InvoicePaymentType.ATTEMPT, context);
+    }
+
+    @Override
+    public InvoicePayment getInvoicePaymentForRefund(final UUID paymentId, final InternalTenantContext context) throws InvoiceApiException {
+        return getInvoicePayment(paymentId, InvoicePaymentType.REFUND, context);
+    }
+
+    @Override
+    public InvoicePayment getInvoicePaymentForChargeback(final UUID paymentId, final InternalTenantContext context) throws InvoiceApiException {
+        return getInvoicePayment(paymentId, InvoicePaymentType.CHARGED_BACK, context);
     }
 
     @Override
@@ -133,11 +130,16 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
     }
 
     @Override
-    public InvoicePayment createRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final UUID paymentCookieId, final InternalCallContext context) throws InvoiceApiException {
+    public InvoicePayment createRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final String transactionExternalKey, final InternalCallContext context) throws InvoiceApiException {
         if (amount.compareTo(BigDecimal.ZERO) <= 0) {
-            throw new InvoiceApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL);
+            throw new InvoiceApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL, paymentId, amount);
         }
-        return new DefaultInvoicePayment(dao.createRefund(paymentId, amount, isInvoiceAdjusted, invoiceItemIdsWithAmounts, paymentCookieId, context));
+        return new DefaultInvoicePayment(dao.createRefund(paymentId, amount, isInvoiceAdjusted, invoiceItemIdsWithAmounts, transactionExternalKey, context));
+    }
+
+    @Override
+    public InvoicePayment createChargeback(final UUID paymentId, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
+        return new DefaultInvoicePayment(dao.postChargeback(paymentId, amount, currency, context));
     }
 
     @Override
@@ -145,4 +147,21 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
         dao.consumeExstingCBAOnAccountWithUnpaidInvoices(accountId, context);
     }
 
+    private InvoicePayment getInvoicePayment(final UUID paymentId, final InvoicePaymentType type, final InternalTenantContext context) throws InvoiceApiException {
+        final Collection<InvoicePayment> invoicePayments = Collections2.transform(dao.getInvoicePayments(paymentId, context), new Function<InvoicePaymentModelDao, InvoicePayment>() {
+            @Override
+            public InvoicePayment apply(final InvoicePaymentModelDao input) {
+                return new DefaultInvoicePayment(input);
+            }
+        });
+        if (invoicePayments.size() == 0) {
+            return null;
+        }
+        return Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
+            @Override
+            public boolean apply(final InvoicePayment input) {
+                return input.getType() == type;
+            }
+        }).orNull();
+    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
index a63f8d7..34474c8 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -25,16 +25,11 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
@@ -50,6 +45,7 @@ import org.killbill.billing.invoice.model.CreditAdjInvoiceItem;
 import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
 import org.killbill.billing.invoice.model.InvoiceItemFactory;
+import org.killbill.billing.invoice.template.HtmlInvoice;
 import org.killbill.billing.invoice.template.HtmlInvoiceGenerator;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.api.TagApiException;
@@ -60,10 +56,16 @@ import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.inject.Inject;
 
 import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEntityPaginationNoException;
@@ -115,6 +117,17 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
+    public Invoice getInvoiceByPayment(final UUID paymentId, final TenantContext context) throws InvoiceApiException {
+        final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        final UUID invoiceId = dao.getInvoiceIdByPaymentId(paymentId, tenantContext);
+        if (invoiceId == null) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_NOT_FOUND, invoiceId);
+        }
+        final InvoiceModelDao invoiceModelDao = invoiceId != null ? dao.getById(invoiceId, tenantContext) : null;
+        return new DefaultInvoice(invoiceModelDao);
+    }
+
+    @Override
     public Pagination<Invoice> getInvoices(final Long offset, final Long limit, final TenantContext context) {
         return getEntityPaginationNoException(limit,
                                               new SourcePaginationBuilder<InvoiceModelDao, InvoiceApiException>() {
@@ -244,33 +257,33 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
-    public InvoiceItem insertExternalCharge(final UUID accountId, final BigDecimal amount, @Nullable final String description,
-                                            final LocalDate effectiveDate, final Currency currency, final CallContext context) throws InvoiceApiException {
-        return insertExternalChargeForInvoiceAndBundle(accountId, null, null, amount, description, effectiveDate, currency, context);
-    }
-
-    @Override
-    public InvoiceItem insertExternalChargeForBundle(final UUID accountId, final UUID bundleId, final BigDecimal amount, @Nullable final String description,
-                                                     final LocalDate effectiveDate, final Currency currency, final CallContext context) throws InvoiceApiException {
-        return insertExternalChargeForInvoiceAndBundle(accountId, null, bundleId, amount, description, effectiveDate, currency, context);
-    }
-
-    @Override
-    public InvoiceItem insertExternalChargeForInvoice(final UUID accountId, final UUID invoiceId, final BigDecimal amount, @Nullable final String description,
-                                                      final LocalDate effectiveDate, final Currency currency, final CallContext context) throws InvoiceApiException {
-        return insertExternalChargeForInvoiceAndBundle(accountId, invoiceId, null, amount, description, effectiveDate, currency, context);
-    }
-
-    @Override
-    public InvoiceItem insertExternalChargeForInvoiceAndBundle(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId,
-                                                               final BigDecimal amount, @Nullable final String description, final LocalDate effectiveDate,
-                                                               final Currency currency, final CallContext context) throws InvoiceApiException {
-        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
-            throw new InvoiceApiException(ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID, amount);
+    public List<InvoiceItem> insertExternalCharges(final UUID accountId, final LocalDate effectiveDate, final Iterable<InvoiceItem> charges, final CallContext context) throws InvoiceApiException {
+        for (final InvoiceItem charge : charges) {
+            if (charge.getAmount() == null || charge.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+                throw new InvoiceApiException(ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID, charge.getAmount());
+            }
         }
 
-        final InvoiceItemModelDao externalCharge = dao.insertExternalCharge(accountId, invoiceId, bundleId, description, amount, effectiveDate, currency, internalCallContextFactory.createInternalCallContext(accountId, context));
-        return InvoiceItemFactory.fromModelDao(externalCharge);
+        final Iterable<InvoiceItemModelDao> chargesModelDao = Iterables.<InvoiceItem, InvoiceItemModelDao>transform(charges,
+                                                                                                                    new Function<InvoiceItem, InvoiceItemModelDao>() {
+                                                                                                                        @Nullable
+                                                                                                                        @Override
+                                                                                                                        public InvoiceItemModelDao apply(final InvoiceItem externalCharge) {
+                                                                                                                            return new InvoiceItemModelDao(externalCharge);
+                                                                                                                        }
+                                                                                                                    }
+                                                                                                                   );
+        final List<InvoiceItemModelDao> externalCharges = dao.insertExternalCharges(accountId, effectiveDate, chargesModelDao, internalCallContextFactory.createInternalCallContext(accountId, context));
+
+        return Lists.<InvoiceItemModelDao, InvoiceItem>transform(externalCharges,
+                                                                 new Function<InvoiceItemModelDao, InvoiceItem>() {
+                                                                     @Nullable
+                                                                     @Override
+                                                                     public InvoiceItem apply(@Nullable final InvoiceItemModelDao externalCharge) {
+                                                                         return InvoiceItemFactory.fromModelDao(externalCharge);
+                                                                     }
+                                                                 }
+                                                                );
     }
 
     @Override
@@ -344,7 +357,8 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
             }
         }
 
-        return generator.generateInvoice(account, invoice, manualPay);
+        HtmlInvoice htmlInvoice = generator.generateInvoice(account, invoice, manualPay);
+        return htmlInvoice.getBody();
     }
 
     @Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java
index 472b796..e815d1f 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/calculator/InvoiceCalculatorUtils.java
@@ -58,7 +58,8 @@ public abstract class InvoiceCalculatorUtils {
 
     // Regular line item (charges)
     public static boolean isCharge(final InvoiceItem invoiceItem) {
-        return InvoiceItemType.EXTERNAL_CHARGE.equals(invoiceItem.getInvoiceItemType()) ||
+        return InvoiceItemType.TAX.equals(invoiceItem.getInvoiceItemType()) ||
+               InvoiceItemType.EXTERNAL_CHARGE.equals(invoiceItem.getInvoiceItemType()) ||
                InvoiceItemType.FIXED.equals(invoiceItem.getInvoiceItemType()) ||
                InvoiceItemType.USAGE.equals(invoiceItem.getInvoiceItemType()) ||
                InvoiceItemType.RECURRING.equals(invoiceItem.getInvoiceItemType());
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index 782a04c..1f574e0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -17,7 +17,11 @@
 package org.killbill.billing.invoice.dao;
 
 import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -26,17 +30,10 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-import org.skife.jdbi.v2.IDBI;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ErrorCode;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.clock.Clock;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItemType;
@@ -53,8 +50,16 @@ import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.clock.Clock;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -122,6 +127,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         });
     }
 
+
     @Override
     public List<InvoiceModelDao> getAllInvoicesByAccount(final InternalTenantContext context) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceModelDao>>() {
@@ -201,7 +207,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
     @Override
     public void createInvoice(final InvoiceModelDao invoice, final List<InvoiceItemModelDao> invoiceItems,
-                              final List<InvoicePaymentModelDao> invoicePayments, final boolean isRealInvoice, final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions,
+                              final boolean isRealInvoice, final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions,
                               final InternalCallContext context) {
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
@@ -225,11 +231,6 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     cbaDao.doCBAComplexity(invoice.getAccountId(), entitySqlDaoWrapperFactory, context);
 
                     notifyOfFutureBillingEvents(entitySqlDaoWrapperFactory, invoice.getAccountId(), callbackDateTimePerSubscriptions, context.getUserToken());
-
-                    // Create associated payments
-                    final InvoicePaymentSqlDao invoicePaymentSqlDao = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
-                    invoicePaymentSqlDao.batchCreateFromTransaction(invoicePayments, context);
-
                 }
                 return null;
             }
@@ -343,8 +344,18 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     }
 
     @Override
+    public List<InvoicePaymentModelDao> getInvoicePaymentsByAccount(final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoicePaymentModelDao>>() {
+            @Override
+            public List<InvoicePaymentModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class).getByAccountRecordId(context);
+            }
+        });
+    }
+
+    @Override
     public InvoicePaymentModelDao createRefund(final UUID paymentId, final BigDecimal requestedRefundAmount, final boolean isInvoiceAdjusted,
-                                               final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts, final UUID paymentCookieId,
+                                               final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts, final String transactionExternalKey,
                                                final InternalCallContext context) throws InvoiceApiException {
         final boolean isInvoiceItemAdjusted = isInvoiceAdjusted && invoiceItemIdsWithNullAmounts.size() > 0;
 
@@ -355,7 +366,13 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 final InvoiceSqlDao transInvoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
 
-                final InvoicePaymentModelDao payment = transactional.getByPaymentId(paymentId.toString(), context);
+                final List<InvoicePaymentModelDao> paymentsForId = transactional.getByPaymentId(paymentId.toString(), context);
+                final InvoicePaymentModelDao payment = Iterables.tryFind(paymentsForId, new Predicate<InvoicePaymentModelDao>() {
+                    @Override
+                    public boolean apply(final InvoicePaymentModelDao input) {
+                        return input.getType() == InvoicePaymentType.ATTEMPT;
+                    }
+                }).orNull();
                 if (payment == null) {
                     throw new InvoiceApiException(ErrorCode.INVOICE_PAYMENT_BY_ATTEMPT_NOT_FOUND, paymentId);
                 }
@@ -371,7 +388,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 // Before we go further, check if that refund already got inserted -- the payment system keeps a state machine
                 // and so this call may be called several time for the same  paymentCookieId (which is really the refundId)
-                final InvoicePaymentModelDao existingRefund = transactional.getPaymentsForCookieId(paymentCookieId.toString(), context);
+                final InvoicePaymentModelDao existingRefund = transactional.getPaymentsForCookieId(transactionExternalKey, context);
                 if (existingRefund != null) {
                     return existingRefund;
                 }
@@ -379,7 +396,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 final InvoicePaymentModelDao refund = new InvoicePaymentModelDao(UUID.randomUUID(), context.getCreatedDate(), InvoicePaymentType.REFUND,
                                                                                  payment.getInvoiceId(), paymentId,
                                                                                  context.getCreatedDate(), requestedPositiveAmount.negate(),
-                                                                                 payment.getCurrency(), payment.getProcessedCurrency(), paymentCookieId, payment.getId());
+                                                                                 payment.getCurrency(), payment.getProcessedCurrency(), transactionExternalKey, payment.getId());
                 transactional.create(refund, context);
 
                 // Retrieve invoice after the Refund
@@ -402,7 +419,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     final BigDecimal requestedPositiveAmountToAdjust = requestedPositiveAmount.compareTo(maxBalanceToAdjust) > 0 ? maxBalanceToAdjust : requestedPositiveAmount;
                     if (requestedPositiveAmountToAdjust.compareTo(BigDecimal.ZERO) > 0) {
                         final InvoiceItemModelDao adjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.REFUND_ADJ, invoice.getId(), invoice.getAccountId(),
-                                                                                    null, null, null, null, null, context.getCreatedDate().toLocalDate(), null,
+                                                                                    null, null, null, null, null, null, context.getCreatedDate().toLocalDate(), null,
                                                                                     requestedPositiveAmountToAdjust.negate(), null, invoice.getCurrency(), null);
                         transInvoiceItemDao.create(adjItem, context);
                     }
@@ -428,12 +445,27 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     }
 
     @Override
-    public InvoicePaymentModelDao postChargeback(final UUID invoicePaymentId, final BigDecimal amount, final InternalCallContext context) throws InvoiceApiException {
+    public InvoicePaymentModelDao postChargeback(final UUID paymentId, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
         return transactionalSqlDao.execute(InvoiceApiException.class, new EntitySqlDaoTransactionWrapper<InvoicePaymentModelDao>() {
             @Override
             public InvoicePaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
                 final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
 
+                final List<InvoicePaymentModelDao> invoicePayments = transactional.getByPaymentId(paymentId.toString(), context);
+                final InvoicePaymentModelDao invoicePayment = Iterables.tryFind(invoicePayments, new Predicate<InvoicePaymentModelDao>() {
+                    @Override
+                    public boolean apply(final InvoicePaymentModelDao input) {
+                        return input.getType() == InvoicePaymentType.ATTEMPT;
+                    }
+                }).orNull();
+                if (invoicePayment == null) {
+                    throw new InvoiceApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
+                }
+                // We expect the code to correctly pass the account currency -- the payment code, more generic accept chargeBack in different currencies,
+                // but this is only for direct payment (no invoice)
+                Preconditions.checkArgument(invoicePayment.getCurrency() == currency);
+
+                final UUID invoicePaymentId = invoicePayment.getId();
                 final BigDecimal maxChargedBackAmount = invoiceDaoHelper.getRemainingAmountPaidFromTransaction(invoicePaymentId, entitySqlDaoWrapperFactory, context);
                 final BigDecimal requestedChargedBackAmount = (amount == null) ? maxChargedBackAmount : amount;
                 if (requestedChargedBackAmount.compareTo(BigDecimal.ZERO) <= 0) {
@@ -545,44 +577,81 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
             public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class).create(invoicePayment, context);
+                final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
+                final List<InvoicePaymentModelDao> invoicePayments = transactional.getInvoicePayments(invoicePayment.getPaymentId().toString(), context);
+                final InvoicePaymentModelDao existingAttempt = Iterables.tryFind(invoicePayments, new Predicate<InvoicePaymentModelDao>() {
+                    @Override
+                    public boolean apply(final InvoicePaymentModelDao input) {
+                        return input.getType() == InvoicePaymentType.ATTEMPT;
+                    }
+                }).orNull();
+                if (existingAttempt == null) {
+                    transactional.create(invoicePayment, context);
+                }
                 return null;
             }
         });
     }
 
     @Override
-    public InvoiceItemModelDao insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId, final String description,
-                                                    final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final InternalCallContext context)
+    public List<InvoiceItemModelDao> insertExternalCharges(final UUID accountId, final LocalDate effectiveDate,
+                                                           final Iterable<InvoiceItemModelDao> charges, final InternalCallContext context)
             throws InvoiceApiException {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<InvoiceItemModelDao>() {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<InvoiceItemModelDao>>() {
             @Override
-            public InvoiceItemModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                final InvoiceSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
+            public List<InvoiceItemModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                final InvoiceSqlDao transInvoiceDao = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
+                final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
 
-                UUID invoiceIdForExternalCharge = invoiceId;
-                // Create an invoice for that external charge if it doesn't exist
-                if (invoiceIdForExternalCharge == null) {
-                    final InvoiceModelDao invoiceForExternalCharge = new InvoiceModelDao(accountId, effectiveDate, effectiveDate, currency);
-                    transactional.create(invoiceForExternalCharge, context);
-                    invoiceIdForExternalCharge = invoiceForExternalCharge.getId();
-                }
+                // Group all new external charges on the same invoice (per currency)
+                final Map<Currency, InvoiceModelDao> newInvoicesForExternalCharges = new HashMap<Currency, InvoiceModelDao>();
+
+                final List<InvoiceItemModelDao> createdExternalCharges = new LinkedList<InvoiceItemModelDao>();
+                final Collection<UUID> changedInvoices = new HashSet<UUID>();
+                for (final InvoiceItemModelDao charge : charges) {
+                    UUID invoiceIdForExternalCharge = charge.getInvoiceId();
+                    // Create an invoice for that external charge if it doesn't exist
+                    if (invoiceIdForExternalCharge == null) {
+                        final Currency currency = charge.getCurrency();
+                        if (newInvoicesForExternalCharges.get(currency) == null) {
+                            final InvoiceModelDao newInvoiceForExternalCharge = new InvoiceModelDao(accountId, effectiveDate, effectiveDate, currency);
+                            transInvoiceDao.create(newInvoiceForExternalCharge, context);
+                            newInvoicesForExternalCharges.put(currency, newInvoiceForExternalCharge);
+                        }
+                        invoiceIdForExternalCharge = newInvoicesForExternalCharges.get(currency).getId();
+                    }
 
-                final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.EXTERNAL_CHARGE,
-                                                                                   invoiceIdForExternalCharge, accountId,
-                                                                                   bundleId, null, description, null, null,
-                                                                                   effectiveDate, null, amount, null,
-                                                                                   currency, null);
-                final InvoiceItemSqlDao transInvoiceItemDao = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
-                transInvoiceItemDao.create(externalCharge, context);
+                    final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(context.getCreatedDate(),
+                                                                                       InvoiceItemType.EXTERNAL_CHARGE,
+                                                                                       invoiceIdForExternalCharge,
+                                                                                       accountId,
+                                                                                       charge.getBundleId(),
+                                                                                       null,
+                                                                                       charge.getDescription(),
+                                                                                       null,
+                                                                                       null,
+                                                                                       null,
+                                                                                       Objects.firstNonNull(charge.getStartDate(), effectiveDate),
+                                                                                       charge.getEndDate(),
+                                                                                       charge.getAmount(),
+                                                                                       null,
+                                                                                       charge.getCurrency(),
+                                                                                       null);
+                    transInvoiceItemDao.create(externalCharge, context);
+
+                    createdExternalCharges.add(externalCharge);
+                    changedInvoices.add(invoiceIdForExternalCharge);
+                }
 
                 cbaDao.doCBAComplexity(accountId, entitySqlDaoWrapperFactory, context);
 
                 // Notify the bus since the balance of the invoice changed
                 // TODO should we post an InvoiceCreationInternalEvent event instead? Note! This will trigger a payment (see InvoiceHandler)
-                notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceId, accountId, context.getUserToken(), context);
+                for (final UUID invoiceId : changedInvoices) {
+                    notifyBusOfInvoiceAdjustment(entitySqlDaoWrapperFactory, invoiceId, accountId, context.getUserToken(), context);
+                }
 
-                return externalCharge;
+                return createdExternalCharges;
             }
         });
     }
@@ -620,7 +689,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 // Note! The amount is negated here!
                 final InvoiceItemModelDao credit = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.CREDIT_ADJ, invoiceIdForCredit,
-                                                                           accountId, null, null, null, null, null, effectiveDate,
+                                                                           accountId, null, null, null, null, null, null, effectiveDate,
                                                                            null, positiveCreditAmount.negate(), null,
                                                                            currency, null);
                 invoiceDaoHelper.insertItem(entitySqlDaoWrapperFactory, credit, context);
@@ -677,8 +746,8 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                 // First, adjust the same invoice with the CBA amount to "delete"
                 final InvoiceItemModelDao cbaAdjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.CBA_ADJ, invoice.getId(), invoice.getAccountId(),
-                                                                               null, null, null, null, null, context.getCreatedDate().toLocalDate(),
-                                                                               null, cbaItem.getAmount().negate(), null,  cbaItem.getCurrency(), cbaItem.getId());
+                                                                               null, null, null, null, null, null, context.getCreatedDate().toLocalDate(),
+                                                                               null, cbaItem.getAmount().negate(), null, cbaItem.getCurrency(), cbaItem.getId());
                 invoiceItemSqlDao.create(cbaAdjItem, context);
 
                 // Verify the final invoice balance is not negative
@@ -733,7 +802,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
 
                         // Add the adjustment on that invoice
                         final InvoiceItemModelDao nextCBAAdjItem = new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.CBA_ADJ, invoiceFollowing.getId(),
-                                                                                           invoice.getAccountId(), null, null, null, null, null,
+                                                                                           invoice.getAccountId(), null, null, null, null, null, null,
                                                                                            context.getCreatedDate().toLocalDate(), null,
                                                                                            positiveCBAAdjItemAmount, null, cbaItem.getCurrency(), cbaItem.getId());
                         invoiceItemSqlDao.create(nextCBAAdjItem, context);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
index 5adb05e..69cdf91 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDao.java
@@ -25,7 +25,6 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
@@ -36,8 +35,9 @@ import org.killbill.billing.util.entity.dao.EntityDao;
 
 public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceApiException> {
 
-    void createInvoice(InvoiceModelDao invoice, List<InvoiceItemModelDao> invoiceItems,
-                       List<InvoicePaymentModelDao> invoicePayments, boolean isRealInvoice, final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions, InternalCallContext context);
+    void createInvoice(final InvoiceModelDao invoice, final List<InvoiceItemModelDao> invoiceItems,
+                       final boolean isRealInvoice, final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions,
+                       final InternalCallContext context);
 
     InvoiceModelDao getByNumber(Integer number, InternalTenantContext context) throws InvoiceApiException;
 
@@ -53,6 +53,8 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
 
     List<InvoicePaymentModelDao> getInvoicePayments(UUID paymentId, InternalTenantContext context);
 
+    List<InvoicePaymentModelDao> getInvoicePaymentsByAccount(InternalTenantContext context);
+
     BigDecimal getAccountBalance(UUID accountId, InternalTenantContext context);
 
     public BigDecimal getAccountCBA(UUID accountId, InternalTenantContext context);
@@ -62,7 +64,7 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
     // Include migrated invoices
     List<InvoiceModelDao> getAllInvoicesByAccount(InternalTenantContext context);
 
-    InvoicePaymentModelDao postChargeback(UUID invoicePaymentId, BigDecimal amount, InternalCallContext context) throws InvoiceApiException;
+    InvoicePaymentModelDao postChargeback(UUID paymentId, BigDecimal amount, Currency currency, InternalCallContext context) throws InvoiceApiException;
 
     /**
      * Create a refund.
@@ -71,13 +73,13 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
      * @param amount                    amount to refund
      * @param isInvoiceAdjusted         whether the refund should trigger an invoice or invoice item adjustment
      * @param invoiceItemIdsWithAmounts invoice item ids and associated amounts to adjust
-     * @param paymentCookieId           payment cookie id
+     * @param transactionExternalKey    transaction refund externalKey
      * @param context                   the call callcontext
      * @return the created invoice payment object associated with this refund
      * @throws InvoiceApiException
      */
     InvoicePaymentModelDao createRefund(UUID paymentId, BigDecimal amount, boolean isInvoiceAdjusted, Map<UUID, BigDecimal> invoiceItemIdsWithAmounts,
-                                        UUID paymentCookieId, InternalCallContext context) throws InvoiceApiException;
+                                        String transactionExternalKey, InternalCallContext context) throws InvoiceApiException;
 
     BigDecimal getRemainingAmountPaid(UUID invoicePaymentId, InternalTenantContext context);
 
@@ -99,20 +101,15 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
     InvoiceItemModelDao getExternalChargeById(UUID externalChargeId, InternalTenantContext context) throws InvoiceApiException;
 
     /**
-     * Add an external charge to a given account and invoice. If invoiceId is null, a new invoice will be created.
+     * Add one or more external charges to a given account.
      *
      * @param accountId     the account id
-     * @param invoiceId     the invoice id
-     * @param bundleId      the bundle id
-     * @param description   a description for that charge
-     * @param amount        the external charge amount
-     * @param effectiveDate the day to post the external charge, in the account timezone
-     * @param currency      the external charge currency
-     * @param context       the call callcontext
-     * @return the newly created external charge invoice item
+     * @param effectiveDate the effective date for newly created invoices (in the account timezone)
+     * @param charges       the external charges
+     * @param context       the call context
+     * @return the newly created external charges invoice items
      */
-    InvoiceItemModelDao insertExternalCharge(UUID accountId, @Nullable UUID invoiceId, @Nullable UUID bundleId, @Nullable String description,
-                                             BigDecimal amount, LocalDate effectiveDate, Currency currency, InternalCallContext context) throws InvoiceApiException;
+    List<InvoiceItemModelDao> insertExternalCharges(UUID accountId, LocalDate effectiveDate, Iterable<InvoiceItemModelDao> charges, InternalCallContext context) throws InvoiceApiException;
 
     /**
      * Retrieve a credit by id.
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
index 00290d1..726dda2 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceDaoHelper.java
@@ -203,7 +203,7 @@ public class InvoiceDaoHelper {
         // Finally, create the adjustment
         // Note! The amount is negated here!
         return new InvoiceItemModelDao(context.getCreatedDate(), InvoiceItemType.ITEM_ADJ, invoiceItemToBeAdjusted.getInvoiceId(), invoiceItemToBeAdjusted.getAccountId(),
-                                       null, null, null, null, null, effectiveDate, effectiveDate, amountToAdjust.negate(), null, currencyForAdjustment, invoiceItemToBeAdjusted.getId());
+                                       null, null, null, null, null, null, effectiveDate, effectiveDate, amountToAdjust.negate(), null, currencyForAdjustment, invoiceItemToBeAdjusted.getId());
     }
 
     /**
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
index 84177e8..e7a3a2a 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceItemModelDao.java
@@ -21,21 +21,22 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.util.dao.TableName;
-import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<InvoiceItem> {
+public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityModelDao<InvoiceItem> {
 
     private InvoiceItemType type;
     private UUID invoiceId;
     private UUID accountId;
     private UUID bundleId;
     private UUID subscriptionId;
+    private String description;
     private String planName;
     private String phaseName;
     private String usageName;
@@ -49,7 +50,7 @@ public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<In
     public InvoiceItemModelDao() { /* For the DAO mapper */ }
 
     public InvoiceItemModelDao(final UUID id, final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId,
-                               final UUID accountId, final UUID bundleId, final UUID subscriptionId, final String planName,
+                               final UUID accountId, final UUID bundleId, final UUID subscriptionId, final String description, final String planName,
                                final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
                                final BigDecimal rate, final Currency currency, final UUID linkedItemId) {
         super(id, createdDate, createdDate);
@@ -58,6 +59,7 @@ public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<In
         this.accountId = accountId;
         this.bundleId = bundleId;
         this.subscriptionId = subscriptionId;
+        this.description = description;
         this.planName = planName;
         this.phaseName = phaseName;
         this.usageName = usageName;
@@ -70,16 +72,16 @@ public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<In
     }
 
     public InvoiceItemModelDao(final DateTime createdDate, final InvoiceItemType type, final UUID invoiceId, final UUID accountId,
-                               final UUID bundleId, final UUID subscriptionId, final String planName,
+                               final UUID bundleId, final UUID subscriptionId, final String description, final String planName,
                                final String phaseName, final String usageName, final LocalDate startDate, final LocalDate endDate, final BigDecimal amount,
                                final BigDecimal rate, final Currency currency, final UUID linkedItemId) {
-        this(UUID.randomUUID(), createdDate, type, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName,
+        this(UUID.randomUUID(), createdDate, type, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName,
              startDate, endDate, amount, rate, currency, linkedItemId);
     }
 
     public InvoiceItemModelDao(final InvoiceItem invoiceItem) {
         this(invoiceItem.getId(), invoiceItem.getCreatedDate(), invoiceItem.getInvoiceItemType(), invoiceItem.getInvoiceId(), invoiceItem.getAccountId(), invoiceItem.getBundleId(),
-             invoiceItem.getSubscriptionId(), invoiceItem.getPlanName(), invoiceItem.getPhaseName(), invoiceItem.getUsageName(), invoiceItem.getStartDate(), invoiceItem.getEndDate(),
+             invoiceItem.getSubscriptionId(), invoiceItem.getDescription(), invoiceItem.getPlanName(), invoiceItem.getPhaseName(), invoiceItem.getUsageName(), invoiceItem.getStartDate(), invoiceItem.getEndDate(),
              invoiceItem.getAmount(), invoiceItem.getRate(), invoiceItem.getCurrency(), invoiceItem.getLinkedItemId());
     }
 
@@ -103,6 +105,10 @@ public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<In
         return subscriptionId;
     }
 
+    public String getDescription() {
+        return description;
+    }
+
     public String getPlanName() {
         return planName;
     }
@@ -197,13 +203,13 @@ public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<In
 
     @Override
     public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("InvoiceItemModelDao");
-        sb.append("{type=").append(type);
+        final StringBuilder sb = new StringBuilder("InvoiceItemModelDao{");
+        sb.append("type=").append(type);
         sb.append(", invoiceId=").append(invoiceId);
         sb.append(", accountId=").append(accountId);
         sb.append(", bundleId=").append(bundleId);
         sb.append(", subscriptionId=").append(subscriptionId);
+        sb.append(", description='").append(description).append('\'');
         sb.append(", planName='").append(planName).append('\'');
         sb.append(", phaseName='").append(phaseName).append('\'');
         sb.append(", usageName='").append(usageName).append('\'');
@@ -243,7 +249,10 @@ public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<In
         if (currency != that.currency) {
             return false;
         }
-        if (endDate != null ? !endDate.equals(that.endDate) : that.endDate != null) {
+        if (description != null ? !description.equals(that.description) : that.description != null) {
+            return false;
+        }
+        if (endDate != null ? endDate.compareTo(that.endDate) != 0 : that.endDate != null) {
             return false;
         }
         if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
@@ -264,7 +273,7 @@ public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<In
         if (rate != null ? rate.compareTo(that.rate) != 0 : that.rate != null) {
             return false;
         }
-        if (startDate != null ? !startDate.equals(that.startDate) : that.startDate != null) {
+        if (startDate != null ? startDate.compareTo(that.startDate) != 0 : that.startDate != null) {
             return false;
         }
         if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
@@ -285,6 +294,7 @@ public class InvoiceItemModelDao extends EntityBase implements EntityModelDao<In
         result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
         result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
         result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (description != null ? description.hashCode() : 0);
         result = 31 * result + (planName != null ? planName.hashCode() : 0);
         result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
         result = 31 * result + (usageName != null ? usageName.hashCode() : 0);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java
index e8cef2b..ae3cce6 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceModelDao.java
@@ -30,8 +30,9 @@ import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class InvoiceModelDao extends EntityBase implements EntityModelDao<Invoice> {
+public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDao<Invoice> {
 
     private UUID accountId;
     private Integer invoiceNumber;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java
index 231425c..20a7c81 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentModelDao.java
@@ -27,8 +27,9 @@ import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class InvoicePaymentModelDao extends EntityBase implements EntityModelDao<InvoicePayment> {
+public class InvoicePaymentModelDao extends EntityModelDaoBase implements EntityModelDao<InvoicePayment> {
 
     private InvoicePaymentType type;
     private UUID invoiceId;
@@ -37,14 +38,14 @@ public class InvoicePaymentModelDao extends EntityBase implements EntityModelDao
     private BigDecimal amount;
     private Currency currency;
     private Currency processedCurrency;
-    private UUID paymentCookieId;
+    private String paymentCookieId;
     private UUID linkedInvoicePaymentId;
 
     public InvoicePaymentModelDao() { /* For the DAO mapper */ }
 
     public InvoicePaymentModelDao(final UUID id, final DateTime createdDate, final InvoicePaymentType type, final UUID invoiceId,
                                   final UUID paymentId, final DateTime paymentDate, final BigDecimal amount, final Currency currency,
-                                  final Currency processedCurrency, final UUID paymentCookieId, final UUID linkedInvoicePaymentId) {
+                                  final Currency processedCurrency, final String paymentCookieId, final UUID linkedInvoicePaymentId) {
         super(id, createdDate, createdDate);
         this.type = type;
         this.invoiceId = invoiceId;
@@ -91,7 +92,7 @@ public class InvoicePaymentModelDao extends EntityBase implements EntityModelDao
         return processedCurrency;
     }
 
-    public UUID getPaymentCookieId() {
+    public String getPaymentCookieId() {
         return paymentCookieId;
     }
 
@@ -127,7 +128,7 @@ public class InvoicePaymentModelDao extends EntityBase implements EntityModelDao
         this.processedCurrency = processedCurrency;
     }
 
-    public void setPaymentCookieId(final UUID paymentCookieId) {
+    public void setPaymentCookieId(final String paymentCookieId) {
         this.paymentCookieId = paymentCookieId;
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
index 474b51c..0d93c9f 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -20,32 +22,27 @@ import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
-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.customizers.RegisterMapper;
-
-import org.killbill.billing.invoice.api.InvoicePayment;
-import org.killbill.billing.util.audit.ChangeType;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.util.dao.UuidMapper;
+import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.util.audit.ChangeType;
 import org.killbill.billing.util.entity.dao.Audited;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.mapper.UUIDMapper;
+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.customizers.RegisterMapper;
 
 @EntitySqlDaoStringTemplate
 public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDao, InvoicePayment> {
 
     @SqlQuery
-    public InvoicePaymentModelDao getByPaymentId(@Bind("paymentId") final String paymentId,
+    public List<InvoicePaymentModelDao> getByPaymentId(@Bind("paymentId") final String paymentId,
                                                  @BindBean final InternalTenantContext context);
 
-    @SqlBatch(transactional = false)
-    @Audited(ChangeType.INSERT)
-    void batchCreateFromTransaction(@BindBean final List<InvoicePaymentModelDao> items,
-                                    @BindBean final InternalCallContext context);
 
     @SqlQuery
     public List<InvoicePaymentModelDao> getPaymentsForInvoice(@Bind("invoiceId") final String invoiceId,
@@ -64,7 +61,6 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDa
                                       @BindBean final InternalTenantContext context);
 
     @SqlQuery
-    @RegisterMapper(UuidMapper.class)
     UUID getAccountIdFromInvoicePaymentId(@Bind("invoicePaymentId") final String invoicePaymentId,
                                           @BindBean final InternalTenantContext context);
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
index 5e2ebe0..89a9905 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -50,6 +50,8 @@ import org.killbill.billing.invoice.usage.SubscriptionConsumableInArrear;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.usage.api.UsageUserApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.config.InvoiceConfig;
 import org.killbill.billing.util.currency.KillBillMoney;
 import org.killbill.billing.util.dao.NonEntityDao;
@@ -72,13 +74,15 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
     private final InvoiceConfig config;
     private final UsageUserApi usageApi;
     private final NonEntityDao nonEntityDao;
+    private final CacheControllerDispatcher controllerDispatcher;
 
     @Inject
-    public DefaultInvoiceGenerator(final Clock clock, final UsageUserApi usageApi, final InvoiceConfig config, final NonEntityDao nonEntityDao) {
+    public DefaultInvoiceGenerator(final Clock clock, final UsageUserApi usageApi, final InvoiceConfig config, final NonEntityDao nonEntityDao, final CacheControllerDispatcher controllerDispatcher) {
         this.clock = clock;
         this.config = config;
         this.usageApi = usageApi;
         this.nonEntityDao = nonEntityDao;
+        this.controllerDispatcher = controllerDispatcher;
     }
 
     /*
@@ -105,7 +109,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         final List<InvoiceItem> usageItems = generateUsageInvoiceItems(invoiceId, events, existingInvoices, targetDate, context);
         invoice.addInvoiceItems(usageItems);
 
-        return inAdvanceItems.size() != 0 ? invoice : null;
+        return invoice.getInvoiceItems().size() != 0 ? invoice : null;
     }
 
     // STEPH_USAGE Only deals with consumable in arrear usage billing.
@@ -113,7 +117,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
                                                         @Nullable final List<Invoice> existingInvoices, final LocalDate targetDate,
                                                         final InternalCallContext context) throws InvoiceApiException {
 
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
+        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID));
         try {
 
             final List<InvoiceItem> items = Lists.newArrayList();
@@ -123,10 +127,16 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
             UUID curSubscriptionId = null;
             while (events.hasNext()) {
                 final BillingEvent event = events.next();
+                // Skip events that are posterior to the targetDate
+                final LocalDate eventLocalEffectiveDate =  new LocalDate(event.getEffectiveDate(), event.getAccount().getTimeZone());
+                if (eventLocalEffectiveDate.isAfter(targetDate)) {
+                    continue;
+                }
+
                 final UUID subscriptionId = event.getSubscription().getId();
                 if (curSubscriptionId != null && !curSubscriptionId.equals(subscriptionId)) {
                     final SubscriptionConsumableInArrear subscriptionConsumableInArrear = new SubscriptionConsumableInArrear(invoiceId, curEvents, usageApi, targetDate, context.toTenantContext(tenantId));
-                    items.addAll(subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(extractUsageItemsForSubscription(subscriptionId, existingInvoices)));
+                    items.addAll(subscriptionConsumableInArrear.computeMissingUsageInvoiceItems(extractUsageItemsForSubscription(curSubscriptionId, existingInvoices)));
                     curEvents = Lists.newArrayList();
                 }
                 curSubscriptionId = subscriptionId;
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
index 3a035b2..268c9dc 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/glue/DefaultInvoiceModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,11 @@
 
 package org.killbill.billing.invoice.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
 import org.killbill.billing.glue.InvoiceModule;
 import org.killbill.billing.invoice.InvoiceListener;
 import org.killbill.billing.invoice.InvoiceTagHandler;
 import org.killbill.billing.invoice.api.DefaultInvoiceService;
+import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.invoice.api.InvoiceMigrationApi;
 import org.killbill.billing.invoice.api.InvoiceNotifier;
 import org.killbill.billing.invoice.api.InvoicePaymentApi;
@@ -43,20 +43,22 @@ import org.killbill.billing.invoice.notification.EmailInvoiceNotifier;
 import org.killbill.billing.invoice.notification.NextBillingDateNotifier;
 import org.killbill.billing.invoice.notification.NextBillingDatePoster;
 import org.killbill.billing.invoice.notification.NullInvoiceNotifier;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.config.InvoiceConfig;
-import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.billing.util.template.translation.TranslatorConfig;
+import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.AbstractModule;
+import com.google.inject.TypeLiteral;
 
-public class DefaultInvoiceModule extends AbstractModule implements InvoiceModule {
+public class DefaultInvoiceModule extends KillBillModule implements InvoiceModule {
 
     InvoiceConfig config;
 
-    protected final ConfigSource configSource;
-
-    public DefaultInvoiceModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public DefaultInvoiceModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     protected void installInvoiceDao() {
@@ -79,7 +81,7 @@ public class DefaultInvoiceModule extends AbstractModule implements InvoiceModul
     }
 
     protected void installConfig() {
-        config = new ConfigurationObjectFactory(configSource).build(InvoiceConfig.class);
+        config = new ConfigurationObjectFactory(skifeConfigSource).build(InvoiceConfig.class);
         bind(InvoiceConfig.class).toInstance(config);
     }
 
@@ -95,7 +97,7 @@ public class DefaultInvoiceModule extends AbstractModule implements InvoiceModul
     protected void installNotifiers() {
         bind(NextBillingDateNotifier.class).to(DefaultNextBillingDateNotifier.class).asEagerSingleton();
         bind(NextBillingDatePoster.class).to(DefaultNextBillingDatePoster.class).asEagerSingleton();
-        final TranslatorConfig config = new ConfigurationObjectFactory(configSource).build(TranslatorConfig.class);
+        final TranslatorConfig config = new ConfigurationObjectFactory(skifeConfigSource).build(TranslatorConfig.class);
         bind(TranslatorConfig.class).toInstance(config);
         bind(InvoiceFormatterFactory.class).to(config.getInvoiceFormatterFactoryClass()).asEagerSingleton();
     }
@@ -120,10 +122,15 @@ public class DefaultInvoiceModule extends AbstractModule implements InvoiceModul
         bind(InvoiceGenerator.class).to(DefaultInvoiceGenerator.class).asEagerSingleton();
     }
 
+    protected void installInvoicePluginApi() {
+        bind(new TypeLiteral<OSGIServiceRegistration<InvoicePluginApi>>() {}).toProvider(DefaultInvoiceProviderPluginRegistryProvider.class).asEagerSingleton();
+    }
+
     @Override
     protected void configure() {
         installConfig();
 
+        installInvoicePluginApi();
         installInvoiceService();
         installInvoiceNotifier();
         installNotifiers();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index 24e9132..03449af 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -30,27 +32,16 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-import org.killbill.billing.catalog.api.BillingMode;
-import org.killbill.billing.catalog.api.Usage;
-import org.killbill.billing.invoice.usage.UsageUtils;
-import org.killbill.billing.junction.BillingEvent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.clock.Clock;
-import org.killbill.commons.locker.GlobalLock;
-import org.killbill.commons.locker.GlobalLocker;
-import org.killbill.commons.locker.LockFailedException;
+import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.events.BusInternalEvent;
 import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
 import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
@@ -72,21 +63,34 @@ import org.killbill.billing.invoice.generator.InvoiceGenerator;
 import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.invoice.model.RecurringInvoiceItem;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
 import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.junction.BillingInternalApi;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.billing.util.timezone.DateAndTimeZoneContext;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.LockFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
 public class InvoiceDispatcher {
@@ -104,9 +108,12 @@ public class InvoiceDispatcher {
     private final GlobalLocker locker;
     private final PersistentBus eventBus;
     private final Clock clock;
+    private final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
+    private final CacheControllerDispatcher controllerDispatcher;
 
     @Inject
-    public InvoiceDispatcher(final InvoiceGenerator generator, final AccountInternalApi accountApi,
+    public InvoiceDispatcher(final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry,
+                             final InvoiceGenerator generator, final AccountInternalApi accountApi,
                              final BillingInternalApi billingApi,
                              final SubscriptionBaseInternalApi SubscriptionApi,
                              final InvoiceDao invoiceDao,
@@ -114,7 +121,8 @@ public class InvoiceDispatcher {
                              final InvoiceNotifier invoiceNotifier,
                              final GlobalLocker locker,
                              final PersistentBus eventBus,
-                             final Clock clock) {
+                             final Clock clock, final CacheControllerDispatcher controllerDispatcher) {
+        this.pluginRegistry = pluginRegistry;
         this.generator = generator;
         this.billingApi = billingApi;
         this.subscriptionApi = SubscriptionApi;
@@ -125,6 +133,7 @@ public class InvoiceDispatcher {
         this.locker = locker;
         this.eventBus = eventBus;
         this.clock = clock;
+        this.controllerDispatcher = controllerDispatcher;
     }
 
     public void processSubscription(final EffectiveSubscriptionInternalEvent transition,
@@ -142,7 +151,7 @@ public class InvoiceDispatcher {
             }
             final UUID accountId = subscriptionApi.getAccountIdFromSubscriptionId(subscriptionId, context);
             processAccount(accountId, targetDate, false, context);
-        } catch (SubscriptionBaseApiException e) {
+        } catch (final SubscriptionBaseApiException e) {
             log.error("Failed handling SubscriptionBase change.",
                       new InvoiceApiException(ErrorCode.INVOICE_NO_ACCOUNT_ID_FOR_SUBSCRIPTION_ID, subscriptionId.toString()));
         }
@@ -155,7 +164,7 @@ public class InvoiceDispatcher {
             lock = locker.lockWithNumberOfTries(LockerType.ACCOUNT_FOR_INVOICE_PAYMENTS.toString(), accountId.toString(), NB_LOCK_TRY);
 
             return processAccountWithLock(accountId, targetDate, dryRun, context);
-        } catch (LockFailedException e) {
+        } catch (final LockFailedException e) {
             // Not good!
             log.error(String.format("Failed to process invoice for account %s, targetDate %s",
                                     accountId.toString(), targetDate), e);
@@ -167,7 +176,6 @@ public class InvoiceDispatcher {
         return null;
     }
 
-
     private Invoice processAccountWithLock(final UUID accountId, final DateTime targetDateTime,
                                            final boolean dryRun, final InternalCallContext context) throws InvoiceApiException {
         try {
@@ -180,7 +188,6 @@ public class InvoiceDispatcher {
                                                                   new DateAndTimeZoneContext(billingEvents.iterator().next().getEffectiveDate(), account.getTimeZone(), clock) :
                                                                   null;
 
-
             List<Invoice> invoices = new ArrayList<Invoice>();
             if (!billingEvents.isAccountAutoInvoiceOff()) {
                 invoices = ImmutableList.<Invoice>copyOf(Collections2.transform(invoiceDao.getInvoicesByAccount(context),
@@ -206,6 +213,21 @@ public class InvoiceDispatcher {
                 }
             } else {
                 if (!dryRun) {
+                    // Ask external invoice plugins if additional items (tax, etc) shall be added to the invoice
+                    final List<InvoicePluginApi> invoicePlugins = this.getInvoicePlugins();
+                    final CallContext callContext = buildCallContext(context);
+                    for (final InvoicePluginApi invoicePlugin : invoicePlugins) {
+                        final List<InvoiceItem> items = invoicePlugin.getAdditionalInvoiceItems(invoice, ImmutableList.<PluginProperty>of(), callContext);
+                        if (items != null) {
+                            for (final InvoiceItem item : items) {
+                                if (InvoiceItemType.EXTERNAL_CHARGE.equals(item.getInvoiceItemType()) || InvoiceItemType.TAX.equals(item.getInvoiceItemType())) {
+                                    invoice.addInvoiceItem(item);
+                                } else {
+                                    log.warn("Ignoring invoice item of type {} from InvoicePluginApi {}: {}", item.getInvoiceItemType(), invoicePlugin, item);
+                                }
+                            }
+                        }
+                    }
 
                     // Extract the set of invoiceId for which we see items that don't belong to current generated invoice
                     final Set<UUID> adjustedUniqueOtherInvoiceId = new TreeSet<UUID>();
@@ -219,12 +241,12 @@ public class InvoiceDispatcher {
                     isRealInvoiceWithItems = adjustedUniqueOtherInvoiceId.remove(invoice.getId());
 
                     if (isRealInvoiceWithItems) {
-                        log.info("Generated invoice {} with {} items for accountId {} and targetDate {} (targetDateTime {})", new Object[]{invoice.getId(), invoice.getNumberOfItems(),                                                                                                                                           accountId, targetDate, targetDateTime});
+                        log.info("Generated invoice {} with {} items for accountId {} and targetDate {} (targetDateTime {})", new Object[]{invoice.getId(), invoice.getNumberOfItems(), accountId, targetDate, targetDateTime});
                     } else {
                         final Joiner joiner = Joiner.on(",");
                         final String adjustedInvoices = joiner.join(adjustedUniqueOtherInvoiceId.toArray(new UUID[adjustedUniqueOtherInvoiceId.size()]));
                         log.info("Adjusting existing invoices {} with {} items for accountId {} and targetDate {} (targetDateTime {})", new Object[]{adjustedInvoices, invoice.getNumberOfItems(),
-                                                                                                                                           accountId, targetDate, targetDateTime});
+                                                                                                                                                     accountId, targetDate, targetDateTime});
                     }
 
                     final InvoiceModelDao invoiceModelDao = new InvoiceModelDao(invoice);
@@ -235,59 +257,60 @@ public class InvoiceDispatcher {
                                                                                                                                                     return new InvoiceItemModelDao(input);
                                                                                                                                                 }
                                                                                                                                             }));
-                    // Not really needed, there shouldn't be any payment at this stage
-                    final List<InvoicePaymentModelDao> invoicePaymentModelDaos = ImmutableList.<InvoicePaymentModelDao>copyOf(Collections2.transform(invoice.getPayments(),
-                                                                                                                                                     new Function<InvoicePayment, InvoicePaymentModelDao>() {
-                                                                                                                                                         @Override
-                                                                                                                                                         public InvoicePaymentModelDao apply(final InvoicePayment input) {
-                                                                                                                                                             return new InvoicePaymentModelDao(input);
-                                                                                                                                                         }
-                                                                                                                                                     }));
-
-                    final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions = createNextFutureNotificationDate(invoiceItemModelDaos, UsageUtils.getKnownUsages(billingEvents, null), dateAndTimeZoneContext);
-                    invoiceDao.createInvoice(invoiceModelDao, invoiceItemModelDaos, invoicePaymentModelDaos, isRealInvoiceWithItems, callbackDateTimePerSubscriptions, context);
+
+                    final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions = createNextFutureNotificationDate(invoiceItemModelDaos, billingEvents.getUsages(), dateAndTimeZoneContext);
+                    invoiceDao.createInvoice(invoiceModelDao, invoiceItemModelDaos, isRealInvoiceWithItems, callbackDateTimePerSubscriptions, context);
 
                     final List<InvoiceItem> fixedPriceInvoiceItems = invoice.getInvoiceItems(FixedPriceInvoiceItem.class);
                     final List<InvoiceItem> recurringInvoiceItems = invoice.getInvoiceItems(RecurringInvoiceItem.class);
                     setChargedThroughDates(dateAndTimeZoneContext, fixedPriceInvoiceItems, recurringInvoiceItems, context);
 
-
                     final List<InvoiceInternalEvent> events = new ArrayList<InvoiceInternalEvent>();
                     if (isRealInvoiceWithItems) {
                         events.add(new DefaultInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
                                                                    invoice.getBalance(), invoice.getCurrency(),
                                                                    context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()));
                     }
-                    for (UUID cur : adjustedUniqueOtherInvoiceId) {
+                    for (final UUID cur : adjustedUniqueOtherInvoiceId) {
                         final InvoiceAdjustmentInternalEvent event = new DefaultInvoiceAdjustmentEvent(cur, invoice.getAccountId(),
                                                                                                        context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
                         events.add(event);
                     }
 
-
-                    for (InvoiceInternalEvent event : events) {
+                    for (final InvoiceInternalEvent event : events) {
                         postEvent(event, accountId, context);
                     }
                 }
             }
 
-            if (account.isNotifiedForInvoices() && isRealInvoiceWithItems  && !dryRun) {
+            if (account.isNotifiedForInvoices() && isRealInvoiceWithItems && !dryRun) {
                 // Need to re-hydrate the invoice object to get the invoice number (record id)
                 // API_FIX InvoiceNotifier public API?
                 invoiceNotifier.notify(account, new DefaultInvoice(invoiceDao.getById(invoice.getId(), context)), buildTenantContext(context));
             }
 
             return invoice;
-        } catch (AccountApiException e) {
+        } catch (final AccountApiException e) {
             log.error("Failed handling SubscriptionBase change.", e);
             return null;
         }
     }
 
     private TenantContext buildTenantContext(final InternalTenantContext context) {
-        return context.toTenantContext(nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT));
+        return context.toTenantContext(nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID)));
+    }
+
+    private CallContext buildCallContext(final InternalCallContext context) {
+        return context.toCallContext(nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID)));
     }
 
+    private List<InvoicePluginApi> getInvoicePlugins() {
+        final List<InvoicePluginApi> invoicePlugins = new ArrayList<InvoicePluginApi>();
+        for (final String name : this.pluginRegistry.getAllServices()) {
+            invoicePlugins.add(this.pluginRegistry.getServiceForName(name));
+        }
+        return invoicePlugins;
+    }
 
     @VisibleForTesting
     Map<UUID, List<DateTime>> createNextFutureNotificationDate(final List<InvoiceItemModelDao> invoiceItems, final Map<String, Usage> knownUsages, final DateAndTimeZoneContext dateAndTimeZoneContext) {
@@ -307,7 +330,7 @@ public class InvoiceDispatcher {
                 result.put(item.getSubscriptionId(), perSubscriptionCallback);
             }
 
-            switch(item.getType()) {
+            switch (item.getType()) {
                 case RECURRING:
                     if ((item.getEndDate() != null) &&
                         (item.getAmount() == null ||
@@ -317,9 +340,8 @@ public class InvoiceDispatcher {
                     break;
 
                 case USAGE:
-                    final Usage usage = knownUsages.get(item.getUsageName());
-                    final String key = item.getSubscriptionId().toString() + ":" + usage.getName();
-                    final LocalDate perSubscriptionUsageRecurringDate  = perSubscriptionUsage.get(key);
+                    final String key = item.getSubscriptionId().toString() + ":" + item.getUsageName();
+                    final LocalDate perSubscriptionUsageRecurringDate = perSubscriptionUsage.get(key);
                     if (perSubscriptionUsageRecurringDate == null || perSubscriptionUsageRecurringDate.compareTo(item.getEndDate()) < 0) {
                         perSubscriptionUsage.put(key, item.getEndDate());
                     }
@@ -331,22 +353,26 @@ public class InvoiceDispatcher {
         }
 
         for (final String key : perSubscriptionUsage.keySet()) {
-            final String [] parts = key.split(":");
+            final String[] parts = key.split(":");
             final UUID subscriptionId = UUID.fromString(parts[0]);
 
             final List<DateTime> perSubscriptionCallback = result.get(subscriptionId);
             final String usageName = parts[1];
-            final Usage usage = knownUsages.get(usageName);
+            final LocalDate endDate = perSubscriptionUsage.get(key);
 
-            final LocalDate endDate =  perSubscriptionUsage.get(key);
-            // STEPH_USAGE WE should double check this is indeed the right date to be called back for that subscription/usage section.
-            final LocalDate nextCallbackUsageDate = (usage.getBillingMode() == BillingMode.IN_ARREAR) ? endDate.plusMonths(usage.getBillingPeriod().getNumberOfMonths()) : endDate;
-            perSubscriptionCallback.add(dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(nextCallbackUsageDate));
+            final DateTime subscriptionUsageCallbackDate = getNextUsageBillingDate(usageName, endDate, dateAndTimeZoneContext, knownUsages);
+            perSubscriptionCallback.add(subscriptionUsageCallbackDate);
         }
 
         return result;
     }
 
+    private DateTime getNextUsageBillingDate(final String usageName, final LocalDate chargedThroughDate, final DateAndTimeZoneContext dateAndTimeZoneContext, final Map<String, Usage> knownUsages) {
+        final Usage usage = knownUsages.get(usageName);
+        final LocalDate nextCallbackUsageDate = (usage.getBillingMode() == BillingMode.IN_ARREAR) ? chargedThroughDate.plusMonths(usage.getBillingPeriod().getNumberOfMonths()) : chargedThroughDate;
+        return dateAndTimeZoneContext.computeUTCDateTimeFromLocalDate(nextCallbackUsageDate);
+    }
+
     private void setChargedThroughDates(final DateAndTimeZoneContext dateAndTimeZoneContext,
                                         final Collection<InvoiceItem> fixedPriceItems,
                                         final Collection<InvoiceItem> recurringItems,
@@ -366,7 +392,7 @@ public class InvoiceDispatcher {
     private void postEvent(final BusInternalEvent event, final UUID accountId, final InternalCallContext context) {
         try {
             eventBus.post(event);
-        } catch (EventBusException e) {
+        } catch (final EventBusException e) {
             log.error(String.format("Failed to post event %s for account %s", event.getBusEventType(), accountId), e);
         }
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/AdjInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/AdjInvoiceItem.java
index ec14a46..e126f90 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/AdjInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/AdjInvoiceItem.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -23,24 +25,17 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
 public abstract class AdjInvoiceItem extends InvoiceItemBase {
 
     AdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId,
-                   final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
-        this(id, createdDate, invoiceId, accountId, startDate, endDate, amount, currency, null);
+                   final LocalDate startDate, final LocalDate endDate, @Nullable final String description,
+                   final BigDecimal amount, final Currency currency, @Nullable final UUID reversingId) {
+        super(id, createdDate, invoiceId, accountId, null, null, description, null, null, null, startDate, endDate, amount, currency, reversingId);
     }
 
-    AdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId,
-                   final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, @Nullable final UUID reversingId) {
-        super(id, createdDate, invoiceId, accountId, null, null, null, null, null, startDate, endDate, amount, currency, reversingId);
-    }
-
-
     @Override
     public abstract InvoiceItemType getInvoiceItemType();
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/CreditAdjInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/CreditAdjInvoiceItem.java
index 270b186..5f84d83 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/CreditAdjInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/CreditAdjInvoiceItem.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -23,10 +25,11 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
+import com.google.common.base.Objects;
+
 public class CreditAdjInvoiceItem extends AdjInvoiceItem {
 
     public CreditAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date,
@@ -36,7 +39,12 @@ public class CreditAdjInvoiceItem extends AdjInvoiceItem {
 
     public CreditAdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final LocalDate date,
                                 final BigDecimal amount, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, date, date, amount, currency);
+        this(id, createdDate, invoiceId, accountId, date, null, amount, currency);
+    }
+
+    public CreditAdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final LocalDate date,
+                                @Nullable final String description, final BigDecimal amount, final Currency currency) {
+        super(id, createdDate, invoiceId, accountId, date, date, description, amount, currency, null);
     }
 
     @Override
@@ -46,6 +54,6 @@ public class CreditAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "Invoice adjustment";
+        return Objects.firstNonNull(description, "Invoice adjustment");
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/CreditBalanceAdjInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
index c046ba3..598249d 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/CreditBalanceAdjInvoiceItem.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -23,7 +25,6 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
@@ -37,7 +38,13 @@ public class CreditBalanceAdjInvoiceItem extends AdjInvoiceItem {
     public CreditBalanceAdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId,
                                        final LocalDate date, final UUID linkedInvoiceItemId,
                                        final BigDecimal amount, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, date, date, amount, currency, linkedInvoiceItemId);
+        this(id, createdDate, invoiceId, accountId, date, linkedInvoiceItemId, null, amount, currency);
+    }
+
+    public CreditBalanceAdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId,
+                                       final LocalDate date, final UUID linkedInvoiceItemId,
+                                       @Nullable final String description, final BigDecimal amount, final Currency currency) {
+        super(id, createdDate, invoiceId, accountId, date, date, description, amount, currency, linkedInvoiceItemId);
     }
 
     @Override
@@ -47,6 +54,10 @@ public class CreditBalanceAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
+        if (description != null) {
+            return description;
+        }
+
         final String secondDescription;
         if (getAmount().compareTo(BigDecimal.ZERO) >= 0) {
             secondDescription = "account credit";
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java
index 1207c34..edca219 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/DefaultInvoicePayment.java
@@ -38,7 +38,7 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment 
     private final BigDecimal amount;
     private final Currency currency;
     private final Currency processedCurrency;
-    private final UUID paymentCookieId;
+    private final String paymentCookieId;
     private final UUID linkedInvoicePaymentId;
 
     public DefaultInvoicePayment(final InvoicePaymentType type, final UUID paymentId, final UUID invoiceId, final DateTime paymentDate,
@@ -47,13 +47,13 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment 
     }
 
     public DefaultInvoicePayment(final UUID id, final InvoicePaymentType type, final UUID paymentId, final UUID invoiceId, final DateTime paymentDate,
-                                 @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final Currency processedCurrency, @Nullable final UUID paymentCookieId,
+                                 @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final Currency processedCurrency, @Nullable final String paymentCookieId,
                                  @Nullable final UUID linkedInvoicePaymentId) {
         this(id, null, type, paymentId, invoiceId, paymentDate, amount, currency, processedCurrency, paymentCookieId, linkedInvoicePaymentId);
     }
 
     public DefaultInvoicePayment(final UUID id, @Nullable final DateTime createdDate, final InvoicePaymentType type, final UUID paymentId, final UUID invoiceId, final DateTime paymentDate,
-                                 @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final Currency processedCurrency, @Nullable final UUID paymentCookieId,
+                                 @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final Currency processedCurrency, @Nullable final String paymentCookieId,
                                  @Nullable final UUID linkedInvoicePaymentId) {
         super(id, createdDate, createdDate);
         this.type = type;
@@ -111,7 +111,7 @@ public class DefaultInvoicePayment extends EntityBase implements InvoicePayment 
     }
 
     @Override
-    public UUID getPaymentCookieId() {
+    public String getPaymentCookieId() {
         return paymentCookieId;
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
index e2c92c5..67821b6 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -23,9 +23,7 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
 public class ExternalChargeInvoiceItem extends InvoiceItemBase {
@@ -42,11 +40,15 @@ public class ExternalChargeInvoiceItem extends InvoiceItemBase {
 
     public ExternalChargeInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
                                      @Nullable final String description, final LocalDate date, final BigDecimal amount, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, bundleId, null, description, null, null, date, null, amount, currency);
+        super(id, createdDate, invoiceId, accountId, bundleId, null, description, null, null, null, date, null, amount, currency);
     }
 
     @Override
     public String getDescription() {
+        if (description != null) {
+            return description;
+        }
+
         if (getPlanName() == null) {
             return "External charge";
         } else {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/FixedPriceInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/FixedPriceInvoiceItem.java
index 16c2c13..590ba09 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/FixedPriceInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/FixedPriceInvoiceItem.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -23,9 +25,7 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
 public class FixedPriceInvoiceItem extends InvoiceItemBase {
@@ -33,17 +33,21 @@ public class FixedPriceInvoiceItem extends InvoiceItemBase {
     public FixedPriceInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId,
                                  final String planName, final String phaseName,
                                  final LocalDate date, final BigDecimal amount, final Currency currency) {
-        this(UUID.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, date, amount, currency);
+        this(UUID.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, null, date, amount, currency);
     }
 
     public FixedPriceInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId,
                                  final UUID subscriptionId, final String planName, final String phaseName,
-                                 final LocalDate date, final BigDecimal amount, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, null, date, null, amount, currency);
+                                 @Nullable final String description, final LocalDate date, final BigDecimal amount, final Currency currency) {
+        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, null, date, null, amount, currency);
     }
 
     @Override
     public String getDescription() {
+        if (description != null) {
+            return description;
+        }
+
         if (getPhaseName() == null) {
             return "Fixed price charge";
         } else {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
index f874ec0..b3f12d8 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemBase.java
@@ -23,11 +23,10 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
-import org.killbill.billing.entity.EntityBase;
 
 public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem {
 
@@ -38,6 +37,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
     protected final LocalDate endDate;
     protected final BigDecimal amount;
     protected final Currency currency;
+    protected final String description;
 
     /* Fixed and recurring specific */
     protected final UUID subscriptionId;
@@ -54,7 +54,6 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
     /* Usage specific */
     protected final String usageName;
 
-
     @Override
     public String toString() {
         // Note: we don't use all fields here, as the output would be overwhelming
@@ -76,27 +75,27 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
     */
     // No rate and no reversing item
     public InvoiceItemBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
-                           @Nullable final UUID subscriptionId, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+                           @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
                            final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
-        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, startDate, endDate, amount, null, currency, null);
+        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, startDate, endDate, amount, null, currency, null);
     }
 
     // With rate but no reversing item
     public InvoiceItemBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
-                           @Nullable final UUID subscriptionId, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+                           @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
                            final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency) {
-        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, startDate, endDate, amount, rate, currency, null);
+        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, startDate, endDate, amount, rate, currency, null);
     }
 
     // With  reversing item, no rate
     public InvoiceItemBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
-                           @Nullable final UUID subscriptionId, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+                           @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
                            final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency, final UUID reversedItemId) {
-        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, startDate, endDate, amount, null, currency, reversedItemId);
+        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, startDate, endDate, amount, null, currency, reversedItemId);
     }
 
     private InvoiceItemBase(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
-                            @Nullable final UUID subscriptionId, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
+                            @Nullable final UUID subscriptionId, @Nullable final String description, @Nullable final String planName, @Nullable final String phaseName, @Nullable final String usageName,
                             final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final BigDecimal rate, final Currency currency,
                             final UUID reversedItemId) {
         super(id, createdDate, createdDate);
@@ -104,6 +103,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
         this.accountId = accountId;
         this.subscriptionId = subscriptionId;
         this.bundleId = bundleId;
+        this.description = description;
         this.planName = planName;
         this.phaseName = phaseName;
         this.usageName = usageName;
@@ -196,10 +196,12 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
         if (linkedItemId != null ? !linkedItemId.equals(that.linkedItemId) : that.linkedItemId != null) {
             return false;
         }
+        if (description != null ? !description.equals(that.description) : that.description != null) {
+            return false;
+        }
         return true;
     }
 
-
     @Override
     public boolean matches(final Object o) {
         if (this == o) {
@@ -244,7 +246,6 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
         return true;
     }
 
-
     @Override
     public int hashCode() {
         int result = super.hashCode();
@@ -256,6 +257,7 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
         result = 31 * result + (currency != null ? currency.hashCode() : 0);
         result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
         result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + (description != null ? description.hashCode() : 0);
         result = 31 * result + (planName != null ? planName.hashCode() : 0);
         result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
         result = 31 * result + (rate != null ? rate.hashCode() : 0);
@@ -268,5 +270,4 @@ public abstract class InvoiceItemBase extends EntityBase implements InvoiceItem 
 
     @Override
     public abstract String getDescription();
-
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
index 01175dd..7eeda4b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/InvoiceItemFactory.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -45,6 +47,7 @@ public class InvoiceItemFactory {
         final String planName = invoiceItemModelDao.getPlanName();
         final String phaseName = invoiceItemModelDao.getPhaseName();
         final String usageName = invoiceItemModelDao.getUsageName();
+        final String description = invoiceItemModelDao.getDescription();
         final LocalDate startDate = invoiceItemModelDao.getStartDate();
         final LocalDate endDate = invoiceItemModelDao.getEndDate();
         final BigDecimal amount = invoiceItemModelDao.getAmount();
@@ -56,31 +59,34 @@ public class InvoiceItemFactory {
         final InvoiceItemType type = invoiceItemModelDao.getType();
         switch (type) {
             case EXTERNAL_CHARGE:
-                item = new ExternalChargeInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, planName, startDate, amount, currency);
+                item = new ExternalChargeInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, description, startDate, amount, currency);
                 break;
             case FIXED:
-                item = new FixedPriceInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, amount, currency);
+                item = new FixedPriceInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, description, startDate, amount, currency);
                 break;
             case RECURRING:
-                item = new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, rate, currency);
+                item = new RecurringInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, description, startDate, endDate, amount, rate, currency);
                 break;
             case CBA_ADJ:
-                item = new CreditBalanceAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, linkedItemId, amount, currency);
+                item = new CreditBalanceAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, linkedItemId, description, amount, currency);
                 break;
             case CREDIT_ADJ:
-                item = new CreditAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, amount, currency);
+                item = new CreditAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, description, amount, currency);
                 break;
             case REFUND_ADJ:
-                item = new RefundAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, amount, currency);
+                item = new RefundAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, description, amount, currency);
                 break;
             case REPAIR_ADJ:
-                item = new RepairAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, endDate, amount, currency, linkedItemId);
+                item = new RepairAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, endDate, description, amount, currency, linkedItemId);
                 break;
             case ITEM_ADJ:
-                item = new ItemAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, amount, currency, linkedItemId);
+                item = new ItemAdjInvoiceItem(id, createdDate, invoiceId, accountId, startDate, description, amount, currency, linkedItemId);
                 break;
             case USAGE:
-                item = new UsageInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, startDate, endDate, amount, currency);
+                item = new UsageInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, startDate, endDate, description, amount, currency);
+                break;
+            case TAX:
+                item = new TaxInvoiceItem(id, createdDate, invoiceId, accountId, bundleId, description, startDate, amount, currency);
                 break;
             default:
                 throw new RuntimeException("Unexpected type of event item " + type);
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/ItemAdjInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/ItemAdjInvoiceItem.java
index 7a11b4b..ac3575f 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/ItemAdjInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/ItemAdjInvoiceItem.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -23,11 +25,12 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
+import com.google.common.base.Objects;
+
 public class ItemAdjInvoiceItem extends AdjInvoiceItem {
 
     public ItemAdjInvoiceItem(final InvoiceItem invoiceItem, final LocalDate effectiveDate,
@@ -38,12 +41,12 @@ public class ItemAdjInvoiceItem extends AdjInvoiceItem {
 
     public ItemAdjInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, final LocalDate startDate,
                               final BigDecimal amount, final Currency currency, final UUID linkedItemId) {
-        this(id, null, invoiceId, accountId, startDate, amount, currency, linkedItemId);
+        this(id, null, invoiceId, accountId, startDate, null, amount, currency, linkedItemId);
     }
 
     public ItemAdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final LocalDate startDate,
-                              final BigDecimal amount, final Currency currency, final UUID linkedItemId) {
-        super(id, createdDate, invoiceId, accountId, startDate, startDate, amount, currency, linkedItemId);
+                              @Nullable final String description, final BigDecimal amount, final Currency currency, final UUID linkedItemId) {
+        super(id, createdDate, invoiceId, accountId, startDate, startDate, description, amount, currency, linkedItemId);
     }
 
     @Override
@@ -53,6 +56,6 @@ public class ItemAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "Invoice item adjustment";
+        return Objects.firstNonNull(description, "Invoice item adjustment");
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItem.java
index b858e8c..d82da43 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/RecurringInvoiceItem.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -23,11 +25,11 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
+import com.google.common.base.Objects;
+
 public class RecurringInvoiceItem extends InvoiceItemBase {
 
     public RecurringInvoiceItem(final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
@@ -39,12 +41,18 @@ public class RecurringInvoiceItem extends InvoiceItemBase {
     public RecurringInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
                                 final String planName, final String phaseName, final LocalDate startDate, final LocalDate endDate,
                                 final BigDecimal amount, final BigDecimal rate, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, null, startDate, endDate, amount, rate, currency);
+        this(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, null, startDate, endDate, amount, rate, currency);
+    }
+
+    public RecurringInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId, final UUID subscriptionId,
+                                final String planName, final String phaseName, @Nullable final String description, final LocalDate startDate, final LocalDate endDate,
+                                final BigDecimal amount, final BigDecimal rate, final Currency currency) {
+        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, null, startDate, endDate, amount, rate, currency);
     }
 
     @Override
     public String getDescription() {
-        return phaseName;
+        return Objects.firstNonNull(description, phaseName);
     }
 
     @Override
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/RefundAdjInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/RefundAdjInvoiceItem.java
index 291b3c5..0f9e3c0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/RefundAdjInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/RefundAdjInvoiceItem.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -23,10 +25,11 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
+import com.google.common.base.Objects;
+
 public class RefundAdjInvoiceItem extends AdjInvoiceItem {
 
     public RefundAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate date,
@@ -36,7 +39,12 @@ public class RefundAdjInvoiceItem extends AdjInvoiceItem {
 
     public RefundAdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final LocalDate date,
                                 final BigDecimal amount, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, date, date, amount, currency);
+        this(id, createdDate, invoiceId, accountId, date, null, amount, currency);
+    }
+
+    public RefundAdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final LocalDate date,
+                                @Nullable final String description, final BigDecimal amount, final Currency currency) {
+        super(id, createdDate, invoiceId, accountId, date, date, description, amount, currency, null);
     }
 
     @Override
@@ -46,6 +54,6 @@ public class RefundAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "Invoice adjustment";
+        return Objects.firstNonNull(description, "Invoice adjustment");
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/RepairAdjInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/RepairAdjInvoiceItem.java
index ffe2eab..cd7fcab 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/RepairAdjInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/RepairAdjInvoiceItem.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -23,10 +25,11 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
+import com.google.common.base.Objects;
+
 public class RepairAdjInvoiceItem extends AdjInvoiceItem {
 
     public RepairAdjInvoiceItem(final UUID invoiceId, final UUID accountId, final LocalDate startDate, final LocalDate endDate,
@@ -36,7 +39,12 @@ public class RepairAdjInvoiceItem extends AdjInvoiceItem {
 
     public RepairAdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final LocalDate startDate, final LocalDate endDate,
                                 final BigDecimal amount, final Currency currency, final UUID reversingId) {
-        super(id, createdDate, invoiceId, accountId, startDate, endDate, amount, currency, reversingId);
+        this(id, createdDate, invoiceId, accountId, startDate, endDate, null, amount, currency, reversingId);
+    }
+
+    public RepairAdjInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final LocalDate startDate, final LocalDate endDate,
+                                @Nullable final String description, final BigDecimal amount, final Currency currency, final UUID reversingId) {
+        super(id, createdDate, invoiceId, accountId, startDate, endDate, description, amount, currency, reversingId);
     }
 
     @Override
@@ -46,6 +54,6 @@ public class RepairAdjInvoiceItem extends AdjInvoiceItem {
 
     @Override
     public String getDescription() {
-        return "Adjustment (subscription change)";
+        return Objects.firstNonNull(description, "Adjustment (subscription change)");
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/TaxInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/TaxInvoiceItem.java
new file mode 100644
index 0000000..24fb389
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/TaxInvoiceItem.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.invoice.api.InvoiceItemType;
+
+public class TaxInvoiceItem extends InvoiceItemBase {
+
+    public TaxInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final String description,
+                          final LocalDate date, final BigDecimal amount, final Currency currency) {
+        this(UUID.randomUUID(), invoiceId, accountId, bundleId, description, date, amount, currency);
+    }
+
+    public TaxInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
+                          @Nullable final String description, final LocalDate date, final BigDecimal amount, final Currency currency) {
+        this(id, null, invoiceId, accountId, bundleId, description, date, amount, currency);
+    }
+
+    public TaxInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId,
+                          @Nullable final String description, final LocalDate date, final BigDecimal amount, final Currency currency) {
+        super(id, createdDate, invoiceId, accountId, bundleId, null, description, null, null, null, date, null, amount, currency);
+    }
+
+    @Override
+    public String getDescription() {
+        if (description != null) {
+            return description;
+        }
+
+        return "Tax";
+    }
+
+    @Override
+    public InvoiceItemType getInvoiceItemType() {
+        return InvoiceItemType.TAX;
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java b/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
index d9ca64f..5233503 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/model/UsageInvoiceItem.java
@@ -1,7 +1,8 @@
 /*
+ * Copyright 2014 Groupon, Inc
  * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -26,18 +27,20 @@ import org.joda.time.LocalDate;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 
+import com.google.common.base.Objects;
+
 public class UsageInvoiceItem extends InvoiceItemBase {
 
     public UsageInvoiceItem(final UUID invoiceId, final UUID accountId, @Nullable final UUID bundleId, @Nullable final UUID subscriptionId,
                             final String planName, final String phaseName, final String usageName,
                             final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
-        this(UUID.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, startDate, endDate, amount, currency);
+        this(UUID.randomUUID(), null, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, startDate, endDate, null, amount, currency);
     }
 
     public UsageInvoiceItem(final UUID id, @Nullable final DateTime createdDate, final UUID invoiceId, final UUID accountId, final UUID bundleId,
                             final UUID subscriptionId, final String planName, final String phaseName, final String usageName,
-                            final LocalDate startDate, final LocalDate endDate, final BigDecimal amount, final Currency currency) {
-        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usageName, startDate, endDate, amount, currency);
+                            final LocalDate startDate, final LocalDate endDate, @Nullable final String description, final BigDecimal amount, final Currency currency) {
+        super(id, createdDate, invoiceId, accountId, bundleId, subscriptionId, description, planName, phaseName, usageName, startDate, endDate, amount, currency);
     }
 
     @Override
@@ -47,7 +50,6 @@ public class UsageInvoiceItem extends InvoiceItemBase {
 
     @Override
     public String getDescription() {
-        return String.format("%s (usage item)", usageName);
+        return Objects.firstNonNull(description, String.format("%s (usage item)", usageName));
     }
-
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/notification/EmailInvoiceNotifier.java b/invoice/src/main/java/org/killbill/billing/invoice/notification/EmailInvoiceNotifier.java
index 723ca8f..ae7f044 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/notification/EmailInvoiceNotifier.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/notification/EmailInvoiceNotifier.java
@@ -27,6 +27,7 @@ import org.killbill.billing.account.api.AccountEmail;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceNotifier;
+import org.killbill.billing.invoice.template.HtmlInvoice;
 import org.killbill.billing.invoice.template.HtmlInvoiceGenerator;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.callcontext.InternalTenantContext;
@@ -85,18 +86,22 @@ public class EmailInvoiceNotifier implements InvoiceNotifier {
             }
         }
 
-        final String htmlBody;
+        final HtmlInvoice htmlInvoice;
         try {
-            htmlBody = generator.generateInvoice(account, invoice, manualPay);
+            htmlInvoice = generator.generateInvoice(account, invoice, manualPay);
         } catch (IOException e) {
             throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
         }
 
-        final String subject = config.getInvoiceEmailSubject();
+        // take localized subject, or the configured one if the localized one is not available
+        String subject = htmlInvoice.getSubject();
+        if (subject == null) {
+            subject = config.getInvoiceEmailSubject();
+        }
 
         final EmailSender sender = new DefaultEmailSender(config);
         try {
-            sender.sendHTMLEmail(to, cc, subject, htmlBody);
+            sender.sendHTMLEmail(to, cc, subject, htmlInvoice.getBody());
         } catch (EmailApiException e) {
             throw new InvoiceApiException(e, ErrorCode.EMAIL_SENDING_FAILED);
         } catch (IOException e) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultInvoiceProviderPluginRegistry.java b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultInvoiceProviderPluginRegistry.java
new file mode 100644
index 0000000..404b9fc
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultInvoiceProviderPluginRegistry.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.provider;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+
+public class DefaultInvoiceProviderPluginRegistry implements OSGIServiceRegistration<InvoicePluginApi> {
+
+    private final static Logger log = LoggerFactory.getLogger(DefaultInvoiceProviderPluginRegistry.class);
+
+    private final Map<String, InvoicePluginApi> pluginsByName = new ConcurrentHashMap<String, InvoicePluginApi>();
+
+    @Inject
+    public DefaultInvoiceProviderPluginRegistry() {
+    }
+
+
+    @Override
+    public void registerService(final OSGIServiceDescriptor desc, final InvoicePluginApi service) {
+        log.info("DefaultInvoiceProviderPluginRegistry registering service " + desc.getRegistrationName());
+        pluginsByName.put(desc.getRegistrationName(), service);
+    }
+
+    @Override
+    public void unregisterService(final String serviceName) {
+        log.info("DefaultInvoiceProviderPluginRegistry unregistering service " + serviceName);
+        pluginsByName.remove(serviceName);
+    }
+
+    @Override
+    public InvoicePluginApi getServiceForName(final String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Null invoice plugin API name");
+        }
+        final InvoicePluginApi plugin = pluginsByName.get(name);
+        return plugin;
+    }
+
+    @Override
+    public Set<String> getAllServices() {
+        return pluginsByName.keySet();
+    }
+
+    @Override
+    public Class<InvoicePluginApi> getServiceType() {
+        return InvoicePluginApi.class;
+    }}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
new file mode 100644
index 0000000..826782c
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/provider/DefaultNoOpInvoiceProviderPlugin.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.provider;
+
+import java.util.List;
+
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.plugin.api.NoOpInvoicePluginApi;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.clock.Clock;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Inject;
+
+public class DefaultNoOpInvoiceProviderPlugin implements NoOpInvoicePluginApi {
+
+    private final Clock clock;
+
+    @Inject
+    public DefaultNoOpInvoiceProviderPlugin(final Clock clock) {
+        this.clock = clock;
+    }
+
+    @Override
+    public List<InvoiceItem> getAdditionalInvoiceItems(final Invoice invoice, Iterable<PluginProperty> properties, CallContext context) {
+        return ImmutableList.<InvoiceItem>of();
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java b/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java
new file mode 100644
index 0000000..0b863f4
--- /dev/null
+++ b/invoice/src/main/java/org/killbill/billing/invoice/provider/NoOpInvoiceProviderPluginProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.provider;
+
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.clock.Clock;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class NoOpInvoiceProviderPluginProvider implements Provider<DefaultNoOpInvoiceProviderPlugin> {
+
+    private final String instanceName;
+
+    private Clock clock;
+    private OSGIServiceRegistration<InvoicePluginApi> registry;
+
+    public NoOpInvoiceProviderPluginProvider(final String instanceName) {
+        this.instanceName = instanceName;
+
+    }
+
+    @Inject
+    public void setPaymentProviderPluginRegistry(final OSGIServiceRegistration<InvoicePluginApi> registry, final Clock clock) {
+        this.clock = clock;
+        this.registry = registry;
+    }
+
+    @Override
+    public DefaultNoOpInvoiceProviderPlugin get() {
+
+        final DefaultNoOpInvoiceProviderPlugin plugin = new DefaultNoOpInvoiceProviderPlugin(clock);
+        final OSGIServiceDescriptor desc = new OSGIServiceDescriptor() {
+            @Override
+            public String getPluginSymbolicName() {
+                return null;
+            }
+            @Override
+            public String getRegistrationName() {
+                return instanceName;
+            }
+        };
+        registry.registerService(desc, plugin);
+        return plugin;
+    }
+}
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/HtmlInvoiceGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/template/HtmlInvoiceGenerator.java
index cd95a16..e04a8f1 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/HtmlInvoiceGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/HtmlInvoiceGenerator.java
@@ -52,12 +52,13 @@ public class HtmlInvoiceGenerator {
         this.currencyConversionApi = currencyConversionApi;
     }
 
-    public String generateInvoice(final Account account, @Nullable final Invoice invoice, final boolean manualPay) throws IOException {
+    public HtmlInvoice generateInvoice(final Account account, @Nullable final Invoice invoice, final boolean manualPay) throws IOException {
         // Don't do anything if the invoice is null
         if (invoice == null) {
             return null;
         }
 
+        HtmlInvoice invoiceData = new HtmlInvoice();
         final Map<String, Object> data = new HashMap<String, Object>();
         final DefaultInvoiceTranslator invoiceTranslator = new DefaultInvoiceTranslator(config);
         final String accountLocale = Strings.emptyToNull(account.getLocale());
@@ -70,10 +71,14 @@ public class HtmlInvoiceGenerator {
         final InvoiceFormatter formattedInvoice = factory.createInvoiceFormatter(config, invoice, locale, currencyConversionApi);
         data.put("invoice", formattedInvoice);
 
+        invoiceData.setSubject(invoiceTranslator.getInvoiceEmailSubject());
+
         if (manualPay) {
-            return templateEngine.executeTemplate(config.getManualPayTemplateName(), data);
+            invoiceData.setBody(templateEngine.executeTemplate(config.getManualPayTemplateName(), data));
         } else {
-            return templateEngine.executeTemplate(config.getTemplateName(), data);
+            invoiceData.setBody(templateEngine.executeTemplate(config.getTemplateName(), data));
         }
+
+        return invoiceData;
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/translator/DefaultInvoiceTranslator.java b/invoice/src/main/java/org/killbill/billing/invoice/template/translator/DefaultInvoiceTranslator.java
index 8c644d3..4bb5ef1 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/translator/DefaultInvoiceTranslator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/translator/DefaultInvoiceTranslator.java
@@ -47,6 +47,12 @@ public class DefaultInvoiceTranslator extends DefaultTranslatorBase implements I
     }
 
     @Override
+    public String getInvoiceEmailSubject() {
+        String subject = getTranslation(locale, "invoiceEmailSubject");
+        return (!"invoiceEmailSubject".equals(subject)) ? subject : null;
+    }
+
+    @Override
     public String getInvoiceTitle() {
         return getTranslation(locale, "invoiceTitle");
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/template/translator/InvoiceStrings.java b/invoice/src/main/java/org/killbill/billing/invoice/template/translator/InvoiceStrings.java
index 624ff76..e98faf4 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/template/translator/InvoiceStrings.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/template/translator/InvoiceStrings.java
@@ -18,6 +18,8 @@ package org.killbill.billing.invoice.template.translator;
 
 public interface InvoiceStrings {
 
+    String getInvoiceEmailSubject();
+
     String getInvoiceTitle();
 
     String getInvoiceDate();
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/AccountItemTree.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/AccountItemTree.java
index be44a10..e005d56 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/AccountItemTree.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/AccountItemTree.java
@@ -94,6 +94,7 @@ public class AccountItemTree {
         Preconditions.checkState(!isBuilt);
         switch (existingItem.getInvoiceItemType()) {
             case EXTERNAL_CHARGE:
+            case TAX:
             case CBA_ADJ:
             case CREDIT_ADJ:
             case REFUND_ADJ:
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsInterval.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsInterval.java
index e4137cd..ff15213 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsInterval.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsInterval.java
@@ -17,17 +17,16 @@
 package org.killbill.billing.invoice.tree;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.ListIterator;
-import java.util.Set;
+import java.util.Map;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.invoice.tree.Item.ItemAction;
 
 import com.google.common.base.Preconditions;
@@ -90,14 +89,55 @@ public class ItemsInterval {
      *
      * @param output
      * @param mergeMode
+     * @return whether or not the parent should ignore the interval covered by the child interval
      */
     public void buildFromItems(final List<Item> output, final boolean mergeMode) {
-        final Item item  = getResultingItem(mergeMode);
+        final Item item = getResultingItem(mergeMode);
         if (item != null) {
             output.add(item);
         }
     }
 
+    /**
+     * Remove all the cancelling pairs (ADD/CANCEL) for which CANCEL linkedId points to ADD id.
+     *
+     * @return true if there is no more items
+     */
+    public boolean mergeCancellingPairs() {
+
+        final Map<UUID, List<Item>> tmp = new HashMap<UUID, List<Item>>();
+        for (Item cur : items) {
+            final UUID idToConsider = (cur.getAction() == ItemAction.ADD) ? cur.getId() : cur.getLinkedId();
+            List<Item> listForItem = tmp.get(idToConsider);
+            if (listForItem == null) {
+                listForItem = new ArrayList<Item>(2);
+                tmp.put(idToConsider, listForItem);
+            }
+            listForItem.add(cur);
+        }
+
+        for (List<Item> listForIds : tmp.values()) {
+            if (listForIds.size() == 2) {
+                items.remove(listForIds.get(0));
+                items.remove(listForIds.get(1));
+            }
+        }
+        return items.size() == 0;
+    }
+
+    public Iterable<Item> get_ADD_items() {
+        return Iterables.filter(items, new Predicate<Item>() {
+            @Override
+            public boolean apply(final Item input) {
+                return input.getAction() == ItemAction.ADD;
+            }
+        });
+    }
+
+    public NodeInterval getNodeInterval() {
+        return interval;
+    }
+
     private Item getResultingItem(final boolean mergeMode) {
         return mergeMode ? getResulting_CANCEL_Item() : getResulting_ADD_Item();
     }
@@ -112,34 +152,23 @@ public class ItemsInterval {
         }).orNull();
     }
 
-
     private Item getResulting_ADD_Item() {
 
-        final Set<UUID> repairedIds = new HashSet<UUID>();
-        final ListIterator<Item> it = items.listIterator(items.size());
-
-        while (it.hasPrevious()) {
-            final Item cur = it.previous();
-            switch (cur.getAction()) {
-                case ADD:
-                    // If we found a CANCEL item pointing to that item then don't return it as it was repair (full repair scenario)
-                    if (!repairedIds.contains(cur.getId())) {
-                        return cur;
-                    }
-                    break;
-
-                case CANCEL:
-                    // In all cases populate the set with the id of target item being repaired
-                    if (cur.getLinkedId() != null) {
-                        repairedIds.add(cur.getLinkedId());
-                    }
-                    break;
-            }
-        }
-        return null;
+        //
+        // At this point we pruned the items so that we can have either:
+        // - 2 items (ADD + CANCEL, where CANCEL does NOT point to ADD item-- otherwise this is a cancelling pair that
+        //            would have been removed in mergeCancellingPairs logic)
+        // - 1 ADD item, simple enough we return it
+        // - 1 CANCEL, there is nothing to return but the period will be ignored by the parent
+        // - Nothing at all; this valid, this just means its original items got removed during mergeCancellingPairs logic,
+        //   but its NodeInterval has children so it could not be deleted.
+        //
+        Preconditions.checkState(items.size() <= 2);
+
+        final Item item = items.size() > 0 && items.get(0).getAction() == ItemAction.ADD ? items.get(0) : null;
+        return item;
     }
 
-
     // Just ensure that ADD items precedes CANCEL items
     public void insertSortedItem(final Item item) {
         items.add(item);
@@ -164,6 +193,24 @@ public class ItemsInterval {
         items.clear();
     }
 
+    public void remove(final Item item) {
+        items.remove(item);
+    }
+
+    public Item getCancelledItemIfExists(final UUID targetId) {
+        final Item item = Iterables.tryFind(items, new Predicate<Item>() {
+            @Override
+            public boolean apply(final Item input) {
+                return input.getAction() == ItemAction.CANCEL && input.getLinkedId().equals(targetId);
+            }
+        }).orNull();
+        return item;
+    }
+
+    public int size() {
+        return items.size();
+    }
+
     /**
      * Creates a new item.
      * <p/>
@@ -180,7 +227,7 @@ public class ItemsInterval {
      */
     private Item createNewItem(LocalDate startDate, LocalDate endDate, final boolean mergeMode) {
 
-        final Item item  = getResultingItem(mergeMode);
+        final Item item = getResultingItem(mergeMode);
         if (item == null) {
             return null;
         }
@@ -191,4 +238,5 @@ public class ItemsInterval {
         }
         return result;
     }
+
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java
index 81b17ea..c215a5c 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/ItemsNodeInterval.java
@@ -19,14 +19,15 @@ package org.killbill.billing.invoice.tree;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.util.jackson.ObjectMapper;
 
-import com.fasterxml.jackson.annotation.JsonIdentityInfo;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.google.common.base.Preconditions;
@@ -75,6 +76,10 @@ public class ItemsNodeInterval extends NodeInterval {
      * @param output result list of built items
      */
     public void buildForExistingItems(final List<Item> output) {
+
+        // We start by pruning useless entries to simplify the build phase.
+        pruneTree();
+
         build(new BuildNodeCallback() {
             @Override
             public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
@@ -90,6 +95,7 @@ public class ItemsNodeInterval extends NodeInterval {
         });
     }
 
+
     /**
      * The merge tree is initially constructed by flattening all the existing items and reversing them (CANCEL node).
      * <p/>
@@ -113,19 +119,21 @@ public class ItemsNodeInterval extends NodeInterval {
      * @param output result list of built items
      */
     public void mergeExistingAndProposed(final List<Item> output) {
-        build(new BuildNodeCallback() {
-            @Override
-            public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
-                final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
-                items.buildForMissingInterval(startDate, endDate, output, true);
-            }
 
-            @Override
-            public void onLastNode(final NodeInterval curNode) {
-                final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
-                items.buildFromItems(output, true);
-            }
-        });
+        build(new BuildNodeCallback() {
+                  @Override
+                  public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
+                      final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
+                      items.buildForMissingInterval(startDate, endDate, output, true);
+                  }
+
+                  @Override
+                  public void onLastNode(final NodeInterval curNode) {
+                      final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
+                      items.buildFromItems(output, true);
+                  }
+              }
+             );
     }
 
     /**
@@ -160,7 +168,7 @@ public class ItemsNodeInterval extends NodeInterval {
      *
      * @param newNode a new proposed item
      * @return true if the item was merged and will trigger a repair or false if the proposed item should be kept as such
-     *         and no repair generated.
+     * and no repair generated.
      */
     public boolean addProposedItem(final ItemsNodeInterval newNode) {
 
@@ -262,4 +270,66 @@ public class ItemsNodeInterval extends NodeInterval {
         items.setAdjustment(amount, linkedId);
     }
 
+    //
+    // Before we build the tree, we make a first pass at removing full repaired items; those can come in two shapes:
+    // Case A - The first one, is the mergeCancellingPairs logics which simply look for one CANCEL pointing to one ADD item in the same
+    //   NodeInterval; this is fairly simple, and *only* requires removing those items and remove the interval from the tree when
+    //   it has no more leaves and no more items.
+    // Case B - This is a bit more involved: We look for full repair that happened in pieces; this will translate to an ADD element of a NodeInterval,
+    // whose children completely map the interval (isPartitionedByChildren) and where each child will have a CANCEL item pointing to the ADD.
+    // When we detect such nodes, we delete both the ADD in the parent interval and the CANCEL in the children
+    //
+    private void pruneTree() {
+        walkTree(new WalkCallback() {
+            @Override
+            public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent) {
+
+                if(curNode.isRoot()) {
+                    return;
+                }
+
+                final ItemsInterval curNodeItems = ((ItemsNodeInterval) curNode).getItemsInterval();
+                // Case A:
+                final boolean isEmpty = curNodeItems.mergeCancellingPairs();
+                if (isEmpty && curNode.getLeftChild() == null) {
+                    curNode.getParent().removeChild(curNode);
+                }
+
+                if (!curNode.isPartitionedByChildren()) {
+                    return;
+                }
+
+                // Case B -- look for such case, and if found (foundFullRepairByParts) we fix them below.
+                final Iterator<Item> it =  curNodeItems.get_ADD_items().iterator();
+                while (it.hasNext()) {
+
+                    final Item curAddItem = it.next();
+
+                    NodeInterval curChild = curNode.getLeftChild();
+                    Map<ItemsInterval, Item> toBeRemoved = new HashMap<ItemsInterval, Item>();
+                    boolean foundFullRepairByParts = true;
+                    while (curChild != null) {
+                        final ItemsInterval curChildItems = ((ItemsNodeInterval) curChild).getItemsInterval();
+                        Item cancellingItem = curChildItems.getCancelledItemIfExists(curAddItem.getId());
+                        if (cancellingItem == null) {
+                            foundFullRepairByParts = false;
+                            break;
+                        }
+                        toBeRemoved.put(curChildItems, cancellingItem);
+                        curChild = curChild.getRightSibling();
+                    }
+
+                    if (foundFullRepairByParts) {
+                        for (ItemsInterval curItemsInterval : toBeRemoved.keySet()) {
+                            curItemsInterval.remove(toBeRemoved.get(curItemsInterval));
+                            if (curItemsInterval.size() == 0) {
+                                curNode.removeChild(curItemsInterval.getNodeInterval());
+                            }
+                        }
+                        curNodeItems.remove(curAddItem);
+                    }
+                }
+            }
+        });
+    }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/NodeInterval.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/NodeInterval.java
index 05ff456..884e31d 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/NodeInterval.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/NodeInterval.java
@@ -49,6 +49,7 @@ public class NodeInterval {
      * Build the tree by calling the callback on the last node in the tree or remaining part with no children.
      *
      * @param callback the callback which perform the build logic.
+     * @return whether or not the parent NodeInterval should ignore the period covered by the child (NodeInterval)
      */
     public void build(final BuildNodeCallback callback) {
 
@@ -66,14 +67,16 @@ public class NodeInterval {
                 callback.onMissingInterval(this, curDate, curChild.getStart());
             }
             curChild.build(callback);
+            // Note that skip to child endDate, meaning that we always consider the child [start end]
             curDate = curChild.getEnd();
             curChild = curChild.getRightSibling();
         }
 
-        // Finally if there is a hole at the end, we build the missing piece from ourself
+        // Finally if there is a hole at the end, we build the missing piece from ourselves
         if (curDate.compareTo(end) < 0) {
             callback.onMissingInterval(this, curDate, end);
         }
+        return;
     }
 
     /**
@@ -82,7 +85,7 @@ public class NodeInterval {
      * @param newNode  the node to be added
      * @param callback the callback that will allow to specify insertion and return behavior.
      * @return true if node was inserted. Note that this is driven by the callback, this method is generic
-     *         and specific behavior can be tuned through specific callbacks.
+     * and specific behavior can be tuned through specific callbacks.
      */
     public boolean addNode(final NodeInterval newNode, final AddNodeCallback callback) {
 
@@ -146,6 +149,44 @@ public class NodeInterval {
         }
     }
 
+    public void removeChild(final NodeInterval toBeRemoved) {
+
+        NodeInterval prevChild = null;
+        NodeInterval curChild = leftChild;
+        while (curChild != null) {
+            if (curChild.isSame(toBeRemoved)) {
+                if (prevChild == null) {
+                    leftChild = curChild.getRightSibling();
+                } else {
+                    prevChild.rightSibling = curChild.getRightSibling();
+                }
+                break;
+            }
+            prevChild = curChild;
+            curChild = curChild.getRightSibling();
+        }
+
+    }
+
+    @JsonIgnore
+    public boolean isPartitionedByChildren() {
+
+        if (leftChild == null) {
+            return false;
+        }
+
+        LocalDate curDate = start;
+        NodeInterval curChild = leftChild;
+        while (curChild != null) {
+            if (curChild.getStart().compareTo(curDate) > 0) {
+                return false;
+            }
+            curDate = curChild.getEnd();
+            curChild = curChild.getRightSibling();
+        }
+        return (curDate.compareTo(end) == 0);
+    }
+
     /**
      * Return the first node satisfying the date and match callback.
      *
@@ -224,7 +265,6 @@ public class NodeInterval {
         }
     }
 
-
     public boolean isItemContained(final NodeInterval newNode) {
         return (newNode.getStart().compareTo(start) >= 0 &&
                 newNode.getStart().compareTo(end) <= 0 &&
@@ -240,6 +280,13 @@ public class NodeInterval {
     }
 
     @JsonIgnore
+    public boolean isSame(final NodeInterval otherNode) {
+        return ((otherNode.getStart().compareTo(start) == 0 &&
+                 otherNode.getEnd().compareTo(end) == 0) &&
+                otherNode.getParent().equals(parent));
+    }
+
+    @JsonIgnore
     public boolean isRoot() {
         return parent == null;
     }
@@ -335,6 +382,7 @@ public class NodeInterval {
      * Provides callback for walking the tree.
      */
     public interface WalkCallback {
+
         public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent);
     }
 
@@ -342,6 +390,7 @@ public class NodeInterval {
      * Provides custom logic for the search.
      */
     public interface SearchCallback {
+
         /**
          * Custom logic to decide which node to return.
          *
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java b/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
index 5079499..2c0671b 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/tree/SubscriptionItemTree.java
@@ -17,8 +17,10 @@
 package org.killbill.billing.invoice.tree;
 
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -49,7 +51,7 @@ public class SubscriptionItemTree {
     private List<Item> items;
 
     private List<InvoiceItem> existingFixedItems;
-    private List<InvoiceItem> remainingFixedItems;
+    private Map<LocalDate, InvoiceItem> remainingFixedItems;
     private List<InvoiceItem> pendingItemAdj;
 
     private static final Comparator<InvoiceItem> INVOICE_ITEM_COMPARATOR = new Comparator<InvoiceItem>() {
@@ -64,7 +66,9 @@ public class SubscriptionItemTree {
             if (itemTypeComp != 0) {
                 return itemTypeComp;
             }
-            Preconditions.checkState(false, "Unexpected list of items for subscription " + o1.getSubscriptionId());
+            Preconditions.checkState(false, "Unexpected list of items for subscription " + o1.getSubscriptionId() +
+                                            ", type(item1) = " + o1.getInvoiceItemType() + ", start(item1) = " + o1.getStartDate() +
+                                            ", type(item12) = " + o2.getInvoiceItemType() + ", start(item2) = " + o2.getStartDate());
             // Never reached...
             return 0;
         }
@@ -75,7 +79,7 @@ public class SubscriptionItemTree {
         this.root = new ItemsNodeInterval();
         this.items = new LinkedList<Item>();
         this.existingFixedItems = new LinkedList<InvoiceItem>();
-        this.remainingFixedItems = new LinkedList<InvoiceItem>();
+        this.remainingFixedItems = new HashMap<LocalDate, InvoiceItem>();
         this.pendingItemAdj = new LinkedList<InvoiceItem>();
         this.isBuilt = false;
     }
@@ -173,7 +177,7 @@ public class SubscriptionItemTree {
                     }
                 }).orNull();
                 if (existingItem == null) {
-                    remainingFixedItems.add(invoiceItem);
+                    remainingFixedItems.put(invoiceItem.getStartDate(), invoiceItem);
                 }
                 break;
 
@@ -194,7 +198,7 @@ public class SubscriptionItemTree {
     public List<InvoiceItem> getView() {
 
         final List<InvoiceItem> tmp = new LinkedList<InvoiceItem>();
-        tmp.addAll(remainingFixedItems);
+        tmp.addAll(remainingFixedItems.values());
         tmp.addAll(Collections2.filter(Collections2.transform(items, new Function<Item, InvoiceItem>() {
             @Override
             public InvoiceItem apply(final Item input) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java
index 4e016f7..69beb39 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/ContiguousIntervalConsumableInArrear.java
@@ -102,7 +102,6 @@ public class ContiguousIntervalConsumableInArrear {
         if (targetDate.isBefore(startDate)) {
             return this;
         }
-        // STEPH_USAGE should we billed up to endDate to targetDate is targetDate < endDate./ What does targetDate means in arrear
         final LocalDate endDate = closedInterval ? new LocalDate(billingEvents.get(billingEvents.size() - 1).getEffectiveDate(), getAccountTimeZone()) : targetDate;
 
         final BillingIntervalDetail bid = new BillingIntervalDetail(startDate, endDate, targetDate, getBCD(), usage.getBillingPeriod());
@@ -147,10 +146,12 @@ public class ContiguousIntervalConsumableInArrear {
                 toBeBilledUsage = toBeBilledUsage.add(toBeBilledForUnit);
             }
             // Retrieves current price amount billed for that period of time (and usage section)
-            final BigDecimal billedUsage = computeBilledUsage(ru.getStartDate(), ru.getEndDate(), existingUsage);
+            final Iterable<InvoiceItem> billedItems = getBilledItems(ru.getStartDate(), ru.getEndDate(), existingUsage);
+            final BigDecimal billedUsage = computeBilledUsage(billedItems);
 
-            // Compare the two and add the missing piece if required.
-            if (billedUsage.compareTo(toBeBilledUsage) < 0) {
+            // Compare the two and add the missing piece if required. If there has never been any billed item for the period
+            // and if there is nothing to bill for we would also insert a $0 amount
+            if (!billedItems.iterator().hasNext() || billedUsage.compareTo(toBeBilledUsage) < 0) {
                 InvoiceItem item = new UsageInvoiceItem(invoiceId, getAccountId(), getBundleId(), getSubscriptionId(), getPlanName(),
                                                         getPhaseName(), usage.getName(), ru.getStartDate(), ru.getEndDate(), toBeBilledUsage.subtract(billedUsage), getCurrency());
                 result.add(item);
@@ -165,6 +166,10 @@ public class ContiguousIntervalConsumableInArrear {
      * @return a list of {@code RolledUpUsage} for each period (between two transitions) * each unitType.
      */
     List<RolledUpUsage> getRolledUpUsage() {
+        // There needs to be at least two transitions to define an interval to bill
+        if (transitionTimes.size() <= 1) {
+            return Collections.emptyList();
+        }
 
         final Iterable<DateTime> transitions = Iterables.transform(transitionTimes, new Function<LocalDate, DateTime>() {
             @Override
@@ -172,7 +177,6 @@ public class ContiguousIntervalConsumableInArrear {
                 return localDateToEndOfDayInAccountTimezone(input, getAccountTimeZone());
             }
         });
-        // STEPH_USAGE optimized api takes set of unitTypes -- for usage section-- and list of transitions date. Should we use dateTime or LocalDate?
         return usageApi.getAllUsageForSubscription(getSubscriptionId(), unitTypes, ImmutableList.copyOf(transitions), context);
     }
 
@@ -210,36 +214,38 @@ public class ContiguousIntervalConsumableInArrear {
 
     /**
      *
-     * @param startDate the startDate of of billed period
-     * @param endDate the endDate of of billed period
-     * @param existingUsage the list of invoiceItem for that subscription
+     * @param filteredUsageForInterval the list of invoiceItem to consider
      * @return the price amount that was already billed for that period and usage section (across unitTypes)
      */
     @VisibleForTesting
-    BigDecimal computeBilledUsage(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
+    BigDecimal computeBilledUsage(final Iterable<InvoiceItem> filteredUsageForInterval) {
+
+        Preconditions.checkState(isBuilt.get());
+        BigDecimal billedAmount = BigDecimal.ZERO;
+        for (InvoiceItem ii : filteredUsageForInterval) {
+            billedAmount = billedAmount.add(ii.getAmount());
+        }
+        // Return the billed $ amount (not the # of units)
+        return billedAmount;
+    }
+
+    Iterable<InvoiceItem> getBilledItems(final LocalDate startDate, final LocalDate endDate, final List<InvoiceItem> existingUsage) {
 
         Preconditions.checkState(isBuilt.get());
-        final Iterable<InvoiceItem> filteredUsageForInterval = Iterables.filter(existingUsage, new Predicate<InvoiceItem>() {
+        return Iterables.filter(existingUsage, new Predicate<InvoiceItem>() {
             @Override
             public boolean apply(final InvoiceItem input) {
                 if (input.getInvoiceItemType() != InvoiceItemType.USAGE) {
                     return false;
                 }
 
-                // STEPH_USAGE what happens if we discover usage period that overlap (one side or both side) the [startDate, endDate] interval
+                // STEPH what happens if we discover usage period that overlap (one side or both side) the [startDate, endDate] interval
                 final UsageInvoiceItem usageInput = (UsageInvoiceItem) input;
                 return usageInput.getUsageName().equals(usage.getName()) &&
                        usageInput.getStartDate().compareTo(startDate) >= 0 &&
                        usageInput.getEndDate().compareTo(endDate) <= 0;
             }
         });
-
-        BigDecimal billedAmount = BigDecimal.ZERO;
-        for (InvoiceItem ii : filteredUsageForInterval) {
-            billedAmount = billedAmount.add(ii.getAmount());
-        }
-        // Return the billed $ amount (not the # of units)
-        return billedAmount;
     }
 
     @VisibleForTesting
@@ -324,7 +330,17 @@ public class ContiguousIntervalConsumableInArrear {
             Preconditions.checkArgument(o instanceof RolledUpUsageForUnitTypes);
             final RolledUpUsageForUnitTypes other = (RolledUpUsageForUnitTypes) o;
             // We will check later intervals don't overlap.
-            return getEndDate().compareTo(other.getStartDate());
+            int i = getEndDate().compareTo(other.getStartDate());
+            if (i != 0) {
+                return i;
+            } else {
+                i = getEndDate().compareTo(other.getEndDate());
+                if (i != 0) {
+                    return i;
+                } else {
+                    return getStartDate().compareTo(other.getStartDate());
+                }
+            }
         }
     }
 
@@ -371,7 +387,7 @@ public class ContiguousIntervalConsumableInArrear {
     }
 
     static DateTime localDateToEndOfDayInAccountTimezone(final LocalDate input, final DateTimeZone accountTimeZone) {
-        final DateTime dateTimeInAccountTimeZone = new DateTime(input.getYear(), input.getMonthOfYear(), input.getDayOfMonth(), 23, 59, 59, accountTimeZone);
+        final DateTime dateTimeInAccountTimeZone = new DateTime(input.getYear(), input.getMonthOfYear(), input.getDayOfMonth(), 0, 0, 0, accountTimeZone);
         return new DateTime(dateTimeInAccountTimeZone, DateTimeZone.UTC);
     }
 
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
index a96b80e..b3a1f5a 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/UsageUtils.java
@@ -42,25 +42,6 @@ import com.google.common.collect.Lists;
 
 public class UsageUtils {
 
-    public static Map<String, Usage> getKnownUsages(final BillingEventSet billingEvents, @Nullable final Predicate filter) {
-        final Iterable<Usage> usages = Iterables.concat(Iterables.transform(billingEvents, new Function<BillingEvent, List<Usage>>() {
-            @Override
-            public List<Usage> apply(final BillingEvent input) {
-                return input.getUsages();
-            }
-        }));
-
-        final Iterable<Usage> filteredUsages = (filter != null) ? Iterables.filter(usages, filter) : usages;
-
-        final Map<String, Usage> result = (filteredUsages.iterator().hasNext()) ? new HashMap<String, Usage>() : Collections.<String, Usage>emptyMap();
-        final Iterator<Usage> iterator = filteredUsages.iterator();
-        while (iterator.hasNext()) {
-            final Usage next = iterator.next();
-            result.put(next.getName(), next);
-        }
-        return result;
-    }
-
     public static List<TieredBlock> getConsumableInArrearTieredBlocks(final Usage usage, final String unitType) {
 
         Preconditions.checkArgument(usage.getBillingMode() == BillingMode.IN_ARREAR && usage.getUsageType() == UsageType.CONSUMABLE);
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
index 2ce721a..d5d3a7f 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceItemSqlDao.sql.stg
@@ -8,6 +8,7 @@ tableFields(prefix) ::= <<
 , <prefix>account_id
 , <prefix>bundle_id
 , <prefix>subscription_id
+, <prefix>description
 , <prefix>plan_name
 , <prefix>phase_name
 , <prefix>usage_name
@@ -27,6 +28,7 @@ tableValues() ::= <<
 , :accountId
 , :bundleId
 , :subscriptionId
+, :description
 , :planName
 , :phaseName
 , :usageName
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index 8d1383d..fe0410f 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -35,6 +35,7 @@ getByPaymentId() ::= <<
   FROM <tableName()>
   WHERE payment_id = :paymentId
   <AND_CHECK_TENANT()>
+  <defaultOrderBy()>
   ;
 >>
 
@@ -43,6 +44,7 @@ getPaymentsForCookieId() ::= <<
   FROM <tableName()>
   WHERE payment_cookie_id = :paymentCookieId
   <AND_CHECK_TENANT()>
+  <defaultOrderBy()>
   ;
 >>
 
@@ -51,6 +53,7 @@ getPaymentsForInvoice() ::= <<
   FROM <tableName()>
   WHERE invoice_id = :invoiceId
   <AND_CHECK_TENANT()>
+  <defaultOrderBy()>
   ;
 >>
 
@@ -59,6 +62,7 @@ getInvoicePayments() ::= <<
     FROM <tableName()>
     WHERE payment_id = :paymentId
     <AND_CHECK_TENANT()>
+   <defaultOrderBy()>
     ;
 >>
 
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
index b940549..ed6f4b8 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
@@ -9,6 +9,7 @@ CREATE TABLE invoice_items (
     account_id char(36) NOT NULL,
     bundle_id char(36),
     subscription_id char(36),
+    description varchar(255),
     plan_name varchar(50),
     phase_name varchar(50),
     usage_name varchar(50),
@@ -60,7 +61,7 @@ CREATE TABLE invoice_payments (
     amount numeric(15,9) NOT NULL,
     currency char(3) NOT NULL,
     processed_currency char(3) NOT NULL,
-    payment_cookie_id char(36) DEFAULT NULL,
+    payment_cookie_id varchar(255) DEFAULT NULL,
     linked_invoice_payment_id char(36) DEFAULT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
@@ -69,7 +70,7 @@ CREATE TABLE invoice_payments (
     PRIMARY KEY(record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
 CREATE UNIQUE INDEX invoice_payments_id ON invoice_payments(id);
-CREATE INDEX invoice_payments ON invoice_payments(payment_id);
+CREATE UNIQUE INDEX invoice_payments ON invoice_payments(payment_id, type);
 CREATE INDEX invoice_payments_invoice_id ON invoice_payments(invoice_id);
 CREATE INDEX invoice_payments_reversals ON invoice_payments(linked_invoice_payment_id);
 CREATE INDEX invoice_payments_tenant_account_record_id ON invoice_payments(tenant_record_id, account_record_id);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
index bef93eb..197f811 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/invoice/TestDefaultInvoicePaymentApi.java
@@ -17,20 +17,18 @@
 package org.killbill.billing.invoice.api.invoice;
 
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -112,12 +110,12 @@ public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB
         final InvoicePayment payment = createAndPersistPayment(invoiceInternalApi, clock, invoice.getId(), invoiceAmount, CURRENCY, internalCallContext);
 
         // Verify the initial invoice balance
-        final BigDecimal initialInvoiceBalance = invoicePaymentApi.getInvoice(invoice.getId(), callContext).getBalance();
+        final BigDecimal initialInvoiceBalance = invoiceInternalApi.getInvoiceById(invoice.getId(), internalCallContext).getBalance();
         Assert.assertEquals(initialInvoiceBalance.compareTo(BigDecimal.ZERO), 0);
 
         // Create a full refund with no adjustment
         final InvoicePayment refund = invoiceInternalApi.createRefund(payment.getPaymentId(), refundAmount, adjusted, invoiceItemIdsWithAmounts,
-                                                                      UUID.randomUUID(), internalCallContext);
+                                                                      UUID.randomUUID().toString(), internalCallContext);
         Assert.assertEquals(refund.getAmount().compareTo(refundAmount.negate()), 0);
         Assert.assertEquals(refund.getCurrency(), CURRENCY);
         Assert.assertEquals(refund.getInvoiceId(), invoice.getId());
@@ -125,7 +123,7 @@ public class TestDefaultInvoicePaymentApi extends InvoiceTestSuiteWithEmbeddedDB
         Assert.assertEquals(refund.getType(), InvoicePaymentType.REFUND);
 
         // Verify the current invoice balance
-        final BigDecimal newInvoiceBalance = invoicePaymentApi.getInvoice(invoice.getId(), callContext).getBalance();
+        final BigDecimal newInvoiceBalance = invoiceInternalApi.getInvoiceById(invoice.getId(), internalCallContext).getBalance();
         Assert.assertEquals(newInvoiceBalance.compareTo(finalInvoiceAmount), 0);
     }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
index 066a99c..5784606 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/migration/TestDefaultInvoiceMigrationApi.java
@@ -101,14 +101,6 @@ public class TestDefaultInvoiceMigrationApi extends InvoiceTestSuiteWithEmbedded
         Assert.assertEquals(unpaid.size(), 2);
     }
 
-    // Check migration invoice IS returned for payment api calls
-    @Test(groups = "slow")
-    public void testPaymentApi() {
-        final List<Invoice> allByAccount = invoicePaymentApi.getAllInvoicesByAccount(accountId, callContext);
-        Assert.assertEquals(allByAccount.size(), 2);
-        Assert.assertTrue(checkContains(allByAccount, regularInvoiceId));
-        Assert.assertTrue(checkContains(allByAccount, migrationInvoiceId));
-    }
 
     // ACCOUNT balance should reflect total of migration and non-migration invoices
     @Test(groups = "slow")
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/MockInvoicePaymentApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/MockInvoicePaymentApi.java
index d284698..4b86b35 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/MockInvoicePaymentApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/MockInvoicePaymentApi.java
@@ -16,19 +16,11 @@
 
 package org.killbill.billing.invoice.api;
 
-import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
-
-import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.invoice.model.DefaultInvoicePayment;
-import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 
 public class MockInvoicePaymentApi implements InvoicePaymentApi {
@@ -41,28 +33,6 @@ public class MockInvoicePaymentApi implements InvoicePaymentApi {
     }
 
     @Override
-    public List<Invoice> getAllInvoicesByAccount(final UUID accountId, final TenantContext context) {
-        final ArrayList<Invoice> result = new ArrayList<Invoice>();
-
-        for (final Invoice invoice : invoices) {
-            if (accountId.equals(invoice.getAccountId())) {
-                result.add(invoice);
-            }
-        }
-        return result;
-    }
-
-    @Override
-    public Invoice getInvoice(final UUID invoiceId, final TenantContext context) {
-        for (final Invoice invoice : invoices) {
-            if (invoiceId.equals(invoice.getId())) {
-                return invoice;
-            }
-        }
-        return null;
-    }
-
-    @Override
     public List<InvoicePayment> getInvoicePayments(final UUID paymentId, final TenantContext context) {
         final List<InvoicePayment> result = new LinkedList<InvoicePayment>();
         for (final InvoicePayment invoicePayment : invoicePayments) {
@@ -74,82 +44,7 @@ public class MockInvoicePaymentApi implements InvoicePaymentApi {
     }
 
     @Override
-    public InvoicePayment getInvoicePaymentForAttempt(final UUID paymentId, final TenantContext context) {
-        for (final InvoicePayment invoicePayment : invoicePayments) {
-            if (paymentId.equals(invoicePayment.getPaymentId()) && invoicePayment.getType() == InvoicePaymentType.ATTEMPT) {
-                return invoicePayment;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public InvoicePayment createChargeback(final UUID invoicePaymentId, final BigDecimal amount, final CallContext context) throws InvoiceApiException {
-        InvoicePayment existingPayment = null;
-        for (final InvoicePayment payment : invoicePayments) {
-            if (payment.getId() == invoicePaymentId) {
-                existingPayment = payment;
-                break;
-            }
-        }
-
-        if (existingPayment != null) {
-            invoicePayments.add(new DefaultInvoicePayment(UUID.randomUUID(), InvoicePaymentType.CHARGED_BACK, null, null, DateTime.now(DateTimeZone.UTC), amount,
-                                                          existingPayment.getCurrency(), existingPayment.getProcessedCurrency(), null, existingPayment.getId()));
-        }
-
-        return existingPayment;
-    }
-
-    @Override
-    public InvoicePayment createChargeback(final UUID invoicePaymentId, final CallContext context) throws InvoiceApiException {
-        InvoicePayment existingPayment = null;
-        for (final InvoicePayment payment : invoicePayments) {
-            if (payment.getId() == invoicePaymentId) {
-                existingPayment = payment;
-            }
-        }
-
-        if (existingPayment != null) {
-            this.createChargeback(invoicePaymentId, existingPayment.getAmount(), context);
-        }
-
-        return existingPayment;
-    }
-
-    @Override
-    public BigDecimal getRemainingAmountPaid(final UUID invoicePaymentId, final TenantContext context) {
-        BigDecimal amount = BigDecimal.ZERO;
-        for (final InvoicePayment payment : invoicePayments) {
-            if (payment.getId().equals(invoicePaymentId)) {
-                amount = amount.add(payment.getAmount());
-            }
-
-            if (payment.getLinkedInvoicePaymentId().equals(invoicePaymentId)) {
-                amount = amount.add(payment.getAmount());
-            }
-        }
-
-        return amount;
-    }
-
-    @Override
-    public List<InvoicePayment> getChargebacksByAccountId(final UUID accountId, final TenantContext context) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public UUID getAccountIdFromInvoicePaymentId(final UUID uuid, final TenantContext context) throws InvoiceApiException {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public List<InvoicePayment> getChargebacksByPaymentId(final UUID paymentId, final TenantContext context) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public InvoicePayment getChargebackById(final UUID chargebackId, final TenantContext context) {
+    public List<InvoicePayment> getInvoicePaymentsByAccount(final UUID uuid, final TenantContext tenantContext) {
         throw new UnsupportedOperationException();
     }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index 0f751e8..79a1ca4 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -22,26 +22,28 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.DefaultCallContext;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
 import org.killbill.billing.util.api.TagApiException;
 import org.killbill.billing.util.callcontext.CallContext;
-import org.killbill.billing.callcontext.DefaultCallContext;
-import org.killbill.clock.ClockMock;
 import org.killbill.billing.util.currency.KillBillMoney;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
+import org.killbill.clock.ClockMock;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
 
 import static org.testng.Assert.assertEquals;
 
@@ -66,8 +68,8 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
 
         // Post an external charge
         final BigDecimal externalChargeAmount = BigDecimal.TEN;
-        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharge(accountId, externalChargeAmount, UUID.randomUUID().toString(),
-                                                                                          clock.getUTCToday(), accountCurrency, callContext);
+        final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, null, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
+        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), callContext).get(0);
         verifyExternalChargeOnNewInvoice(accountBalance, null, externalChargeAmount, externalChargeInvoiceItem);
     }
 
@@ -79,9 +81,8 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
         // Post an external charge
         final BigDecimal externalChargeAmount = BigDecimal.TEN;
         final UUID bundleId = UUID.randomUUID();
-        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalChargeForBundle(accountId, bundleId, externalChargeAmount,
-                                                                                                   UUID.randomUUID().toString(), clock.getUTCToday(),
-                                                                                                   accountCurrency, callContext);
+        final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(null, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
+        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), callContext).get(0);
         verifyExternalChargeOnNewInvoice(accountBalance, bundleId, externalChargeAmount, externalChargeInvoiceItem);
     }
 
@@ -116,13 +117,12 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
         Assert.assertEquals(accountBalance, invoiceBalance);
         // Post an external charge
         final BigDecimal externalChargeAmount = BigDecimal.TEN;
-        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalChargeForInvoice(accountId, invoiceId,
-                                                                                                    externalChargeAmount, UUID.randomUUID().toString(),
-                                                                                                    clock.getUTCToday(), accountCurrency, callContext);
+        final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, null, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
+        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), callContext).get(0);
         verifyExternalChargeOnExistingInvoice(invoiceBalance, null, externalChargeAmount, externalChargeInvoiceItem);
     }
 
-    @Test(groups = "slow", enabled= false)
+    @Test(groups = "slow", enabled = false)
     public void testOriginalAmountCharged() throws Exception {
 
         final Invoice initialInvoice = invoiceUserApi.getInvoice(invoiceId, callContext);
@@ -136,9 +136,8 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
         CallContext newCallContextLater = new DefaultCallContext(callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getUserToken(), clock);
         // Post an external charge
         final BigDecimal externalChargeAmount = BigDecimal.TEN;
-        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalChargeForInvoice(accountId, invoiceId,
-                                                                                                    externalChargeAmount, UUID.randomUUID().toString(),
-                                                                                                    clock.getUTCToday(), accountCurrency, newCallContextLater);
+        final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, null, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
+        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), newCallContextLater).get(0);
 
         final Invoice newInvoice = invoiceUserApi.getInvoice(invoiceId, callContext);
         final BigDecimal newOriginalAmountCharged = newInvoice.getOriginalChargedAmount();
@@ -162,9 +161,8 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
         // Post an external charge
         final BigDecimal externalChargeAmount = BigDecimal.TEN;
         final UUID bundleId = UUID.randomUUID();
-        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalChargeForInvoiceAndBundle(accountId, invoiceId, bundleId,
-                                                                                                             externalChargeAmount, UUID.randomUUID().toString(),
-                                                                                                             clock.getUTCToday(), accountCurrency, callContext);
+        final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceId, accountId, bundleId, UUID.randomUUID().toString(), clock.getUTCToday(), externalChargeAmount, accountCurrency);
+        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItem>of(externalCharge), callContext).get(0);
         verifyExternalChargeOnExistingInvoice(invoiceBalance, bundleId, externalChargeAmount, externalChargeInvoiceItem);
     }
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
index 6edc2b3..98e1e20 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/MockInvoiceDao.java
@@ -28,8 +28,6 @@ import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
-import org.killbill.bus.api.PersistentBus;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
@@ -39,6 +37,7 @@ import org.killbill.billing.invoice.api.user.DefaultInvoiceCreationEvent;
 import org.killbill.billing.util.entity.DefaultPagination;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.MockEntityDaoBase;
+import org.killbill.bus.api.PersistentBus;
 
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
@@ -60,15 +59,12 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
 
     @Override
     public void createInvoice(final InvoiceModelDao invoice, final List<InvoiceItemModelDao> invoiceItems,
-                              final List<InvoicePaymentModelDao> invoicePayments, final boolean isRealInvoice, final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions, final InternalCallContext context) {
+                              final boolean isRealInvoice, final Map<UUID, List<DateTime>> callbackDateTimePerSubscriptions, final InternalCallContext context) {
         synchronized (monitor) {
             invoices.put(invoice.getId(), invoice);
             for (final InvoiceItemModelDao invoiceItemModelDao : invoiceItems) {
                 items.put(invoiceItemModelDao.getId(), invoiceItemModelDao);
             }
-            for (final InvoicePaymentModelDao paymentModelDao : invoicePayments) {
-                payments.put(paymentModelDao.getId(), paymentModelDao);
-            }
             accountRecordIds.put(invoice.getAccountId(), context.getAccountRecordId());
         }
         try {
@@ -87,7 +83,6 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
         }
     }
 
-
     @Override
     public InvoiceModelDao getByNumber(final Integer number, final InternalTenantContext context) {
         synchronized (monitor) {
@@ -126,7 +121,6 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     @Override
     public List<InvoiceModelDao> getInvoicesByAccount(final LocalDate fromDate, final InternalTenantContext context) {
         final List<InvoiceModelDao> invoicesForAccount = new ArrayList<InvoiceModelDao>();
-
         synchronized (monitor) {
             final UUID accountId = accountRecordIds.inverse().get(context.getAccountRecordId());
             for (final InvoiceModelDao invoice : getAll(context)) {
@@ -201,6 +195,23 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     }
 
     @Override
+    public List<InvoicePaymentModelDao> getInvoicePaymentsByAccount(final InternalTenantContext context) {
+
+        throw new UnsupportedOperationException();
+/*
+        InvoicePaymentModelDao does not export accountId ?
+
+        final List<InvoicePaymentModelDao> invoicesForAccount = new ArrayList<InvoicePaymentModelDao>();
+        synchronized (monitor) {
+            final UUID accountId = accountRecordIds.inverse().get(context.getAccountRecordId());
+            for (final InvoicePaymentModelDao payment : payments.values()) {
+            }
+        }
+        return null;
+*/
+    }
+
+    @Override
     public void notifyOfPayment(final InvoicePaymentModelDao invoicePayment, final InternalCallContext context) {
         synchronized (monitor) {
             payments.put(invoicePayment.getId(), invoicePayment);
@@ -253,7 +264,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     }
 
     @Override
-    public InvoicePaymentModelDao postChargeback(final UUID invoicePaymentId, final BigDecimal amount, final InternalCallContext context) throws InvoiceApiException {
+    public InvoicePaymentModelDao postChargeback(final UUID invoicePaymentId, final BigDecimal amount, final Currency currency, final InternalCallContext context) throws InvoiceApiException {
         throw new UnsupportedOperationException();
     }
 
@@ -288,9 +299,8 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     }
 
     @Override
-    public InvoiceItemModelDao insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final UUID bundleId,
-                                                    @Nullable final String description, final BigDecimal amount, final LocalDate effectiveDate,
-                                                    final Currency currency, final InternalCallContext context) {
+    public List<InvoiceItemModelDao> insertExternalCharges(final UUID accountId, final LocalDate effectiveDate,
+                                                           final Iterable<InvoiceItemModelDao> charges, final InternalCallContext context) {
         throw new UnsupportedOperationException();
     }
 
@@ -319,7 +329,7 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
 
     @Override
     public InvoicePaymentModelDao createRefund(final UUID paymentId, final BigDecimal amount, final boolean isInvoiceAdjusted,
-                                               final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final UUID paymentCookieId,
+                                               final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final String transactionExternalKey,
                                                final InternalCallContext context)
             throws InvoiceApiException {
         return null;
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
index 051ec3d..08c30dc 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceDao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -26,13 +28,6 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-import org.killbill.billing.catalog.api.BillingMode;
-import org.mockito.Mockito;
-import org.skife.jdbi.v2.exceptions.TransactionFailedException;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalCallContext;
@@ -40,13 +35,13 @@ import org.killbill.billing.catalog.DefaultPrice;
 import org.killbill.billing.catalog.MockInternationalPrice;
 import org.killbill.billing.catalog.MockPlan;
 import org.killbill.billing.catalog.MockPlanPhase;
+import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.clock.ClockMock;
 import org.killbill.billing.entity.EntityPersistenceException;
 import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
 import org.killbill.billing.invoice.MockBillingEventSet;
@@ -60,6 +55,7 @@ import org.killbill.billing.invoice.model.CreditAdjInvoiceItem;
 import org.killbill.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.invoice.model.DefaultInvoicePayment;
+import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.invoice.model.RecurringInvoiceItem;
 import org.killbill.billing.invoice.model.RepairAdjInvoiceItem;
@@ -68,7 +64,14 @@ import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
 import org.killbill.billing.util.currency.KillBillMoney;
+import org.killbill.clock.ClockMock;
+import org.mockito.Mockito;
+import org.skife.jdbi.v2.exceptions.TransactionFailedException;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
 import static org.killbill.billing.invoice.TestInvoiceHelper.FIVE;
@@ -626,7 +629,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         balance = invoiceDao.getAccountBalance(accountId, context);
         assertEquals(balance.compareTo(new BigDecimal("0.00")), 0);
 
-        invoiceDao.createRefund(paymentId, refund1, withAdjustment, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID(), context);
+        invoiceDao.createRefund(paymentId, refund1, withAdjustment, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID().toString(), context);
         balance = invoiceDao.getAccountBalance(accountId, context);
         if (withAdjustment) {
             assertEquals(balance.compareTo(BigDecimal.ZERO), 0);
@@ -686,7 +689,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // PAss a null value to let invoice calculate the amount to adjust
         itemAdjustment.put(item2.getId(), null);
 
-        invoiceDao.createRefund(paymentId, refundAmount, true, itemAdjustment, UUID.randomUUID(), context);
+        invoiceDao.createRefund(paymentId, refundAmount, true, itemAdjustment, UUID.randomUUID().toString(), context);
         balancePriorRefund = invoiceDao.getAccountBalance(accountId, context);
 
         final boolean partialRefund = refundAmount.compareTo(amount) < 0;
@@ -798,7 +801,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         assertEquals(cba.compareTo(new BigDecimal("10.00")), 0);
 
         // PARTIAL REFUND on the payment
-        invoiceDao.createRefund(paymentId, refundAmount, withAdjustment, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID(), context);
+        invoiceDao.createRefund(paymentId, refundAmount, withAdjustment, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID().toString(), context);
 
         balance = invoiceDao.getAccountBalance(accountId, context);
         assertEquals(balance.compareTo(expectedFinalBalance), 0);
@@ -809,22 +812,25 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testExternalChargeWithCBA() throws InvoiceApiException, EntityPersistenceException {
-
         final UUID accountId = account.getId();
         final UUID bundleId = UUID.randomUUID();
 
         invoiceDao.insertCredit(accountId, null, new BigDecimal("20.0"), new LocalDate(), Currency.USD, context);
 
-        final InvoiceItemModelDao charge = invoiceDao.insertExternalCharge(accountId, null, bundleId, "bla", new BigDecimal("15.0"), clock.getUTCNow().toLocalDate(), Currency.USD, context);
+        final String description = UUID.randomUUID().toString();
+        final InvoiceItemModelDao externalCharge = new InvoiceItemModelDao(new ExternalChargeInvoiceItem(null, accountId, bundleId, description, clock.getUTCToday(), new BigDecimal("15.0"), Currency.USD));
+        final InvoiceItemModelDao charge = invoiceDao.insertExternalCharges(accountId, clock.getUTCToday(), ImmutableList.<InvoiceItemModelDao>of(externalCharge), context).get(0);
 
         final InvoiceModelDao newInvoice = invoiceDao.getById(charge.getInvoiceId(), context);
         final List<InvoiceItemModelDao> items = newInvoice.getInvoiceItems();
         assertEquals(items.size(), 2);
         for (final InvoiceItemModelDao cur : items) {
-            if (!cur.getId().equals(charge.getId())) {
+            if (cur.getId().equals(charge.getId())) {
+                assertEquals(cur.getType(), InvoiceItemType.EXTERNAL_CHARGE);
+                assertEquals(cur.getDescription(), description);
+            } else {
                 assertEquals(cur.getType(), InvoiceItemType.CBA_ADJ);
                 assertTrue(cur.getAmount().compareTo(new BigDecimal("-15.00")) == 0);
-                break;
             }
         }
     }
@@ -1310,7 +1316,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         // AND THEN THIRD THE REFUND
         final Map<UUID, BigDecimal> invoiceItemMap = new HashMap<UUID, BigDecimal>();
         invoiceItemMap.put(invoiceItem.getId(), new BigDecimal("239.00"));
-        invoiceDao.createRefund(paymentId, paymentAmount, true, invoiceItemMap, UUID.randomUUID(), context);
+        invoiceDao.createRefund(paymentId, paymentAmount, true, invoiceItemMap, UUID.randomUUID().toString(), context);
 
         final InvoiceModelDao savedInvoice = invoiceDao.getById(invoiceId, context);
         assertNotNull(savedInvoice);
@@ -1475,7 +1481,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         invoiceUtil.verifyInvoice(invoice2.getId(), 0.00, -5.00, context);
 
         // Refund Payment before we can deleted CBA
-        invoiceDao.createRefund(paymentId, new BigDecimal("10.0"), false, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID(), context);
+        invoiceDao.createRefund(paymentId, new BigDecimal("10.0"), false, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID().toString(), context);
 
         // Verify all three invoices were affected
         Assert.assertEquals(invoiceDao.getAccountCBA(accountId, context).doubleValue(), 0.00);
@@ -1549,7 +1555,7 @@ public class TestInvoiceDao extends InvoiceTestSuiteWithEmbeddedDB {
         invoiceUtil.verifyInvoice(invoice2.getId(), 0.00, -5.00, context);
         invoiceUtil.verifyInvoice(invoice3.getId(), 0.00, -5.00, context);
 
-        invoiceDao.createRefund(paymentId, paymentAmount, false, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID(), context);
+        invoiceDao.createRefund(paymentId, paymentAmount, false, ImmutableMap.<UUID, BigDecimal>of(), UUID.randomUUID().toString(), context);
 
         // Verify all three invoices were affected
         Assert.assertEquals(invoiceDao.getAccountCBA(accountId, context).doubleValue(), 0.00);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index faa4eb2..52a1112 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -20,6 +20,7 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -27,40 +28,44 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.killbill.billing.catalog.api.BillingMode;
-import org.mockito.Mockito;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.DefaultPrice;
 import org.killbill.billing.catalog.MockInternationalPrice;
 import org.killbill.billing.catalog.MockPlan;
 import org.killbill.billing.catalog.MockPlanPhase;
+import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.clock.Clock;
-import org.killbill.clock.DefaultClock;
+import org.killbill.billing.entity.EntityPersistenceException;
 import org.killbill.billing.invoice.InvoiceTestSuiteNoDB;
 import org.killbill.billing.invoice.MockBillingEventSet;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.killbill.billing.invoice.model.DefaultInvoice;
 import org.killbill.billing.invoice.model.DefaultInvoicePayment;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.invoice.model.RecurringInvoiceItem;
+import org.killbill.billing.invoice.model.RepairAdjInvoiceItem;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
 import org.killbill.billing.util.config.InvoiceConfig;
 import org.killbill.billing.util.currency.KillBillMoney;
+import org.killbill.clock.Clock;
+import org.killbill.clock.DefaultClock;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
 
 import static org.killbill.billing.invoice.TestInvoiceHelper.EIGHT;
 import static org.killbill.billing.invoice.TestInvoiceHelper.FIFTEEN;
@@ -102,7 +107,7 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
                 return false;
             }
         };
-        this.generator = new DefaultInvoiceGenerator(clock, null, invoiceConfig, null);
+        this.generator = new DefaultInvoiceGenerator(clock, null, invoiceConfig, null, controllerDispatcher);
     }
 
     @Test(groups = "fast")
@@ -899,6 +904,92 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         assertEquals(invoice2.getBalance().compareTo(FIVE), 0);
     }
 
+    // Regression test for #170 (see https://github.com/killbill/killbill/pull/173)
+    @Test(groups = "fast")
+    public void testRegressionFor170() throws EntityPersistenceException, InvoiceApiException, CatalogApiException {
+        final UUID accountId = UUID.randomUUID();
+        final Currency currency = Currency.USD;
+        final SubscriptionBase subscription = createSubscription();
+        final MockInternationalPrice recurringPrice = new MockInternationalPrice(new DefaultPrice(new BigDecimal("2.9500"), Currency.USD));
+        final MockPlanPhase phase = new MockPlanPhase(recurringPrice, null);
+        final Plan plan = new MockPlan(phase);
+
+        final LocalDate targetDate = new LocalDate(2013, 10, 30);
+
+        final Invoice existingInvoice = new DefaultInvoice(UUID.randomUUID(), accountId, null, clock.getUTCToday(), targetDate, currency, false);
+
+        // Set the existing recurring invoice item 2013/06/15 - 2013/07/15
+        final LocalDate startDate = new LocalDate(2013, 06, 15);
+        final LocalDate endDate = new LocalDate(2013, 07, 15);
+        final InvoiceItem recurringInvoiceItem = new RecurringInvoiceItem(existingInvoice.getId(), accountId, subscription.getBundleId(),
+                                                                          subscription.getId(), plan.getName(), phase.getName(),
+                                                                          startDate, endDate, recurringPrice.getPrice(currency),
+                                                                          recurringPrice.getPrice(currency), Currency.USD);
+        existingInvoice.addInvoiceItem(recurringInvoiceItem);
+
+        // Set an existing repair item
+        final LocalDate repairStartDate = new LocalDate(2013, 06, 21);
+        final LocalDate repairEndDate = new LocalDate(2013, 06, 26);
+        final BigDecimal repairAmount = new BigDecimal("0.4900").negate();
+        final InvoiceItem repairItem = new RepairAdjInvoiceItem(existingInvoice.getId(), accountId, repairStartDate, repairEndDate,
+                                                                repairAmount, currency, recurringInvoiceItem.getId());
+        existingInvoice.addInvoiceItem(repairItem);
+
+        // Create the billing event associated with the subscription creation
+        //
+        // Note : this is the interesting part of the test; it does not provide the blocking billing events, which force invoice
+        // to un repair what was previously repaired.
+        final BillingEventSet events = new MockBillingEventSet();
+        final BillingEvent event = invoiceUtil.createMockBillingEvent(null, subscription, new DateTime("2013-06-15", DateTimeZone.UTC),
+                                                                      plan, phase,
+                                                                      null, recurringPrice.getPrice(currency), currency,
+                                                                      BillingPeriod.MONTHLY, 15, BillingMode.IN_ADVANCE, "testEvent", 1L,
+                                                                      SubscriptionBaseTransitionType.CREATE);
+        events.add(event);
+
+        final List<Invoice> existingInvoices = new LinkedList<Invoice>();
+        existingInvoices.add(existingInvoice);
+
+        // Generate a new invoice
+
+        final Invoice invoice = generator.generateInvoice(accountId, events, existingInvoices, targetDate, currency, internalCallContext);
+        assertEquals(invoice.getNumberOfItems(), 7);
+        assertEquals(invoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), new LocalDate(2013, 6, 15));
+        assertEquals(invoice.getInvoiceItems().get(0).getEndDate(), new LocalDate(2013, 7, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(1).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+        assertEquals(invoice.getInvoiceItems().get(1).getStartDate(), new LocalDate(2013, 6, 15));
+        assertEquals(invoice.getInvoiceItems().get(1).getEndDate(), new LocalDate(2013, 6, 21));
+
+        assertEquals(invoice.getInvoiceItems().get(2).getInvoiceItemType(), InvoiceItemType.REPAIR_ADJ);
+        assertEquals(invoice.getInvoiceItems().get(2).getStartDate(), new LocalDate(2013, 6, 26));
+        assertEquals(invoice.getInvoiceItems().get(2).getEndDate(), new LocalDate(2013, 7, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(3).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(3).getStartDate(), new LocalDate(2013, 7, 15));
+        assertEquals(invoice.getInvoiceItems().get(3).getEndDate(), new LocalDate(2013, 8, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(4).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(4).getStartDate(), new LocalDate(2013, 8, 15));
+        assertEquals(invoice.getInvoiceItems().get(4).getEndDate(), new LocalDate(2013, 9, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(5).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(5).getStartDate(), new LocalDate(2013, 9, 15));
+        assertEquals(invoice.getInvoiceItems().get(5).getEndDate(), new LocalDate(2013, 10, 15));
+
+        assertEquals(invoice.getInvoiceItems().get(6).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(6).getStartDate(), new LocalDate(2013, 10, 15));
+        assertEquals(invoice.getInvoiceItems().get(6).getEndDate(), new LocalDate(2013, 11, 15));
+
+        // Add newly generated invoice to existing invoices
+        existingInvoices.add(invoice);
+
+        // Generate next invoice (no-op)
+        final Invoice newInvoice = generator.generateInvoice(accountId, events, existingInvoices, targetDate, currency, internalCallContext);
+        assertNull(newInvoice);
+    }
+
     private void distributeItems(final List<Invoice> invoices) {
         final Map<UUID, Invoice> invoiceMap = new HashMap<UUID, Invoice>();
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java
index e3a2942..2467a92 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,6 +21,7 @@ package org.killbill.billing.invoice.glue;
 import org.killbill.billing.catalog.glue.CatalogModule;
 import org.killbill.billing.invoice.TestInvoiceHelper;
 import org.killbill.billing.junction.BillingInternalApi;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.usage.glue.UsageModule;
 import org.killbill.billing.util.email.EmailModule;
@@ -27,14 +30,12 @@ import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
 import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
-import org.killbill.billing.util.glue.NotificationQueueModule;
 import org.killbill.billing.util.glue.TagStoreModule;
 import org.mockito.Mockito;
-import org.skife.config.ConfigSource;
 
 public class TestInvoiceModule extends DefaultInvoiceModule {
 
-    public TestInvoiceModule(final ConfigSource configSource) {
+    public TestInvoiceModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -46,17 +47,16 @@ public class TestInvoiceModule extends DefaultInvoiceModule {
     @Override
     protected void configure() {
         super.configure();
-        install(new CallContextModule());
-        install(new MemoryGlobalLockerModule());
+        install(new CallContextModule(configSource));
+        install(new MemoryGlobalLockerModule(configSource));
 
         install(new CatalogModule(configSource));
         install(new CacheModule(configSource));
-        install(new TemplateModule());
+        install(new TemplateModule(configSource));
         install(new EmailModule(configSource));
 
-        install(new NotificationQueueModule(configSource));
-        install(new TagStoreModule());
-        install(new CustomFieldModule());
+        install(new TagStoreModule(configSource));
+        install(new CustomFieldModule(configSource));
         install(new UsageModule(configSource));
         installExternalApis();
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModuleNoDB.java b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModuleNoDB.java
index 2dfc564..10c58c2 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModuleNoDB.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,14 +21,11 @@ package org.killbill.billing.invoice.glue;
 import java.math.BigDecimal;
 import java.util.HashSet;
 import java.util.Set;
-import java.util.TreeSet;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
-import org.mockito.Mockito;
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
+import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.currency.api.CurrencyConversion;
@@ -36,12 +35,12 @@ import org.killbill.billing.currency.api.Rate;
 import org.killbill.billing.invoice.dao.InvoiceDao;
 import org.killbill.billing.invoice.dao.MockInvoiceDao;
 import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
-import org.killbill.billing.util.bus.InMemoryBusModule;
-import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.mockito.Mockito;
 
 public class TestInvoiceModuleNoDB extends TestInvoiceModule {
 
-    public TestInvoiceModuleNoDB(final ConfigSource configSource) {
+    public TestInvoiceModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -52,9 +51,8 @@ public class TestInvoiceModuleNoDB extends TestInvoiceModule {
     @Override
     public void configure() {
         super.configure();
-        install(new GuicyKillbillTestNoDBModule());
-        install(new MockNonEntityDaoModule());
-        install(new InMemoryBusModule(configSource));
+        install(new GuicyKillbillTestNoDBModule(configSource));
+        install(new MockNonEntityDaoModule(configSource));
 
         bind(AccountInternalApi.class).toInstance(Mockito.mock(AccountInternalApi.class));
         bind(AccountUserApi.class).toInstance(Mockito.mock(AccountUserApi.class));
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModuleWithEmbeddedDb.java b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModuleWithEmbeddedDb.java
index 7304cc5..0e161e0 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModuleWithEmbeddedDb.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/glue/TestInvoiceModuleWithEmbeddedDb.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,21 +18,18 @@
 
 package org.killbill.billing.invoice.glue;
 
-import org.mockito.Mockito;
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
 import org.killbill.billing.account.glue.DefaultAccountModule;
 import org.killbill.billing.currency.api.CurrencyConversionApi;
 import org.killbill.billing.invoice.InvoiceListener;
 import org.killbill.billing.invoice.TestInvoiceNotificationQListener;
-import org.killbill.billing.util.glue.BusModule;
-import org.killbill.billing.util.glue.MetricsModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
+import org.mockito.Mockito;
 
 public class TestInvoiceModuleWithEmbeddedDb extends TestInvoiceModule {
 
-    public TestInvoiceModuleWithEmbeddedDb(final ConfigSource configSource) {
+    public TestInvoiceModuleWithEmbeddedDb(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -44,10 +43,8 @@ public class TestInvoiceModuleWithEmbeddedDb extends TestInvoiceModule {
     public void configure() {
         super.configure();
         install(new DefaultAccountModule(configSource));
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
-        install(new NonEntityDaoModule());
-        install(new MetricsModule());
-        install(new BusModule(configSource));
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
+        install(new NonEntityDaoModule(configSource));
 
         bind(CurrencyConversionApi.class).toInstance(Mockito.mock(CurrencyConversionApi.class));
     }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java
index dc72ae4..aa62f70 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,11 +18,7 @@
 
 package org.killbill.billing.invoice;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
-
 import org.killbill.billing.GuicyKillbillTestSuiteNoDB;
-import org.killbill.billing.TestKillbillConfigSource;
 import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.currency.api.CurrencyConversionApi;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
@@ -31,13 +29,13 @@ import org.killbill.billing.invoice.dao.InvoiceDao;
 import org.killbill.billing.invoice.generator.InvoiceGenerator;
 import org.killbill.billing.invoice.glue.TestInvoiceModuleNoDB;
 import org.killbill.billing.junction.BillingInternalApi;
+import org.killbill.billing.lifecycle.api.BusService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.usage.api.UsageUserApi;
-import org.killbill.billing.util.KillbillConfigSource;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.svcsapi.bus.BusService;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
@@ -95,8 +93,8 @@ public abstract class InvoiceTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
     protected UsageUserApi usageUserApi;
 
     @Override
-    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
-        return new TestKillbillConfigSource("/resource.properties");
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/resource.properties");
     }
 
     @BeforeClass(groups = "fast")
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
index 039ede4..de92ab1 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,11 +18,7 @@
 
 package org.killbill.billing.invoice;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
-
 import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
-import org.killbill.billing.TestKillbillConfigSource;
 import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.catalog.api.Currency;
@@ -34,14 +32,16 @@ import org.killbill.billing.invoice.dao.InvoiceDao;
 import org.killbill.billing.invoice.generator.InvoiceGenerator;
 import org.killbill.billing.invoice.glue.TestInvoiceModuleWithEmbeddedDb;
 import org.killbill.billing.invoice.notification.NextBillingDateNotifier;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
 import org.killbill.billing.junction.BillingInternalApi;
+import org.killbill.billing.lifecycle.api.BusService;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
-import org.killbill.billing.util.KillbillConfigSource;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.dao.NonEntityDao;
-import org.killbill.billing.util.svcsapi.bus.BusService;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
 import org.killbill.commons.locker.GlobalLocker;
@@ -108,10 +108,12 @@ public abstract class InvoiceTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     protected TestInvoiceHelper invoiceUtil;
     @Inject
     protected TestInvoiceNotificationQListener testInvoiceNotificationQListener;
+    @Inject
+    protected OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
 
     @Override
-    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
-        return new TestKillbillConfigSource("/resource.properties");
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/resource.properties");
     }
 
     @BeforeClass(groups = "slow")
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/MockBillingEventSet.java b/invoice/src/test/java/org/killbill/billing/invoice/MockBillingEventSet.java
index 261122f..d93cf5b 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/MockBillingEventSet.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/MockBillingEventSet.java
@@ -17,11 +17,14 @@
 package org.killbill.billing.invoice;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.TreeSet;
 import java.util.UUID;
 
 import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.junction.BillingEventSet;
 
@@ -51,6 +54,11 @@ public class MockBillingEventSet extends TreeSet<BillingEvent> implements Billin
         return subscriptionIdsWithAutoInvoiceOff;
     }
 
+    @Override
+    public Map<String, Usage> getUsages() {
+        return Collections.emptyMap();
+    }
+
     public void setAccountInvoiceOff(final boolean isAccountInvoiceOff) {
         this.isAccountInvoiceOff = isAccountInvoiceOff;
     }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java b/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
index 9bd2154..35f50a9 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/model/TestExternalChargeInvoiceItem.java
@@ -20,12 +20,11 @@ import java.math.BigDecimal;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.InvoiceTestSuiteNoDB;
 import org.killbill.billing.invoice.api.InvoiceItemType;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestExternalChargeInvoiceItem extends InvoiceTestSuiteNoDB {
 
@@ -46,7 +45,8 @@ public class TestExternalChargeInvoiceItem extends InvoiceTestSuiteNoDB {
         Assert.assertEquals(item.getBundleId(), bundleId);
         Assert.assertEquals(item.getCurrency(), currency);
         Assert.assertEquals(item.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
-        Assert.assertEquals(item.getPlanName(), description);
+        Assert.assertEquals(item.getDescription(), description);
+        Assert.assertNull(item.getPlanName());
         Assert.assertNull(item.getEndDate());
         Assert.assertNull(item.getLinkedItemId());
         Assert.assertNull(item.getPhaseName());
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
index 291af98..6000893 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceFormatter.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -55,7 +57,7 @@ public class TestDefaultInvoiceFormatter extends InvoiceTestSuiteNoDB {
     @BeforeClass(groups = "fast")
     public void beforeClass() throws Exception {
         super.beforeClass();
-        config = new ConfigurationObjectFactory(configSource).build(TranslatorConfig.class);
+        config = new ConfigurationObjectFactory(skifeConfigSource).build(TranslatorConfig.class);
         templateEngine = new MustacheTemplateEngine();
     }
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
index 4fda588..eec566d 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/template/formatters/TestDefaultInvoiceItemFormatter.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -46,7 +48,7 @@ public class TestDefaultInvoiceItemFormatter extends InvoiceTestSuiteNoDB {
     @BeforeClass(groups = "fast")
     public void beforeClass() throws Exception {
         super.beforeClass();
-        config = new ConfigurationObjectFactory(configSource).build(TranslatorConfig.class);
+        config = new ConfigurationObjectFactory(skifeConfigSource).build(TranslatorConfig.class);
         templateEngine = new MustacheTemplateEngine();
     }
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestHtmlInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/TestHtmlInvoiceGenerator.java
index 2ad6780..53f096f 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestHtmlInvoiceGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestHtmlInvoiceGenerator.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -29,6 +31,7 @@ import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.formatters.InvoiceFormatterFactory;
+import org.killbill.billing.invoice.template.HtmlInvoice;
 import org.killbill.billing.invoice.template.HtmlInvoiceGenerator;
 import org.killbill.billing.invoice.template.formatters.DefaultInvoiceFormatterFactory;
 import org.killbill.billing.util.email.templates.MustacheTemplateEngine;
@@ -48,7 +51,7 @@ public class TestHtmlInvoiceGenerator extends InvoiceTestSuiteNoDB {
     @BeforeClass(groups = "fast")
     public void beforeClass() throws Exception {
         super.beforeClass();
-        final TranslatorConfig config = new ConfigurationObjectFactory(configSource).build(TranslatorConfig.class);
+        final TranslatorConfig config = new ConfigurationObjectFactory(skifeConfigSource).build(TranslatorConfig.class);
         final TemplateEngine templateEngine = new MustacheTemplateEngine();
         final InvoiceFormatterFactory factory = new DefaultInvoiceFormatterFactory();
         g = new HtmlInvoiceGenerator(factory, templateEngine, config, null);
@@ -56,20 +59,24 @@ public class TestHtmlInvoiceGenerator extends InvoiceTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testGenerateInvoice() throws Exception {
-        final String output = g.generateInvoice(createAccount(), createInvoice(), false);
+        final HtmlInvoice output = g.generateInvoice(createAccount(), createInvoice(), false);
         Assert.assertNotNull(output);
+        Assert.assertNotNull(output.getBody());
+        Assert.assertEquals(output.getSubject(), "Your invoice");
     }
 
     @Test(groups = "fast")
     public void testGenerateEmptyInvoice() throws Exception {
         final Invoice invoice = Mockito.mock(Invoice.class);
-        final String output = g.generateInvoice(createAccount(), invoice, false);
+        final HtmlInvoice output = g.generateInvoice(createAccount(), invoice, false);
         Assert.assertNotNull(output);
+        Assert.assertNotNull(output.getBody());
+        Assert.assertEquals(output.getSubject(), "Your invoice");
     }
 
     @Test(groups = "fast")
     public void testGenerateNullInvoice() throws Exception {
-        final String output = g.generateInvoice(createAccount(), null, false);
+        final HtmlInvoice output = g.generateInvoice(createAccount(), null, false);
         Assert.assertNull(output);
     }
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
index a306cab..cf40b6d 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceDispatcher.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -25,22 +27,17 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.killbill.billing.catalog.api.BillingMode;
-import org.mockito.Mockito;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.MockPlan;
 import org.killbill.billing.catalog.MockPlanPhase;
+import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.clock.ClockMock;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
@@ -49,11 +46,15 @@ import org.killbill.billing.invoice.api.InvoiceNotifier;
 import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
 import org.killbill.billing.invoice.dao.InvoiceModelDao;
 import org.killbill.billing.invoice.notification.NullInvoiceNotifier;
+import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
-import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.util.timezone.DateAndTimeZoneContext;
+import org.killbill.clock.ClockMock;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
 
 public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
 
@@ -89,9 +90,9 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
         final DateTime target = new DateTime();
 
         final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
-        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
+        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(pluginRegistry, generator, accountApi, billingApi, subscriptionApi, invoiceDao,
                                                                    nonEntityDao, invoiceNotifier, locker, busService.getBus(),
-                                                                   clock);
+                                                                   clock, controllerDispatcher);
 
         Invoice invoice = dispatcher.processAccount(accountId, target, true, context);
         Assert.assertNotNull(invoice);
@@ -142,9 +143,9 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
 
         Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(Mockito.<UUID>any(), Mockito.<InternalCallContext>any())).thenReturn(events);
         final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
-        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
+        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(pluginRegistry, generator, accountApi, billingApi, subscriptionApi, invoiceDao,
                                                                    nonEntityDao, invoiceNotifier, locker, busService.getBus(),
-                                                                   clock);
+                                                                   clock, controllerDispatcher);
 
         final Invoice invoice = dispatcher.processAccount(account.getId(), new DateTime("2012-07-30T00:00:00.000Z"), false, context);
         Assert.assertNotNull(invoice);
@@ -189,22 +190,20 @@ public class TestInvoiceDispatcher extends InvoiceTestSuiteWithEmbeddedDB {
     @Test(groups = "slow")
     public void testCreateNextFutureNotificationDate() throws Exception {
 
-
         final LocalDate startDate = new LocalDate("2012-10-26");
         final LocalDate endDate = new LocalDate("2012-11-26");
 
-
         ((ClockMock) clock).setTime(new DateTime(2012, 10, 13, 1, 12, 23, DateTimeZone.UTC));
 
         final DateAndTimeZoneContext dateAndTimeZoneContext = new DateAndTimeZoneContext(clock.getUTCNow(), DateTimeZone.forID("Pacific/Pitcairn"), clock);
 
         final InvoiceItemModelDao item = new InvoiceItemModelDao(UUID.randomUUID(), clock.getUTCNow(), InvoiceItemType.RECURRING, UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(),
-                                                                 "planName", "phaseName", null, startDate, endDate, new BigDecimal("23.9"), new BigDecimal("23.9"), Currency.EUR, null);
+                                                                 null, "planName", "phaseName", null, startDate, endDate, new BigDecimal("23.9"), new BigDecimal("23.9"), Currency.EUR, null);
 
         final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
-        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi, invoiceDao,
+        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(pluginRegistry, generator, accountApi, billingApi, subscriptionApi, invoiceDao,
                                                                    nonEntityDao, invoiceNotifier, locker, busService.getBus(),
-                                                                   clock);
+                                                                   clock, controllerDispatcher);
 
         final Map<UUID, List<DateTime>> result = dispatcher.createNextFutureNotificationDate(Collections.singletonList(item), null, dateAndTimeZoneContext);
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
index 91a4385..955e9a0 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/TestInvoiceHelper.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -27,12 +29,6 @@ import javax.inject.Inject;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.killbill.billing.catalog.api.BillingMode;
-import org.killbill.billing.catalog.api.Usage;
-import org.mockito.Mockito;
-import org.skife.jdbi.v2.IDBI;
-import org.testng.Assert;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountData;
@@ -42,12 +38,12 @@ import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.MockPlan;
 import org.killbill.billing.catalog.MockPlanPhase;
+import org.killbill.billing.catalog.api.BillingMode;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
-import org.killbill.clock.Clock;
-import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.entity.EntityPersistenceException;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
@@ -63,19 +59,27 @@ import org.killbill.billing.invoice.dao.InvoicePaymentModelDao;
 import org.killbill.billing.invoice.dao.InvoicePaymentSqlDao;
 import org.killbill.billing.invoice.generator.InvoiceGenerator;
 import org.killbill.billing.invoice.notification.NullInvoiceNotifier;
+import org.killbill.billing.invoice.plugin.api.InvoicePluginApi;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.junction.BillingEventSet;
 import org.killbill.billing.junction.BillingInternalApi;
+import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.mock.MockAccountBuilder;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseTransitionType;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.currency.KillBillMoney;
 import org.killbill.billing.util.dao.NonEntityDao;
-import org.killbill.billing.util.svcsapi.bus.BusService;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.mockito.Mockito;
+import org.skife.jdbi.v2.IDBI;
+import org.testng.Assert;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
@@ -137,6 +141,7 @@ public class TestInvoiceHelper {
     private final InvoiceGenerator generator;
     private final BillingInternalApi billingApi;
     private final AccountInternalApi accountApi;
+    private final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry;
     private final AccountUserApi accountUserApi;
     private final SubscriptionBaseInternalApi subscriptionApi;
     private final BusService busService;
@@ -146,16 +151,18 @@ public class TestInvoiceHelper {
     private final InternalCallContext internalCallContext;
     private final NonEntityDao nonEntityDao;
     private final InternalCallContextFactory internalCallContextFactory;
+    private final CacheControllerDispatcher cacheControllerDispatcher;
 
     // Low level SqlDao used by the tests to directly insert rows
     private final InvoicePaymentSqlDao invoicePaymentSqlDao;
     private final InvoiceItemSqlDao invoiceItemSqlDao;
 
     @Inject
-    public TestInvoiceHelper(final InvoiceGenerator generator, final IDBI dbi,
+    public TestInvoiceHelper(final OSGIServiceRegistration<InvoicePluginApi> pluginRegistry, final InvoiceGenerator generator, final IDBI dbi,
                              final BillingInternalApi billingApi, final AccountInternalApi accountApi, final AccountUserApi accountUserApi, final SubscriptionBaseInternalApi subscriptionApi, final BusService busService,
                              final InvoiceDao invoiceDao, final GlobalLocker locker, final Clock clock, final NonEntityDao nonEntityDao, final InternalCallContext internalCallContext,
-                             final InternalCallContextFactory internalCallContextFactory) {
+                             final InternalCallContextFactory internalCallContextFactory, final CacheControllerDispatcher cacheControllerDispatcher) {
+        this.pluginRegistry = pluginRegistry;
         this.generator = generator;
         this.billingApi = billingApi;
         this.accountApi = accountApi;
@@ -170,6 +177,7 @@ public class TestInvoiceHelper {
         this.internalCallContextFactory = internalCallContextFactory;
         this.invoiceItemSqlDao = dbi.onDemand(InvoiceItemSqlDao.class);
         this.invoicePaymentSqlDao = dbi.onDemand(InvoicePaymentSqlDao.class);
+        this.cacheControllerDispatcher = cacheControllerDispatcher;
     }
 
     public UUID generateRegularInvoice(final Account account, final DateTime targetDate, final CallContext callContext) throws Exception {
@@ -189,9 +197,9 @@ public class TestInvoiceHelper {
         Mockito.when(billingApi.getBillingEventsForAccountAndUpdateAccountBCD(Mockito.<UUID>any(), Mockito.<InternalCallContext>any())).thenReturn(events);
 
         final InvoiceNotifier invoiceNotifier = new NullInvoiceNotifier();
-        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(generator, accountApi, billingApi, subscriptionApi,
+        final InvoiceDispatcher dispatcher = new InvoiceDispatcher(pluginRegistry, generator, accountApi, billingApi, subscriptionApi,
                                                                    invoiceDao, nonEntityDao, invoiceNotifier, locker, busService.getBus(),
-                                                                   clock);
+                                                                   clock, cacheControllerDispatcher);
 
         Invoice invoice = dispatcher.processAccount(account.getId(), targetDate, true, internalCallContext);
         Assert.assertNotNull(invoice);
@@ -273,7 +281,7 @@ public class TestInvoiceHelper {
                                                                                                                                          }));
 
         // The test does not use the invoice callback notifier hence the empty map
-        invoiceDao.createInvoice(invoiceModelDao, invoiceItemModelDaos, invoicePaymentModelDaos, isRealInvoiceWithItems, ImmutableMap.<UUID, List<DateTime>>of(), internalCallContext);
+        invoiceDao.createInvoice(invoiceModelDao, invoiceItemModelDaos, isRealInvoiceWithItems, ImmutableMap.<UUID, List<DateTime>>of(), internalCallContext);
     }
 
     public void createPayment(final InvoicePayment invoicePayment, final InternalCallContext internalCallContext) {
@@ -313,10 +321,14 @@ public class TestInvoiceHelper {
                                                final BillingMode billingMode, final String description,
                                                final long totalOrdering,
                                                final SubscriptionBaseTransitionType type) {
+
+        final Account mockAccount = Mockito.mock(Account.class);
+        Mockito.when(mockAccount.getTimeZone()).thenReturn(DateTimeZone.UTC);
+        final Account accountOrMockAcount = account != null ? account : mockAccount;
         return new BillingEvent() {
             @Override
             public Account getAccount() {
-                return account;
+                return accountOrMockAcount;
             }
 
             @Override
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java
index 811dacc..313ffac 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tests/InvoiceTestUtils.java
@@ -34,7 +34,6 @@ import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.invoice.dao.InvoiceDao;
 import org.killbill.billing.invoice.dao.InvoiceItemModelDao;
 import org.killbill.billing.invoice.dao.InvoiceModelDao;
-import org.killbill.billing.invoice.dao.InvoicePaymentModelDao;
 import org.killbill.billing.invoice.model.FixedPriceInvoiceItem;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.clock.Clock;
@@ -87,7 +86,7 @@ public class InvoiceTestUtils {
         }
         Mockito.when(invoice.getInvoiceItems()).thenReturn(invoiceItems);
 
-        invoiceDao.createInvoice(new InvoiceModelDao(invoice), invoiceModelItems, ImmutableList.<InvoicePaymentModelDao>of(), true, ImmutableMap.<UUID, List<DateTime>>of(), internalCallContext);
+        invoiceDao.createInvoice(new InvoiceModelDao(invoice), invoiceModelItems, true, ImmutableMap.<UUID, List<DateTime>>of(), internalCallContext);
 
         return invoice;
     }
@@ -108,7 +107,7 @@ public class InvoiceTestUtils {
         Mockito.when(payment.getType()).thenReturn(InvoicePaymentType.ATTEMPT);
         Mockito.when(payment.getInvoiceId()).thenReturn(invoiceId);
         Mockito.when(payment.getPaymentId()).thenReturn(UUID.randomUUID());
-        Mockito.when(payment.getPaymentCookieId()).thenReturn(UUID.randomUUID());
+        Mockito.when(payment.getPaymentCookieId()).thenReturn(UUID.randomUUID().toString());
         Mockito.when(payment.getPaymentDate()).thenReturn(clock.getUTCNow());
         Mockito.when(payment.getAmount()).thenReturn(amount);
         Mockito.when(payment.getCurrency()).thenReturn(currency);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestNodeInterval.java b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestNodeInterval.java
index f75aad3..ba35fff 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestNodeInterval.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestNodeInterval.java
@@ -162,6 +162,7 @@ public class TestNodeInterval /* extends InvoiceTestSuiteNoDB  */ {
             @Override
             public void onLastNode(final NodeInterval curNode) {
                 // Nothing
+                return;
             }
         });
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
index 317fb9e..1a12fdb 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -218,7 +218,6 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB  */ {
         expectedResult.add(expected1);
         expectedResult.add(expected2);
 
-        // First test with items in order
         SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
         tree.addItem(first);
         tree.addItem(second);
@@ -351,6 +350,51 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB  */ {
         verifyResult(tree.getView(), expectedResult);
     }
 
+    // The test that first repair (repair1) and new Item (newItem1) end up being ignored.
+    @Test(groups = "fast")
+    public void testOverlappingRepair() {
+
+        final LocalDate startDate = new LocalDate(2012, 5, 1);
+        final LocalDate endDate = new LocalDate(2013, 5, 1);
+
+        final LocalDate changeDate = new LocalDate(2012, 5, 11);
+        final LocalDate monthlyAlignmentDate = new LocalDate(2012, 6, 1);
+
+        final BigDecimal rate1 = new BigDecimal("2400.00");
+        final BigDecimal amount1 = rate1;
+
+        final BigDecimal rate2 = new BigDecimal("300.00");
+        final BigDecimal amount2 = rate2;
+
+        // Start with a ANNUAL plan (high rate, rate1)
+        final InvoiceItem initial = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount1, rate1, currency);
+
+        // Change to MONTHLY plan 10 days later (low rate, rate2)
+        final InvoiceItem repair1 = new RepairAdjInvoiceItem(invoiceId, accountId, changeDate, endDate, amount1.negate(), currency, initial.getId());
+        final InvoiceItem newItem1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, "someelse", "someelse", changeDate, monthlyAlignmentDate, amount2, rate2, currency);
+
+        // On the same day, now revert back to ANNUAL
+        final InvoiceItem repair2 = new RepairAdjInvoiceItem(invoiceId, accountId, changeDate, monthlyAlignmentDate, amount2.negate(), currency, newItem1.getId());
+        final InvoiceItem newItem2 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, changeDate, endDate, amount1, rate1, currency);
+
+
+        final SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+        tree.addItem(initial);
+        tree.addItem(newItem1);
+        tree.addItem(repair1);
+        tree.addItem(newItem2);
+        tree.addItem(repair2);
+
+        final List<InvoiceItem> expectedResult = Lists.newLinkedList();
+        final InvoiceItem expected1 = new RecurringInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, changeDate, new BigDecimal("65.75"), rate1, currency);
+        expectedResult.add(expected1);
+        expectedResult.add(newItem2);
+
+        tree.build();
+        verifyResult(tree.getView(), expectedResult);
+    }
+
+
     @Test(groups = "fast")
     public void testMergeWithNoExisting() {
 
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
index 0e9d662..5cb31a9 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestContiguousIntervalConsumableInArrear.java
@@ -1,7 +1,9 @@
 /*
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
  * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -39,8 +41,8 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.beust.jcommander.internal.Lists;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
@@ -72,7 +74,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         final LocalDate targetDate = startDate.plusDays(1);
         final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, targetDate, false,
                                                                                                                            createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
-                                                                                                                                                  Collections.<Usage>emptyList()));
+                                                                                                                                                  Collections.<Usage>emptyList())
+                                                                                                                          );
 
         final List<InvoiceItem> existingUsage = Lists.newArrayList();
         final UsageInvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, endDate, BigDecimal.TEN, currency);
@@ -92,7 +95,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         final FixedPriceInvoiceItem ii5 = new FixedPriceInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, BigDecimal.TEN, currency);
         existingUsage.add(ii5);
 
-        final BigDecimal result = intervalConsumableInArrear.computeBilledUsage(startDate, endDate, existingUsage);
+        final BigDecimal result = intervalConsumableInArrear.computeBilledUsage(intervalConsumableInArrear.getBilledItems(startDate, endDate, existingUsage));
         assertEquals(result, BigDecimal.TEN.add(BigDecimal.TEN));
     }
 
@@ -109,7 +112,8 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         final LocalDate targetDate = new LocalDate(2014, 03, 20);
         final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, targetDate, false,
                                                                                                                            createMockBillingEvent(targetDate.toDateTimeAtStartOfDay(DateTimeZone.UTC),
-                                                                                                                           Collections.<Usage>emptyList()));
+                                                                                                                                                  Collections.<Usage>emptyList())
+                                                                                                                          );
 
         final BigDecimal result = intervalConsumableInArrear.computeToBeBilledUsage(new BigDecimal("5325"), "unit");
 
@@ -146,16 +150,16 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         final ContiguousIntervalConsumableInArrear intervalConsumableInArrear = createContiguousIntervalConsumableInArrear(usage, targetDate, true, event1, event2);
 
         final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
-        InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, firstBCDDate, BigDecimal.ONE, currency);
+        final InvoiceItem ii1 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), startDate, firstBCDDate, BigDecimal.ONE, currency);
         invoiceItems.add(ii1);
 
-        InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
+        final InvoiceItem ii2 = new UsageInvoiceItem(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, usage.getName(), firstBCDDate, endDate, BigDecimal.ONE, currency);
         invoiceItems.add(ii2);
 
         final List<InvoiceItem> result = intervalConsumableInArrear.computeMissingItems(invoiceItems);
         assertEquals(result.size(), 2);
         // Invoiced for 1 BTC and used 130 + 271 = 401 => 5 blocks => 5 BTC so remaining piece should be 4 BTC
-        assertTrue(result.get(0).getAmount().compareTo(new BigDecimal("4.0")) == 0);
+        assertEquals(result.get(0).getAmount().compareTo(new BigDecimal("4.0")), 0, String.format("%s != 4.0", result.get(0).getAmount()));
         assertEquals(result.get(0).getCurrency(), Currency.BTC);
         assertEquals(result.get(0).getAccountId(), accountId);
         assertEquals(result.get(0).getBundleId(), bundleId);
@@ -167,7 +171,7 @@ public class TestContiguousIntervalConsumableInArrear extends TestUsageInArrearB
         assertTrue(result.get(0).getEndDate().compareTo(firstBCDDate) == 0);
 
         // Invoiced for 1 BTC and used 199  => 2 blocks => 2 BTC so remaining piece should be 1 BTC
-        assertTrue(result.get(1).getAmount().compareTo(new BigDecimal("1.0")) == 0);
+        assertEquals(result.get(1).getAmount().compareTo(new BigDecimal("1.0")), 0, String.format("%s != 1.0", result.get(0).getAmount()));
         assertEquals(result.get(1).getCurrency(), Currency.BTC);
         assertEquals(result.get(1).getAccountId(), accountId);
         assertEquals(result.get(1).getBundleId(), bundleId);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
index 06df090..c124390 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/usage/TestSubscriptionConsumableInArrear.java
@@ -29,8 +29,8 @@ import org.killbill.billing.junction.BillingEvent;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-import com.beust.jcommander.internal.Lists;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
diff --git a/invoice/src/test/resources/org/killbill/billing/util/template/translation/InvoiceTranslation_en_US.properties b/invoice/src/test/resources/org/killbill/billing/util/template/translation/InvoiceTranslation_en_US.properties
index 02d074a..823dc54 100644
--- a/invoice/src/test/resources/org/killbill/billing/util/template/translation/InvoiceTranslation_en_US.properties
+++ b/invoice/src/test/resources/org/killbill/billing/util/template/translation/InvoiceTranslation_en_US.properties
@@ -1,3 +1,4 @@
+invoiceEmailSubject=Your invoice
 invoiceTitle=INVOICE
 invoiceDate=Date:
 invoiceNumber=Invoice #

jaxrs/pom.xml 66(+61 -5)

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 72d4733..1715ed9 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-jaxrs</artifactId>
@@ -27,6 +27,26 @@
     <name>killbill-jaxrs</name>
     <dependencies>
         <dependency>
+            <groupId>com.codahale.metrics</groupId>
+            <artifactId>metrics-annotation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.codahale.metrics</groupId>
+            <artifactId>metrics-jersey</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
             <scope>provided</scope>
@@ -41,6 +61,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
@@ -53,11 +78,28 @@
             <artifactId>joda-time</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-api</artifactId>
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-internal-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
@@ -67,6 +109,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.billing.plugin</groupId>
+            <artifactId>killbill-plugin-api-notification</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-clock</artifactId>
         </dependency>
@@ -81,6 +127,10 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-concurrent</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-embeddeddb-h2</artifactId>
             <scope>test</scope>
         </dependency>
@@ -94,13 +144,19 @@
             <artifactId>killbill-queue</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-all</artifactId>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-queue</artifactId>
+            <type>test-jar</type>
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.skife.config</groupId>
-            <artifactId>config-magic</artifactId>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-xmlloader</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountTimelineJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountTimelineJson.java
index 228e085..7537ada 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountTimelineJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountTimelineJson.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -29,71 +31,41 @@ import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.jaxrs.resources.JaxRsResourceBase;
 import org.killbill.billing.payment.api.Payment;
-import org.killbill.billing.payment.api.Refund;
 import org.killbill.billing.util.audit.AccountAuditLogs;
 import org.killbill.billing.util.audit.AuditLog;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import com.google.common.collect.Multimap;
 
 public class AccountTimelineJson {
 
     private final AccountJson account;
     private final List<BundleJson> bundles;
     private final List<InvoiceJson> invoices;
-    private final List<PaymentJson> payments;
+    private final List<InvoicePaymentJson> payments;
 
     @JsonCreator
     public AccountTimelineJson(@JsonProperty("account") final AccountJson account,
                                @JsonProperty("bundles") final List<BundleJson> bundles,
                                @JsonProperty("invoices") final List<InvoiceJson> invoices,
-                               @JsonProperty("payments") final List<PaymentJson> payments) {
+                               @JsonProperty("payments") final List<InvoicePaymentJson> payments) {
         this.account = account;
         this.bundles = bundles;
         this.invoices = invoices;
         this.payments = payments;
     }
 
-    private String getBundleExternalKey(final UUID invoiceId, final List<Invoice> invoices, final List<SubscriptionBundle> bundles) {
-        for (final Invoice cur : invoices) {
-            if (cur.getId().equals(invoiceId)) {
-                return getBundleExternalKey(cur, bundles);
-            }
-        }
-        return null;
-    }
-
-    private String getBundleExternalKey(final Invoice invoice, final List<SubscriptionBundle> bundles) {
-        final Set<UUID> b = new HashSet<UUID>();
-        for (final InvoiceItem cur : invoice.getInvoiceItems()) {
-            b.add(cur.getBundleId());
-        }
-        boolean first = true;
-        final StringBuilder tmp = new StringBuilder();
-        for (final UUID cur : b) {
-            for (final SubscriptionBundle bt : bundles) {
-                if (bt.getId().equals(cur)) {
-                    if (!first) {
-                        tmp.append(",");
-                    }
-                    tmp.append(bt.getExternalKey());
-                    first = false;
-                    break;
-                }
-            }
-        }
-        return tmp.toString();
-    }
-
-    public AccountTimelineJson(final Account account, final List<Invoice> invoices, final List<Payment> payments,
-                               final List<SubscriptionBundle> bundles, final Multimap<UUID, Refund> refundsByPayment,
-                               final Multimap<UUID, InvoicePayment> chargebacksByPayment, final AccountAuditLogs accountAuditLogs) {
+    public AccountTimelineJson(final Account account,
+                               final List<Invoice> invoices,
+                               final List<Payment> payments,
+                               final List<InvoicePayment> invoicePayments,
+                               final List<SubscriptionBundle> bundles,
+                               final AccountAuditLogs accountAuditLogs) {
         this.account = new AccountJson(account, null, null, accountAuditLogs);
         this.bundles = new LinkedList<BundleJson>();
         for (final SubscriptionBundle bundle : bundles) {
-            final List<AuditLog> bundleAuditLogs = accountAuditLogs.getAuditLogsForBundle(bundle.getId());
             final BundleJson jsonWithSubscriptions = new BundleJson(bundle, accountAuditLogs);
             this.bundles.add(jsonWithSubscriptions);
         }
@@ -118,27 +90,10 @@ public class AccountTimelineJson {
                                               auditLogs));
         }
 
-        this.payments = new LinkedList<PaymentJson>();
+        this.payments = new LinkedList<InvoicePaymentJson>();
         for (final Payment payment : payments) {
-            final List<RefundJson> refunds = new ArrayList<RefundJson>();
-            for (final Refund refund : refundsByPayment.get(payment.getId())) {
-                final List<AuditLog> auditLogs = accountAuditLogs.getAuditLogsForRefund(refund.getId());
-                // TODO add adjusted invoice items?
-                refunds.add(new RefundJson(refund, null, auditLogs));
-            }
-
-            final List<ChargebackJson> chargebacks = new ArrayList<ChargebackJson>();
-            for (final InvoicePayment chargeback : chargebacksByPayment.get(payment.getId())) {
-                final List<AuditLog> auditLogs = accountAuditLogs.getAuditLogsForChargeback(chargeback.getId());
-                chargebacks.add(new ChargebackJson(payment.getAccountId(), chargeback, auditLogs));
-            }
-
-            final List<AuditLog> auditLogs = accountAuditLogs.getAuditLogsForPayment(payment.getId());
-            this.payments.add(new PaymentJson(payment,
-                                              getBundleExternalKey(payment.getInvoiceId(), invoices, bundles),
-                                              refunds,
-                                              chargebacks,
-                                              auditLogs));
+            final UUID invoiceId = JaxRsResourceBase.getInvoiceId(invoicePayments, payment);
+            this.payments.add(new InvoicePaymentJson(payment, invoiceId, accountAuditLogs));
         }
     }
 
@@ -154,7 +109,7 @@ public class AccountTimelineJson {
         return invoices;
     }
 
-    public List<PaymentJson> getPayments() {
+    public List<InvoicePaymentJson> getPayments() {
         return payments;
     }
 
@@ -205,4 +160,38 @@ public class AccountTimelineJson {
         result = 31 * result + (payments != null ? payments.hashCode() : 0);
         return result;
     }
+
+    private String getBundleExternalKey(final UUID invoiceId, final List<Invoice> invoices, final List<SubscriptionBundle> bundles) {
+        if (invoiceId == null) {
+            return null;
+        }
+        for (final Invoice cur : invoices) {
+            if (cur.getId().equals(invoiceId)) {
+                return getBundleExternalKey(cur, bundles);
+            }
+        }
+        return null;
+    }
+
+    private String getBundleExternalKey(final Invoice invoice, final List<SubscriptionBundle> bundles) {
+        final Set<UUID> b = new HashSet<UUID>();
+        for (final InvoiceItem cur : invoice.getInvoiceItems()) {
+            b.add(cur.getBundleId());
+        }
+        boolean first = true;
+        final StringBuilder tmp = new StringBuilder();
+        for (final UUID cur : b) {
+            for (final SubscriptionBundle bt : bundles) {
+                if (bt.getId().equals(cur)) {
+                    if (!first) {
+                        tmp.append(",");
+                    }
+                    tmp.append(bt.getExternalKey());
+                    first = false;
+                    break;
+                }
+            }
+        }
+        return tmp.toString();
+    }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BundleTimelineJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BundleTimelineJson.java
index ff0d8af..942daee 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BundleTimelineJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/BundleTimelineJson.java
@@ -27,7 +27,7 @@ public class BundleTimelineJson {
 
     private final BundleJson bundle;
 
-    private final List<PaymentJson> payments;
+    private final List<InvoicePaymentJson> payments;
 
     private final List<InvoiceJson> invoices;
 
@@ -37,7 +37,7 @@ public class BundleTimelineJson {
     @JsonCreator
     public BundleTimelineJson(@JsonProperty("viewId") final String viewId,
                               @JsonProperty("bundle") final BundleJson bundle,
-                              @JsonProperty("payments") final List<PaymentJson> payments,
+                              @JsonProperty("payments") final List<InvoicePaymentJson> payments,
                               @JsonProperty("invoices") final List<InvoiceJson> invoices,
                               @JsonProperty("reasonForChange") final String reason) {
         this.viewId = viewId;
@@ -55,7 +55,7 @@ public class BundleTimelineJson {
         return bundle;
     }
 
-    public List<PaymentJson> getPayments() {
+    public List<InvoicePaymentJson> getPayments() {
         return payments;
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/GatewayNotificationJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/GatewayNotificationJson.java
new file mode 100644
index 0000000..923258c
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/GatewayNotificationJson.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
+
+import org.killbill.billing.payment.plugin.api.GatewayNotification;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class GatewayNotificationJson extends JsonBase {
+
+    private final String kbPaymentId;
+    private final Integer status;
+    private final String entity;
+    private final Map<String, List<String>> headers;
+    private final Map<String, String> properties;
+
+    @JsonCreator
+    public GatewayNotificationJson(@JsonProperty("kbPaymentId") final String kbPaymentId,
+                                   @JsonProperty("status") final Integer status,
+                                   @JsonProperty("entity") final String entity,
+                                   @JsonProperty("headers") final Map<String, List<String>> headers,
+                                   @JsonProperty("properties") final Map<String, String> properties) {
+        this.kbPaymentId = kbPaymentId;
+        this.status = status;
+        this.entity = entity;
+        this.headers = headers;
+        this.properties = properties;
+    }
+
+    public GatewayNotificationJson(final GatewayNotification notification) {
+        this.kbPaymentId = notification.getKbPaymentId() == null ? null : notification.getKbPaymentId().toString();
+        this.status = notification.getStatus();
+        this.entity = notification.getEntity();
+        this.headers = notification.getHeaders();
+        this.properties = propertiesToMap(notification.getProperties());
+    }
+
+    public Response toResponse() {
+        final ResponseBuilder responseBuilder = Response.status(status == null ? Status.OK : Status.fromStatusCode(status));
+        if (entity != null) {
+            responseBuilder.entity(entity);
+        }
+        if (headers != null) {
+            for (final String key : headers.keySet()) {
+                if (headers.get(key) != null) {
+                    for (final String value : headers.get(key)) {
+                        responseBuilder.header(key, value);
+                    }
+                }
+            }
+        }
+
+        return responseBuilder.build();
+    }
+
+    public String getKbPaymentId() {
+        return kbPaymentId;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public String getEntity() {
+        return entity;
+    }
+
+    public Map<String, List<String>> getHeaders() {
+        return headers;
+    }
+
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer("GatewayNotificationJson{");
+        sb.append("kbPaymentId='").append(kbPaymentId).append('\'');
+        sb.append(", status=").append(status);
+        sb.append(", entity='").append(entity).append('\'');
+        sb.append(", headers=").append(headers);
+        sb.append(", properties=").append(properties);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final GatewayNotificationJson that = (GatewayNotificationJson) o;
+
+        if (entity != null ? !entity.equals(that.entity) : that.entity != null) {
+            return false;
+        }
+        if (headers != null ? !headers.equals(that.headers) : that.headers != null) {
+            return false;
+        }
+        if (kbPaymentId != null ? !kbPaymentId.equals(that.kbPaymentId) : that.kbPaymentId != null) {
+            return false;
+        }
+        if (properties != null ? !properties.equals(that.properties) : that.properties != null) {
+            return false;
+        }
+        if (status != null ? !status.equals(that.status) : that.status != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = kbPaymentId != null ? kbPaymentId.hashCode() : 0;
+        result = 31 * result + (status != null ? status.hashCode() : 0);
+        result = 31 * result + (entity != null ? entity.hashCode() : 0);
+        result = 31 * result + (headers != null ? headers.hashCode() : 0);
+        result = 31 * result + (properties != null ? properties.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageBillingAddressJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageBillingAddressJson.java
new file mode 100644
index 0000000..a294598
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageBillingAddressJson.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class HostedPaymentPageBillingAddressJson extends JsonBase {
+
+    private final String city;
+    private final String address1;
+    private final String address2;
+    private final String state;
+    private final String zip;
+    private final String country;
+
+    @JsonCreator
+    public HostedPaymentPageBillingAddressJson(@JsonProperty("city") final String city,
+                                               @JsonProperty("address1") final String address1,
+                                               @JsonProperty("address2") final String address2,
+                                               @JsonProperty("state") final String state,
+                                               @JsonProperty("zip") final String zip,
+                                               @JsonProperty("country") final String country) {
+        this.city = city;
+        this.address1 = address1;
+        this.address2 = address2;
+        this.state = state;
+        this.zip = zip;
+        this.country = country;
+    }
+
+    public String getCity() {
+        return city;
+    }
+
+    public String getAddress1() {
+        return address1;
+    }
+
+    public String getAddress2() {
+        return address2;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public String getZip() {
+        return zip;
+    }
+
+    public String getCountry() {
+        return country;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer("HostedPaymentPageBillingAddressJson{");
+        sb.append("city='").append(city).append('\'');
+        sb.append(", address1='").append(address1).append('\'');
+        sb.append(", address2='").append(address2).append('\'');
+        sb.append(", state='").append(state).append('\'');
+        sb.append(", zip='").append(zip).append('\'');
+        sb.append(", country='").append(country).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final HostedPaymentPageBillingAddressJson that = (HostedPaymentPageBillingAddressJson) o;
+
+        if (address1 != null ? !address1.equals(that.address1) : that.address1 != null) {
+            return false;
+        }
+        if (address2 != null ? !address2.equals(that.address2) : that.address2 != null) {
+            return false;
+        }
+        if (city != null ? !city.equals(that.city) : that.city != null) {
+            return false;
+        }
+        if (country != null ? !country.equals(that.country) : that.country != null) {
+            return false;
+        }
+        if (state != null ? !state.equals(that.state) : that.state != null) {
+            return false;
+        }
+        if (zip != null ? !zip.equals(that.zip) : that.zip != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = city != null ? city.hashCode() : 0;
+        result = 31 * result + (address1 != null ? address1.hashCode() : 0);
+        result = 31 * result + (address2 != null ? address2.hashCode() : 0);
+        result = 31 * result + (state != null ? state.hashCode() : 0);
+        result = 31 * result + (zip != null ? zip.hashCode() : 0);
+        result = 31 * result + (country != null ? country.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageCustomerJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageCustomerJson.java
new file mode 100644
index 0000000..3780c27
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageCustomerJson.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class HostedPaymentPageCustomerJson extends JsonBase {
+
+    private final String firstName;
+    private final String lastName;
+    private final String email;
+    private final String phone;
+
+    @JsonCreator
+    public HostedPaymentPageCustomerJson(@JsonProperty("firstName") final String firstName,
+                                         @JsonProperty("lastName") final String lastName,
+                                         @JsonProperty("email") final String email,
+                                         @JsonProperty("phone") final String phone) {
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.email = email;
+        this.phone = phone;
+    }
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer("HostedPaymentPageCustomerJson{");
+        sb.append("firstName='").append(firstName).append('\'');
+        sb.append(", lastName='").append(lastName).append('\'');
+        sb.append(", email='").append(email).append('\'');
+        sb.append(", phone='").append(phone).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final HostedPaymentPageCustomerJson that = (HostedPaymentPageCustomerJson) o;
+
+        if (email != null ? !email.equals(that.email) : that.email != null) {
+            return false;
+        }
+        if (firstName != null ? !firstName.equals(that.firstName) : that.firstName != null) {
+            return false;
+        }
+        if (lastName != null ? !lastName.equals(that.lastName) : that.lastName != null) {
+            return false;
+        }
+        if (phone != null ? !phone.equals(that.phone) : that.phone != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = firstName != null ? firstName.hashCode() : 0;
+        result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
+        result = 31 * result + (email != null ? email.hashCode() : 0);
+        result = 31 * result + (phone != null ? phone.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageFieldsJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageFieldsJson.java
new file mode 100644
index 0000000..470f8f8
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageFieldsJson.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class HostedPaymentPageFieldsJson extends JsonBase {
+
+    private final List<PluginPropertyJson> formFields;
+
+    @JsonCreator
+    public HostedPaymentPageFieldsJson(@JsonProperty("formFields") final List<PluginPropertyJson> formFields) {
+        this.formFields = formFields;
+    }
+
+    public List<PluginPropertyJson> getCustomFields() {
+        return formFields;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer("HostedPaymentPageFieldsJson{");
+        sb.append(", formFields=").append(formFields);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final HostedPaymentPageFieldsJson that = (HostedPaymentPageFieldsJson) o;
+
+        if (formFields != null ? !formFields.equals(that.formFields) : that.formFields != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return formFields != null ? formFields.hashCode() : 0;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageFormDescriptorJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageFormDescriptorJson.java
new file mode 100644
index 0000000..2753e70
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/HostedPaymentPageFormDescriptorJson.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.util.Map;
+
+import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class HostedPaymentPageFormDescriptorJson extends JsonBase {
+
+    private final String kbAccountId;
+    private final String formMethod;
+    private final String formUrl;
+    private final Map<String, String> formFields;
+    private final Map<String, String> properties;
+
+    @JsonCreator
+    public HostedPaymentPageFormDescriptorJson(@JsonProperty("kbAccountId") final String kbAccountId,
+                                               @JsonProperty("formMethod") final String formMethod,
+                                               @JsonProperty("formUrl") final String formUrl,
+                                               @JsonProperty("formFields") final Map<String, String> formFields,
+                                               @JsonProperty("properties") final Map<String, String> properties) {
+        this.kbAccountId = kbAccountId;
+        this.formMethod = formMethod;
+        this.formUrl = formUrl;
+        this.formFields = formFields;
+        this.properties = properties;
+    }
+
+    public HostedPaymentPageFormDescriptorJson(final HostedPaymentPageFormDescriptor descriptor) {
+        this.kbAccountId = descriptor.getKbAccountId().toString();
+        this.formMethod = descriptor.getFormMethod();
+        this.formUrl = descriptor.getFormUrl();
+        this.formFields = propertiesToMap(descriptor.getFormFields());
+        this.properties = propertiesToMap(descriptor.getProperties());
+    }
+
+    public String getKbAccountId() {
+        return kbAccountId;
+    }
+
+    public String getFormMethod() {
+        return formMethod;
+    }
+
+    public String getFormUrl() {
+        return formUrl;
+    }
+
+    public Map<String, String> getFormFields() {
+        return formFields;
+    }
+
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer("HostedPaymentPageFormDescriptorJson{");
+        sb.append("kbAccountId='").append(kbAccountId).append('\'');
+        sb.append(", formMethod='").append(formMethod).append('\'');
+        sb.append(", formUrl='").append(formUrl).append('\'');
+        sb.append(", formFields=").append(formFields);
+        sb.append(", properties=").append(properties);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final HostedPaymentPageFormDescriptorJson that = (HostedPaymentPageFormDescriptorJson) o;
+
+        if (formFields != null ? !formFields.equals(that.formFields) : that.formFields != null) {
+            return false;
+        }
+        if (formMethod != null ? !formMethod.equals(that.formMethod) : that.formMethod != null) {
+            return false;
+        }
+        if (formUrl != null ? !formUrl.equals(that.formUrl) : that.formUrl != null) {
+            return false;
+        }
+        if (kbAccountId != null ? !kbAccountId.equals(that.kbAccountId) : that.kbAccountId != null) {
+            return false;
+        }
+        if (properties != null ? !properties.equals(that.properties) : that.properties != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = kbAccountId != null ? kbAccountId.hashCode() : 0;
+        result = 31 * result + (formMethod != null ? formMethod.hashCode() : 0);
+        result = 31 * result + (formUrl != null ? formUrl.hashCode() : 0);
+        result = 31 * result + (formFields != null ? formFields.hashCode() : 0);
+        result = 31 * result + (properties != null ? properties.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java
index e280dc8..0d87f23 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoiceItemJson.java
@@ -18,13 +18,15 @@ package org.killbill.billing.jaxrs.json;
 
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.UUID;
 
 import javax.annotation.Nullable;
 
+import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.util.audit.AuditLog;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
@@ -91,6 +93,105 @@ public class InvoiceItemJson extends JsonBase {
              item.getAmount(), item.getCurrency(), toAuditLogJson(auditLogs));
     }
 
+    public InvoiceItem toInvoiceItem() {
+        return new InvoiceItem() {
+            @Override
+            public InvoiceItemType getInvoiceItemType() {
+                return itemType != null ? InvoiceItemType.valueOf(itemType) : null;
+            }
+
+            @Override
+            public UUID getInvoiceId() {
+                return invoiceId != null ? UUID.fromString(invoiceId) : null;
+            }
+
+            @Override
+            public UUID getAccountId() {
+                return accountId != null ? UUID.fromString(accountId) : null;
+            }
+
+            @Override
+            public LocalDate getStartDate() {
+                return startDate;
+            }
+
+            @Override
+            public LocalDate getEndDate() {
+                return endDate;
+            }
+
+            @Override
+            public BigDecimal getAmount() {
+                return amount;
+            }
+
+            @Override
+            public Currency getCurrency() {
+                return currency;
+            }
+
+            @Override
+            public String getDescription() {
+                return description;
+            }
+
+            @Override
+            public UUID getBundleId() {
+                return bundleId != null ? UUID.fromString(bundleId) : null;
+            }
+
+            @Override
+            public UUID getSubscriptionId() {
+                return subscriptionId != null ? UUID.fromString(subscriptionId) : null;
+            }
+
+            @Override
+            public String getPlanName() {
+                return planName;
+            }
+
+            @Override
+            public String getPhaseName() {
+                return phaseName;
+            }
+
+            @Override
+            public String getUsageName() {
+                return usageName;
+            }
+
+            @Override
+            public BigDecimal getRate() {
+                return null;
+            }
+
+            @Override
+            public UUID getLinkedItemId() {
+                return linkedInvoiceItemId != null ? UUID.fromString(linkedInvoiceItemId) : null;
+            }
+
+            @Override
+            public boolean matches(final Object o) {
+                return false;
+            }
+
+            @Override
+            public UUID getId() {
+                return null;
+            }
+
+            @Override
+            public DateTime getCreatedDate() {
+                return null;
+            }
+
+            @Override
+            public DateTime getUpdatedDate() {
+                return null;
+            }
+        };
+    }
+
     public InvoiceItemJson(final InvoiceItem input) {
         this(input, null);
     }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoicePaymentJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoicePaymentJson.java
new file mode 100644
index 0000000..f7c366d
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoicePaymentJson.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.util.audit.AccountAuditLogs;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class InvoicePaymentJson extends PaymentJson {
+
+    private final String targetInvoiceId;
+
+
+    @JsonCreator
+    public InvoicePaymentJson(@JsonProperty("targetInvoiceId") final String targetInvoiceId,
+                              @JsonProperty("accountId") final String accountId,
+                              @JsonProperty("paymentId") final String paymentId,
+                              @JsonProperty("paymentNumber") final String paymentNumber,
+                              @JsonProperty("paymentExternalKey") final String paymentExternalKey,
+                              @JsonProperty("authAmount") final BigDecimal authAmount,
+                              @JsonProperty("capturedAmount") final BigDecimal capturedAmount,
+                              @JsonProperty("purchasedAmount") final BigDecimal purchasedAmount,
+                              @JsonProperty("refundedAmount") final BigDecimal refundedAmount,
+                              @JsonProperty("creditedAmount") final BigDecimal creditedAmount,
+                              @JsonProperty("currency") final String currency,
+                              @JsonProperty("paymentMethodId") final String paymentMethodId,
+                              @JsonProperty("transactions") final List<? extends PaymentTransactionJson> transactions,
+                              @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
+        super(accountId, paymentId, paymentNumber, paymentExternalKey, authAmount, capturedAmount, purchasedAmount, refundedAmount, creditedAmount, currency, paymentMethodId, transactions, auditLogs);
+        this.targetInvoiceId = targetInvoiceId;
+    }
+
+    public InvoicePaymentJson(final Payment payment, @Nullable final UUID invoiceId, @Nullable final AccountAuditLogs accountAuditLogs) {
+        super(payment, accountAuditLogs);
+        //  TODO should build InvoicePaymentTransactionJson instead of PaymentTransactionJson here.
+        this.targetInvoiceId = invoiceId != null ? invoiceId.toString() : null;
+    }
+
+
+        public String getTargetInvoiceId() {
+        return targetInvoiceId;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof InvoicePaymentJson)) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        final InvoicePaymentJson that = (InvoicePaymentJson) o;
+
+        if (targetInvoiceId != null ? !targetInvoiceId.equals(that.targetInvoiceId) : that.targetInvoiceId != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (targetInvoiceId != null ? targetInvoiceId.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoicePaymentTransactionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoicePaymentTransactionJson.java
new file mode 100644
index 0000000..487825b
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/InvoicePaymentTransactionJson.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class InvoicePaymentTransactionJson extends PaymentTransactionJson {
+
+    private final Boolean isAdjusted;
+    private final List<InvoiceItemJson> adjustments;
+
+    @JsonCreator
+    public InvoicePaymentTransactionJson(@JsonProperty("transactionId") final String transactionId,
+                                         @JsonProperty("transactionExternalKey") final String transactionExternalKey,
+                                         @JsonProperty("paymentId") final String paymentId,
+                                         @JsonProperty("paymentExternalKey") final String paymentExternalKey,
+                                         @JsonProperty("transactionType") final String transactionType,
+                                         @JsonProperty("amount") final BigDecimal amount,
+                                         @JsonProperty("currency") final String currency,
+                                         @JsonProperty("effectiveDate") final DateTime effectiveDate,
+                                         @JsonProperty("status") final String status,
+                                         @JsonProperty("gatewayErrorCode") final String gatewayErrorCode,
+                                         @JsonProperty("gatewayErrorMsg") final String gatewayErrorMsg,
+                                         @JsonProperty("firstPaymentReferenceId") final String firstPaymentReferenceId,
+                                         @JsonProperty("secondPaymentReferenceId") final String secondPaymentReferenceId,
+                                         @JsonProperty("properties") final List<PluginPropertyJson> properties,
+                                         @JsonProperty("isAdjusted") final Boolean isAdjusted,
+                                         @JsonProperty("adjustments") final List<InvoiceItemJson> adjustments,
+                                         @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
+        super(transactionId, transactionExternalKey, paymentId, paymentExternalKey, transactionType, amount, currency, effectiveDate, status,
+              gatewayErrorCode, gatewayErrorMsg, firstPaymentReferenceId, secondPaymentReferenceId, properties, auditLogs);
+        this.isAdjusted = isAdjusted;
+        this.adjustments = adjustments;
+    }
+
+    @JsonProperty("isAdjusted")
+    public Boolean isAdjusted() {
+        return isAdjusted;
+    }
+
+    public List<InvoiceItemJson> getAdjustments() {
+        return adjustments;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof InvoicePaymentTransactionJson)) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        final InvoicePaymentTransactionJson that = (InvoicePaymentTransactionJson) o;
+
+        if (adjustments != null ? !adjustments.equals(that.adjustments) : that.adjustments != null) {
+            return false;
+        }
+        if (isAdjusted != null ? !isAdjusted.equals(that.isAdjusted) : that.isAdjusted != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (isAdjusted != null ? isAdjusted.hashCode() : 0);
+        result = 31 * result + (adjustments != null ? adjustments.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/JsonBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/JsonBase.java
index 9ac1001..a92f6e9 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/JsonBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/JsonBase.java
@@ -16,11 +16,16 @@
 
 package org.killbill.billing.jaxrs.json;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
 
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.util.audit.AuditLog;
 
 import com.google.common.base.Function;
@@ -59,4 +64,31 @@ public abstract class JsonBase {
     public List<AuditLogJson> getAuditLogs() {
         return auditLogs;
     }
+
+    protected List<PluginProperty> propertiesToList(final Map<String, String> propertiesMap) {
+        final List<PluginProperty> properties = new LinkedList<PluginProperty>();
+        for (final String key : propertiesMap.keySet()) {
+            final PluginProperty property = new PluginProperty(key, propertiesMap.get(key), false);
+            properties.add(property);
+        }
+        return properties;
+    }
+
+    protected Map<String, String> propertiesToMap(final Iterable<PluginProperty> properties) {
+        final Map<String, String> propertiesMap = new HashMap<String, String>();
+        for (final PluginProperty pluginProperty : properties) {
+            if (pluginProperty.getValue() != null) {
+                propertiesMap.put(pluginProperty.getKey(), pluginProperty.getValue().toString());
+            }
+        }
+        return propertiesMap;
+    }
+
+    protected static List<PluginPropertyJson> toPluginPropertyJson(final Iterable<PluginProperty> properties) {
+        final List<PluginPropertyJson> pluginProperties = new ArrayList<PluginPropertyJson>();
+        for (final PluginProperty pluginProperty : properties) {
+            pluginProperties.add(new PluginPropertyJson(pluginProperty));
+        }
+        return pluginProperties;
+    }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueStateJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueStateJson.java
index ec76f2f..79af69f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueStateJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/OverdueStateJson.java
@@ -62,7 +62,7 @@ public class OverdueStateJson {
         Period reevaluationIntervalPeriod = null;
         try {
             reevaluationIntervalPeriod = overdueState.getReevaluationInterval();
-        } catch (OverdueApiException ignored) {
+        } catch (final OverdueApiException ignored) {
         }
 
         if (reevaluationIntervalPeriod != null) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentJson.java
index 8441ecb..4bf0f5d 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentJson.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * Groupon 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:
  *
@@ -21,117 +21,93 @@ import java.util.List;
 
 import javax.annotation.Nullable;
 
-import org.joda.time.DateTime;
-
-import org.killbill.clock.DefaultClock;
 import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.util.audit.AccountAuditLogs;
 import org.killbill.billing.util.audit.AuditLog;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 public class PaymentJson extends JsonBase {
 
-    private final BigDecimal paidAmount;
-    private final BigDecimal amount;
     private final String accountId;
-    private final String invoiceId;
     private final String paymentId;
     private final String paymentNumber;
-    private final DateTime requestedDate;
-    private final DateTime effectiveDate;
-    private final Integer retryCount;
+    private final String paymentExternalKey;
+    private final BigDecimal authAmount;
+    private final BigDecimal capturedAmount;
+    private final BigDecimal purchasedAmount;
+    private final BigDecimal refundedAmount;
+    private final BigDecimal creditedAmount;
     private final String currency;
-    private final String status;
-    private final String gatewayErrorCode;
-    private final String gatewayErrorMsg;
     private final String paymentMethodId;
-    private final String bundleKeys;
-    private final List<RefundJson> refunds;
-    private final List<ChargebackJson> chargebacks;
+    private final List<? extends PaymentTransactionJson> transactions;
 
     @JsonCreator
-    public PaymentJson(@JsonProperty("amount") final BigDecimal amount,
-                       @JsonProperty("paidAmount") final BigDecimal paidAmount,
-                       @JsonProperty("accountId") final String accountId,
-                       @JsonProperty("invoiceId") final String invoiceId,
+    public PaymentJson(@JsonProperty("accountId") final String accountId,
                        @JsonProperty("paymentId") final String paymentId,
                        @JsonProperty("paymentNumber") final String paymentNumber,
-                       @JsonProperty("paymentMethodId") final String paymentMethodId,
-                       @JsonProperty("requestedDate") final DateTime requestedDate,
-                       @JsonProperty("effectiveDate") final DateTime effectiveDate,
-                       @JsonProperty("retryCount") final Integer retryCount,
+                       @JsonProperty("paymentExternalKey") final String paymentExternalKey,
+                       @JsonProperty("authAmount") final BigDecimal authAmount,
+                       @JsonProperty("capturedAmount") final BigDecimal capturedAmount,
+                       @JsonProperty("purchasedAmount") final BigDecimal purchasedAmount,
+                       @JsonProperty("refundedAmount") final BigDecimal refundedAmount,
+                       @JsonProperty("creditedAmount") final BigDecimal creditedAmount,
                        @JsonProperty("currency") final String currency,
-                       @JsonProperty("status") final String status,
-                       @JsonProperty("gatewayErrorCode") final String gatewayErrorCode,
-                       @JsonProperty("gatewayErrorMsg") final String gatewayErrorMsg,
-                       @JsonProperty("externalBundleKeys") final String bundleKeys,
-                       @JsonProperty("refunds") final List<RefundJson> refunds,
-                       @JsonProperty("chargebacks") final List<ChargebackJson> chargebacks,
+                       @JsonProperty("paymentMethodId") final String paymentMethodId,
+                       @JsonProperty("transactions") final List<? extends PaymentTransactionJson> transactions,
                        @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
         super(auditLogs);
-        this.amount = amount;
-        this.paidAmount = paidAmount;
-        this.invoiceId = invoiceId;
         this.accountId = accountId;
         this.paymentId = paymentId;
         this.paymentNumber = paymentNumber;
-        this.paymentMethodId = paymentMethodId;
-        this.requestedDate = DefaultClock.toUTCDateTime(requestedDate);
-        this.effectiveDate = DefaultClock.toUTCDateTime(effectiveDate);
+        this.paymentExternalKey = paymentExternalKey;
+        this.authAmount = authAmount;
+        this.capturedAmount = capturedAmount;
+        this.purchasedAmount = purchasedAmount;
+        this.refundedAmount = refundedAmount;
+        this.creditedAmount = creditedAmount;
         this.currency = currency;
-        this.retryCount = retryCount;
-        this.status = status;
-        this.gatewayErrorCode = gatewayErrorCode;
-        this.gatewayErrorMsg = gatewayErrorMsg;
-        this.bundleKeys = bundleKeys;
-        this.refunds = refunds;
-        this.chargebacks = chargebacks;
-    }
-
-    public PaymentJson(final Payment payment, final String bundleExternalKey,
-                       final List<RefundJson> refunds, final List<ChargebackJson> chargebacks) {
-        this(payment, bundleExternalKey, refunds, chargebacks, null);
-    }
-
-    public PaymentJson(final Payment payment, final String bundleExternalKey,
-                       final List<RefundJson> refunds, final List<ChargebackJson> chargebacks,
-                       @Nullable final List<AuditLog> auditLogs) {
-        this(payment.getAmount(), payment.getPaidAmount(), payment.getAccountId().toString(),
-             payment.getInvoiceId().toString(), payment.getId().toString(),
-             payment.getPaymentNumber().toString(),
-             payment.getPaymentMethodId().toString(),
-             payment.getEffectiveDate(), payment.getEffectiveDate(),
-             payment.getAttempts().size(), payment.getCurrency().toString(), payment.getPaymentStatus().toString(),
-             payment.getAttempts().get(payment.getAttempts().size() - 1).getGatewayErrorCode(),
-             payment.getAttempts().get(payment.getAttempts().size() - 1).getGatewayErrorMsg(),
-             bundleExternalKey, refunds, chargebacks, toAuditLogJson(auditLogs));
-    }
-
-    public PaymentJson(final Payment payment, final List<AuditLog> auditLogs) {
-        this(payment, null, null, null, auditLogs);
-    }
-
-    public String getBundleKeys() {
-        return bundleKeys;
-    }
-
-    public BigDecimal getPaidAmount() {
-        return paidAmount;
-    }
-
-    public BigDecimal getAmount() {
-        return amount;
+        this.paymentMethodId = paymentMethodId;
+        this.transactions = transactions;
+    }
+
+    public PaymentJson(final Payment dp, @Nullable final AccountAuditLogs accountAuditLogs) {
+        this(dp.getAccountId().toString(),
+             dp.getId().toString(),
+             dp.getPaymentNumber().toString(),
+             dp.getExternalKey(),
+             dp.getAuthAmount(),
+             dp.getCapturedAmount(),
+             dp.getPurchasedAmount(),
+             dp.getRefundedAmount(),
+             dp.getCreditedAmount(),
+             dp.getCurrency() != null ? dp.getCurrency().toString() : null,
+             dp.getPaymentMethodId() != null ? dp.getPaymentMethodId().toString() : null,
+             getTransactions(dp.getTransactions(), dp.getExternalKey(), accountAuditLogs),
+             toAuditLogJson(accountAuditLogs == null ? null : accountAuditLogs.getAuditLogsForPayment(dp.getId())));
+    }
+
+    private static List<PaymentTransactionJson> getTransactions(final Iterable<PaymentTransaction> transactions, final String paymentExternalKey, @Nullable final AccountAuditLogs accountAuditLogs) {
+        return ImmutableList.copyOf(Iterables.transform(transactions,
+                                                        new Function<PaymentTransaction, PaymentTransactionJson>() {
+                                                            @Override
+                                                            public PaymentTransactionJson apply(final PaymentTransaction paymentTransaction) {
+                                                                final List<AuditLog> auditLogsForPaymentTransaction = accountAuditLogs == null ? null : accountAuditLogs.getAuditLogsForPaymentTransaction(paymentTransaction.getId());
+                                                                return new PaymentTransactionJson(paymentTransaction, paymentExternalKey, auditLogsForPaymentTransaction);
+                                                            }
+                                                        }
+                                                       ));
     }
 
     public String getAccountId() {
         return accountId;
     }
 
-    public String getInvoiceId() {
-        return invoiceId;
-    }
-
     public String getPaymentId() {
         return paymentId;
     }
@@ -140,67 +116,59 @@ public class PaymentJson extends JsonBase {
         return paymentNumber;
     }
 
-    public DateTime getRequestedDate() {
-        return requestedDate;
+    public String getPaymentExternalKey() {
+        return paymentExternalKey;
     }
 
-    public DateTime getEffectiveDate() {
-        return effectiveDate;
+    public BigDecimal getAuthAmount() {
+        return authAmount;
     }
 
-    public Integer getRetryCount() {
-        return retryCount;
+    public BigDecimal getCapturedAmount() {
+        return capturedAmount;
     }
 
-    public String getCurrency() {
-        return currency;
+    public BigDecimal getRefundedAmount() {
+        return refundedAmount;
     }
 
-    public String getStatus() {
-        return status;
+    public BigDecimal getPurchasedAmount() {
+        return purchasedAmount;
     }
 
-    public String getGatewayErrorCode() {
-        return gatewayErrorCode;
+    public BigDecimal getCreditedAmount() {
+        return creditedAmount;
     }
 
-    public String getGatewayErrorMsg() {
-        return gatewayErrorMsg;
+    public String getCurrency() {
+        return currency;
     }
 
     public String getPaymentMethodId() {
         return paymentMethodId;
     }
 
-    public List<RefundJson> getRefunds() {
-        return refunds;
-    }
-
-    public List<ChargebackJson> getChargebacks() {
-        return chargebacks;
+    public List<? extends PaymentTransactionJson> getTransactions() {
+        return transactions;
     }
 
     @Override
     public String toString() {
-        return "PaymentJson{" +
-               "paidAmount=" + paidAmount +
-               ", amount=" + amount +
-               ", accountId='" + accountId + '\'' +
-               ", invoiceId='" + invoiceId + '\'' +
-               ", paymentId='" + paymentId + '\'' +
-               ", paymentNumber='" + paymentNumber + '\'' +
-               ", requestedDate=" + requestedDate +
-               ", effectiveDate=" + effectiveDate +
-               ", retryCount=" + retryCount +
-               ", currency='" + currency + '\'' +
-               ", status='" + status + '\'' +
-               ", gatewayErrorCode='" + gatewayErrorCode + '\'' +
-               ", gatewayErrorMsg='" + gatewayErrorMsg + '\'' +
-               ", paymentMethodId='" + paymentMethodId + '\'' +
-               ", bundleKeys='" + bundleKeys + '\'' +
-               ", refunds=" + refunds +
-               ", chargebacks=" + chargebacks +
-               '}';
+        final StringBuilder sb = new StringBuilder("PaymentJson{");
+        sb.append("accountId='").append(accountId).append('\'');
+        sb.append(", paymentId='").append(paymentId).append('\'');
+        sb.append(", paymentNumber='").append(paymentNumber).append('\'');
+        sb.append(", paymentExternalKey='").append(paymentExternalKey).append('\'');
+        sb.append(", authAmount=").append(authAmount);
+        sb.append(", capturedAmount=").append(capturedAmount);
+        sb.append(", purchasedAmount=").append(purchasedAmount);
+        sb.append(", refundedAmount=").append(refundedAmount);
+        sb.append(", creditedAmount=").append(creditedAmount);
+        sb.append(", currency='").append(currency).append('\'');
+        sb.append(", paymentMethodId='").append(paymentMethodId).append('\'');
+        sb.append(", transactions=").append(transactions);
+        sb.append('}');
+        return sb.toString();
     }
 
     @Override
@@ -217,31 +185,22 @@ public class PaymentJson extends JsonBase {
         if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
         }
-        if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) {
+        if (authAmount != null ? authAmount.compareTo(that.authAmount) != 0 : that.authAmount != null) {
             return false;
         }
-        if (bundleKeys != null ? !bundleKeys.equals(that.bundleKeys) : that.bundleKeys != null) {
+        if (capturedAmount != null ? capturedAmount.compareTo(that.capturedAmount) != 0 : that.capturedAmount != null) {
             return false;
         }
-        if (chargebacks != null ? !chargebacks.equals(that.chargebacks) : that.chargebacks != null) {
+        if (creditedAmount != null ? creditedAmount.compareTo(that.creditedAmount) != 0 : that.creditedAmount != null) {
             return false;
         }
-        if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
+        if (purchasedAmount != null ? purchasedAmount.compareTo(that.purchasedAmount) != 0 : that.purchasedAmount != null) {
             return false;
         }
-        if (effectiveDate != null ? effectiveDate.compareTo(that.effectiveDate) != 0 : that.effectiveDate != null) {
-            return false;
-        }
-        if (gatewayErrorCode != null ? !gatewayErrorCode.equals(that.gatewayErrorCode) : that.gatewayErrorCode != null) {
-            return false;
-        }
-        if (gatewayErrorMsg != null ? !gatewayErrorMsg.equals(that.gatewayErrorMsg) : that.gatewayErrorMsg != null) {
-            return false;
-        }
-        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+        if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
             return false;
         }
-        if (paidAmount != null ? paidAmount.compareTo(that.paidAmount) != 0 : that.paidAmount != null) {
+        if (paymentExternalKey != null ? !paymentExternalKey.equals(that.paymentExternalKey) : that.paymentExternalKey != null) {
             return false;
         }
         if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
@@ -253,40 +212,30 @@ public class PaymentJson extends JsonBase {
         if (paymentNumber != null ? !paymentNumber.equals(that.paymentNumber) : that.paymentNumber != null) {
             return false;
         }
-        if (refunds != null ? !refunds.equals(that.refunds) : that.refunds != null) {
+        if (refundedAmount != null ? refundedAmount.compareTo(that.refundedAmount) != 0 : that.refundedAmount != null) {
             return false;
         }
-        if (requestedDate != null ? requestedDate.compareTo(that.requestedDate) != 0 : that.requestedDate != null) {
-            return false;
-        }
-        if (retryCount != null ? !retryCount.equals(that.retryCount) : that.retryCount != null) {
-            return false;
-        }
-        if (status != null ? !status.equals(that.status) : that.status != null) {
+        if (transactions != null ? !transactions.equals(that.transactions) : that.transactions != null) {
             return false;
         }
+
         return true;
     }
 
     @Override
     public int hashCode() {
-        int result = paidAmount != null ? paidAmount.hashCode() : 0;
-        result = 31 * result + (amount != null ? amount.hashCode() : 0);
-        result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
-        result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
+        int result = accountId != null ? accountId.hashCode() : 0;
         result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
         result = 31 * result + (paymentNumber != null ? paymentNumber.hashCode() : 0);
-        result = 31 * result + (requestedDate != null ? requestedDate.hashCode() : 0);
-        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
-        result = 31 * result + (retryCount != null ? retryCount.hashCode() : 0);
+        result = 31 * result + (paymentExternalKey != null ? paymentExternalKey.hashCode() : 0);
+        result = 31 * result + (authAmount != null ? authAmount.hashCode() : 0);
+        result = 31 * result + (capturedAmount != null ? capturedAmount.hashCode() : 0);
+        result = 31 * result + (creditedAmount != null ? creditedAmount.hashCode() : 0);
+        result = 31 * result + (purchasedAmount != null ? purchasedAmount.hashCode() : 0);
+        result = 31 * result + (refundedAmount != null ? refundedAmount.hashCode() : 0);
         result = 31 * result + (currency != null ? currency.hashCode() : 0);
-        result = 31 * result + (status != null ? status.hashCode() : 0);
-        result = 31 * result + (gatewayErrorCode != null ? gatewayErrorCode.hashCode() : 0);
-        result = 31 * result + (gatewayErrorMsg != null ? gatewayErrorMsg.hashCode() : 0);
         result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
-        result = 31 * result + (bundleKeys != null ? bundleKeys.hashCode() : 0);
-        result = 31 * result + (refunds != null ? refunds.hashCode() : 0);
-        result = 31 * result + (chargebacks != null ? chargebacks.hashCode() : 0);
+        result = 31 * result + (transactions != null ? transactions.hashCode() : 0);
         return result;
     }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentMethodJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentMethodJson.java
index cd681b6..7117692 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentMethodJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentMethodJson.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -24,11 +26,10 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.payment.api.PaymentMethod;
-import org.killbill.billing.payment.api.PaymentMethodKVInfo;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.util.audit.AccountAuditLogs;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
@@ -38,6 +39,7 @@ import com.google.common.collect.Collections2;
 
 public class PaymentMethodJson extends JsonBase {
 
+    private final String externalKey;
     private final String paymentMethodId;
     private final String accountId;
     private final Boolean isDefault;
@@ -46,12 +48,14 @@ public class PaymentMethodJson extends JsonBase {
 
     @JsonCreator
     public PaymentMethodJson(@JsonProperty("paymentMethodId") final String paymentMethodId,
+                             @JsonProperty("externalKey") final String externalKey,
                              @JsonProperty("accountId") final String accountId,
                              @JsonProperty("isDefault") final Boolean isDefault,
                              @JsonProperty("pluginName") final String pluginName,
                              @JsonProperty("pluginInfo") final PaymentMethodPluginDetailJson pluginInfo,
                              @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
         super(auditLogs);
+        this.externalKey = externalKey;
         this.paymentMethodId = paymentMethodId;
         this.accountId = accountId;
         this.isDefault = isDefault;
@@ -64,32 +68,20 @@ public class PaymentMethodJson extends JsonBase {
         final PaymentMethodPlugin pluginDetail = in.getPluginDetail();
         PaymentMethodPluginDetailJson pluginDetailJson = null;
         if (pluginDetail != null) {
-            List<PaymentMethodProperties> properties = null;
+            List<PluginPropertyJson> properties = null;
             if (pluginDetail.getProperties() != null) {
-                properties = new ArrayList<PaymentMethodJson.PaymentMethodProperties>(Collections2.transform(pluginDetail.getProperties(), new Function<PaymentMethodKVInfo, PaymentMethodProperties>() {
+                properties = new ArrayList<PluginPropertyJson>(Collections2.transform(pluginDetail.getProperties(), new Function<PluginProperty, PluginPropertyJson>() {
                     @Override
-                    public PaymentMethodProperties apply(final PaymentMethodKVInfo input) {
-                        return new PaymentMethodProperties(input.getKey(), input.getValue() == null ? null : input.getValue().toString(), input.getIsUpdatable());
+                    public PluginPropertyJson apply(final PluginProperty input) {
+                        return new PluginPropertyJson(input.getKey(), input.getValue() == null ? null : input.getValue().toString(), input.getIsUpdatable());
                     }
                 }));
             }
             pluginDetailJson = new PaymentMethodPluginDetailJson(pluginDetail.getExternalPaymentMethodId(),
                                                                  pluginDetail.isDefaultPaymentMethod(),
-                                                                 pluginDetail.getType(),
-                                                                 pluginDetail.getCCName(),
-                                                                 pluginDetail.getCCType(),
-                                                                 pluginDetail.getCCExpirationMonth(),
-                                                                 pluginDetail.getCCExpirationYear(),
-                                                                 pluginDetail.getCCLast4(),
-                                                                 pluginDetail.getAddress1(),
-                                                                 pluginDetail.getAddress2(),
-                                                                 pluginDetail.getCity(),
-                                                                 pluginDetail.getState(),
-                                                                 pluginDetail.getZip(),
-                                                                 pluginDetail.getCountry(),
                                                                  properties);
         }
-        return new PaymentMethodJson(in.getId().toString(), account.getId().toString(), isDefault, in.getPluginName(),
+        return new PaymentMethodJson(in.getId().toString(), in.getExternalKey(), account.getId().toString(), isDefault, in.getPluginName(),
                                      pluginDetailJson, toAuditLogJson(accountAuditLogs == null ? null : accountAuditLogs.getAuditLogsForPaymentMethod(in.getId())));
     }
 
@@ -111,6 +103,11 @@ public class PaymentMethodJson extends JsonBase {
             }
 
             @Override
+            public String getExternalKey() {
+                return externalKey;
+            }
+
+            @Override
             public DateTime getCreatedDate() {
                 return null;
             }
@@ -138,90 +135,17 @@ public class PaymentMethodJson extends JsonBase {
                         // N/A
                         return false;
                     }
-
-                    @Override
-                    public String getType() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getCCName() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getCCType() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getCCExpirationMonth() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getCCExpirationYear() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getCCLast4() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getAddress1() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getAddress2() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getCity() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getState() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getZip() {
-                        // N/A
-                        return null;
-                    }
-
-                    @Override
-                    public String getCountry() {
-                        // N/A
-                        return null;
-                    }
-
                     @Override
                     public String getExternalPaymentMethodId() {
                         return pluginInfo.getExternalPaymentId();
                     }
 
                     @Override
-                    public List<PaymentMethodKVInfo> getProperties() {
+                    public List<PluginProperty> getProperties() {
                         if (pluginInfo.getProperties() != null) {
-                            final List<PaymentMethodKVInfo> result = new LinkedList<PaymentMethodKVInfo>();
-                            for (final PaymentMethodProperties cur : pluginInfo.getProperties()) {
-                                result.add(new PaymentMethodKVInfo(cur.getKey(), cur.getValue(), cur.isUpdatable));
+                            final List<PluginProperty> result = new LinkedList<PluginProperty>();
+                            for (final PluginPropertyJson cur : pluginInfo.getProperties()) {
+                                result.add(new PluginProperty(cur.getKey(), cur.getValue(), cur.getIsUpdatable()));
                             }
                             return result;
                         }
@@ -253,6 +177,10 @@ public class PaymentMethodJson extends JsonBase {
         return pluginInfo;
     }
 
+    public String getExternalKey() {
+        return externalKey;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("PaymentMethodJson{");
@@ -309,50 +237,14 @@ public class PaymentMethodJson extends JsonBase {
 
         private final String externalPaymentId;
         private final Boolean isDefaultPaymentMethod;
-        private final String type;
-        private final String ccName;
-        private final String ccType;
-        private final String ccExpirationMonth;
-        private final String ccExpirationYear;
-        private final String ccLast4;
-        private final String address1;
-        private final String address2;
-        private final String city;
-        private final String state;
-        private final String zip;
-        private final String country;
-        private final List<PaymentMethodProperties> properties;
+        private final List<PluginPropertyJson> properties;
 
         @JsonCreator
         public PaymentMethodPluginDetailJson(@JsonProperty("externalPaymentId") final String externalPaymentId,
                                              @JsonProperty("isDefaultPaymentMethod") final Boolean isDefaultPaymentMethod,
-                                             @JsonProperty("type") final String type,
-                                             @JsonProperty("ccName") final String ccName,
-                                             @JsonProperty("ccType") final String ccType,
-                                             @JsonProperty("ccExpirationMonth") final String ccExpirationMonth,
-                                             @JsonProperty("ccExpirationYear") final String ccExpirationYear,
-                                             @JsonProperty("ccLast4") final String ccLast4,
-                                             @JsonProperty("address1") final String address1,
-                                             @JsonProperty("address2") final String address2,
-                                             @JsonProperty("city") final String city,
-                                             @JsonProperty("state") final String state,
-                                             @JsonProperty("zip") final String zip,
-                                             @JsonProperty("country") final String country,
-                                             @JsonProperty("properties") final List<PaymentMethodProperties> properties) {
+                                             @JsonProperty("properties") final List<PluginPropertyJson> properties) {
             this.externalPaymentId = externalPaymentId;
             this.isDefaultPaymentMethod = isDefaultPaymentMethod;
-            this.type = type;
-            this.ccName = ccName;
-            this.ccType = ccType;
-            this.ccExpirationMonth = ccExpirationMonth;
-            this.ccExpirationYear = ccExpirationYear;
-            this.ccLast4 = ccLast4;
-            this.address1 = address1;
-            this.address2 = address2;
-            this.city = city;
-            this.state = state;
-            this.zip = zip;
-            this.country = country;
             this.properties = properties;
         }
 
@@ -364,55 +256,7 @@ public class PaymentMethodJson extends JsonBase {
             return isDefaultPaymentMethod;
         }
 
-        public String getType() {
-            return type;
-        }
-
-        public String getCcName() {
-            return ccName;
-        }
-
-        public String getCcType() {
-            return ccType;
-        }
-
-        public String getCcExpirationMonth() {
-            return ccExpirationMonth;
-        }
-
-        public String getCcExpirationYear() {
-            return ccExpirationYear;
-        }
-
-        public String getCcLast4() {
-            return ccLast4;
-        }
-
-        public String getAddress1() {
-            return address1;
-        }
-
-        public String getAddress2() {
-            return address2;
-        }
-
-        public String getCity() {
-            return city;
-        }
-
-        public String getState() {
-            return state;
-        }
-
-        public String getZip() {
-            return zip;
-        }
-
-        public String getCountry() {
-            return country;
-        }
-
-        public List<PaymentMethodProperties> getProperties() {
+        public List<PluginPropertyJson> getProperties() {
             return properties;
         }
 
@@ -421,18 +265,6 @@ public class PaymentMethodJson extends JsonBase {
             final StringBuilder sb = new StringBuilder("PaymentMethodPluginDetailJson{");
             sb.append("externalPaymentId='").append(externalPaymentId).append('\'');
             sb.append(", isDefaultPaymentMethod=").append(isDefaultPaymentMethod);
-            sb.append(", type='").append(type).append('\'');
-            sb.append(", ccName='").append(ccName).append('\'');
-            sb.append(", ccType='").append(ccType).append('\'');
-            sb.append(", ccExpirationMonth='").append(ccExpirationMonth).append('\'');
-            sb.append(", ccExpirationYear='").append(ccExpirationYear).append('\'');
-            sb.append(", ccLast4='").append(ccLast4).append('\'');
-            sb.append(", address1='").append(address1).append('\'');
-            sb.append(", address2='").append(address2).append('\'');
-            sb.append(", city='").append(city).append('\'');
-            sb.append(", state='").append(state).append('\'');
-            sb.append(", zip='").append(zip).append('\'');
-            sb.append(", country='").append(country).append('\'');
             sb.append(", properties=").append(properties);
             sb.append('}');
             return sb.toString();
@@ -449,33 +281,6 @@ public class PaymentMethodJson extends JsonBase {
 
             final PaymentMethodPluginDetailJson that = (PaymentMethodPluginDetailJson) o;
 
-            if (address1 != null ? !address1.equals(that.address1) : that.address1 != null) {
-                return false;
-            }
-            if (address2 != null ? !address2.equals(that.address2) : that.address2 != null) {
-                return false;
-            }
-            if (ccExpirationMonth != null ? !ccExpirationMonth.equals(that.ccExpirationMonth) : that.ccExpirationMonth != null) {
-                return false;
-            }
-            if (ccExpirationYear != null ? !ccExpirationYear.equals(that.ccExpirationYear) : that.ccExpirationYear != null) {
-                return false;
-            }
-            if (ccLast4 != null ? !ccLast4.equals(that.ccLast4) : that.ccLast4 != null) {
-                return false;
-            }
-            if (ccName != null ? !ccName.equals(that.ccName) : that.ccName != null) {
-                return false;
-            }
-            if (ccType != null ? !ccType.equals(that.ccType) : that.ccType != null) {
-                return false;
-            }
-            if (city != null ? !city.equals(that.city) : that.city != null) {
-                return false;
-            }
-            if (country != null ? !country.equals(that.country) : that.country != null) {
-                return false;
-            }
             if (externalPaymentId != null ? !externalPaymentId.equals(that.externalPaymentId) : that.externalPaymentId != null) {
                 return false;
             }
@@ -485,16 +290,6 @@ public class PaymentMethodJson extends JsonBase {
             if (properties != null ? !properties.equals(that.properties) : that.properties != null) {
                 return false;
             }
-            if (state != null ? !state.equals(that.state) : that.state != null) {
-                return false;
-            }
-            if (type != null ? !type.equals(that.type) : that.type != null) {
-                return false;
-            }
-            if (zip != null ? !zip.equals(that.zip) : that.zip != null) {
-                return false;
-            }
-
             return true;
         }
 
@@ -502,91 +297,8 @@ public class PaymentMethodJson extends JsonBase {
         public int hashCode() {
             int result = externalPaymentId != null ? externalPaymentId.hashCode() : 0;
             result = 31 * result + (isDefaultPaymentMethod != null ? isDefaultPaymentMethod.hashCode() : 0);
-            result = 31 * result + (type != null ? type.hashCode() : 0);
-            result = 31 * result + (ccName != null ? ccName.hashCode() : 0);
-            result = 31 * result + (ccType != null ? ccType.hashCode() : 0);
-            result = 31 * result + (ccExpirationMonth != null ? ccExpirationMonth.hashCode() : 0);
-            result = 31 * result + (ccExpirationYear != null ? ccExpirationYear.hashCode() : 0);
-            result = 31 * result + (ccLast4 != null ? ccLast4.hashCode() : 0);
-            result = 31 * result + (address1 != null ? address1.hashCode() : 0);
-            result = 31 * result + (address2 != null ? address2.hashCode() : 0);
-            result = 31 * result + (city != null ? city.hashCode() : 0);
-            result = 31 * result + (state != null ? state.hashCode() : 0);
-            result = 31 * result + (zip != null ? zip.hashCode() : 0);
-            result = 31 * result + (country != null ? country.hashCode() : 0);
             result = 31 * result + (properties != null ? properties.hashCode() : 0);
             return result;
         }
     }
-
-    public static final class PaymentMethodProperties {
-
-        private final String key;
-        private final String value;
-        private final Boolean isUpdatable;
-
-        @JsonCreator
-        public PaymentMethodProperties(@JsonProperty("key") final String key,
-                                       @JsonProperty("value") final String value,
-                                       @JsonProperty("isUpdatable") final Boolean isUpdatable) {
-            super();
-            this.key = key;
-            this.value = value;
-            this.isUpdatable = isUpdatable;
-        }
-
-        public String getKey() {
-            return key;
-        }
-
-        public String getValue() {
-            return value;
-        }
-
-        public Boolean getIsUpdatable() {
-            return isUpdatable;
-        }
-
-        @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder("PaymentMethodProperties{");
-            sb.append("key='").append(key).append('\'');
-            sb.append(", value='").append(value).append('\'');
-            sb.append(", isUpdatable=").append(isUpdatable);
-            sb.append('}');
-            return sb.toString();
-        }
-
-        @Override
-        public boolean equals(final Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            final PaymentMethodProperties that = (PaymentMethodProperties) o;
-
-            if (isUpdatable != null ? !isUpdatable.equals(that.isUpdatable) : that.isUpdatable != null) {
-                return false;
-            }
-            if (key != null ? !key.equals(that.key) : that.key != null) {
-                return false;
-            }
-            if (value != null ? !value.equals(that.value) : that.value != null) {
-                return false;
-            }
-
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = key != null ? key.hashCode() : 0;
-            result = 31 * result + (value != null ? value.hashCode() : 0);
-            result = 31 * result + (isUpdatable != null ? isUpdatable.hashCode() : 0);
-            return result;
-        }
-    }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentTransactionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentTransactionJson.java
new file mode 100644
index 0000000..09fb976
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PaymentTransactionJson.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.util.audit.AuditLog;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PaymentTransactionJson extends JsonBase {
+
+    private final String transactionId;
+    private final String transactionExternalKey;
+    private final String paymentId;
+    private final String paymentExternalKey;
+    private final String transactionType;
+    private final DateTime effectiveDate;
+    private final String status;
+    private final BigDecimal amount;
+    private final String currency;
+    private final String gatewayErrorCode;
+    private final String gatewayErrorMsg;
+    // Plugin specific fields
+    private final String firstPaymentReferenceId;
+    private final String secondPaymentReferenceId;
+    private final List<PluginPropertyJson> properties;
+
+    @JsonCreator
+    public PaymentTransactionJson(@JsonProperty("transactionId") final String transactionId,
+                                  @JsonProperty("transactionExternalKey") final String transactionExternalKey,
+                                  @JsonProperty("paymentId") final String paymentId,
+                                  @JsonProperty("paymentExternalKey") final String paymentExternalKey,
+                                  @JsonProperty("transactionType") final String transactionType,
+                                  @JsonProperty("amount") final BigDecimal amount,
+                                  @JsonProperty("currency") final String currency,
+                                  @JsonProperty("effectiveDate") final DateTime effectiveDate,
+                                  @JsonProperty("status") final String status,
+                                  @JsonProperty("gatewayErrorCode") final String gatewayErrorCode,
+                                  @JsonProperty("gatewayErrorMsg") final String gatewayErrorMsg,
+                                  @JsonProperty("firstPaymentReferenceId") final String firstPaymentReferenceId,
+                                  @JsonProperty("secondPaymentReferenceId") final String secondPaymentReferenceId,
+                                  @JsonProperty("properties") final List<PluginPropertyJson> properties,
+                                  @JsonProperty("auditLogs") @Nullable final List<AuditLogJson> auditLogs) {
+        super(auditLogs);
+        this.transactionId = transactionId;
+        this.transactionExternalKey = transactionExternalKey;
+        this.paymentId = paymentId;
+        this.paymentExternalKey = paymentExternalKey;
+        this.transactionType = transactionType;
+        this.effectiveDate = effectiveDate;
+        this.status = status;
+        this.amount = amount;
+        this.currency = currency;
+        this.gatewayErrorCode = gatewayErrorCode;
+        this.gatewayErrorMsg = gatewayErrorMsg;
+        this.firstPaymentReferenceId = firstPaymentReferenceId;
+        this.secondPaymentReferenceId = secondPaymentReferenceId;
+        this.properties = properties;
+    }
+
+    public PaymentTransactionJson(final PaymentTransaction transaction, final String paymentExternalKey, @Nullable final List<AuditLog> transactionLogs) {
+        this(transaction.getId().toString(),
+             transaction.getExternalKey(),
+             transaction.getPaymentId().toString(),
+             paymentExternalKey,
+             transaction.getTransactionType().toString(),
+             transaction.getAmount(),
+             transaction.getCurrency() != null ? transaction.getCurrency().toString() : null,
+             transaction.getEffectiveDate(),
+             transaction.getTransactionStatus() != null ? transaction.getTransactionStatus().toString() : null,
+             transaction.getGatewayErrorCode(),
+             transaction.getGatewayErrorMsg(),
+             transaction.getPaymentInfoPlugin() == null ? null : transaction.getPaymentInfoPlugin().getFirstPaymentReferenceId(),
+             transaction.getPaymentInfoPlugin() == null ? null : transaction.getPaymentInfoPlugin().getSecondPaymentReferenceId(),
+             transaction.getPaymentInfoPlugin() == null ? null : toPluginPropertyJson(transaction.getPaymentInfoPlugin().getProperties()),
+             toAuditLogJson(transactionLogs));
+    }
+
+    public String getTransactionId() {
+        return transactionId;
+    }
+
+    public String getPaymentId() {
+        return paymentId;
+    }
+
+    public String getTransactionExternalKey() {
+        return transactionExternalKey;
+    }
+
+    public String getTransactionType() {
+        return transactionType;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public String getGatewayErrorCode() {
+        return gatewayErrorCode;
+    }
+
+    public String getGatewayErrorMsg() {
+        return gatewayErrorMsg;
+    }
+
+    public String getFirstPaymentReferenceId() {
+        return firstPaymentReferenceId;
+    }
+
+    public String getSecondPaymentReferenceId() {
+        return secondPaymentReferenceId;
+    }
+
+    public List<PluginPropertyJson> getProperties() {
+        return properties;
+    }
+
+    public String getPaymentExternalKey() {
+        return paymentExternalKey;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("PaymentTransactionJson{");
+        sb.append("transactionId='").append(transactionId).append('\'');
+        sb.append(", paymentId='").append(paymentId).append('\'');
+        sb.append(", transactionExternalKey='").append(transactionExternalKey).append('\'');
+        sb.append(", transactionType='").append(transactionType).append('\'');
+        sb.append(", effectiveDate=").append(effectiveDate);
+        sb.append(", status='").append(status).append('\'');
+        sb.append(", amount=").append(amount);
+        sb.append(", currency='").append(currency).append('\'');
+        sb.append(", gatewayErrorCode='").append(gatewayErrorCode).append('\'');
+        sb.append(", gatewayErrorMsg='").append(gatewayErrorMsg).append('\'');
+        sb.append(", firstPaymentReferenceId='").append(firstPaymentReferenceId).append('\'');
+        sb.append(", secondPaymentReferenceId='").append(secondPaymentReferenceId).append('\'');
+        sb.append(", properties=").append(properties);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final PaymentTransactionJson that = (PaymentTransactionJson) o;
+
+        if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) {
+            return false;
+        }
+        if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
+            return false;
+        }
+        if (transactionExternalKey != null ? !transactionExternalKey.equals(that.transactionExternalKey) : that.transactionExternalKey != null) {
+            return false;
+        }
+        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+            return false;
+        }
+        if (transactionId != null ? !transactionId.equals(that.transactionId) : that.transactionId != null) {
+            return false;
+        }
+        if (effectiveDate != null ? effectiveDate.compareTo(that.effectiveDate) != 0 : that.effectiveDate != null) {
+            return false;
+        }
+        if (firstPaymentReferenceId != null ? !firstPaymentReferenceId.equals(that.firstPaymentReferenceId) : that.firstPaymentReferenceId != null) {
+            return false;
+        }
+        if (gatewayErrorCode != null ? !gatewayErrorCode.equals(that.gatewayErrorCode) : that.gatewayErrorCode != null) {
+            return false;
+        }
+        if (gatewayErrorMsg != null ? !gatewayErrorMsg.equals(that.gatewayErrorMsg) : that.gatewayErrorMsg != null) {
+            return false;
+        }
+        if (properties != null ? !properties.equals(that.properties) : that.properties != null) {
+            return false;
+        }
+        if (secondPaymentReferenceId != null ? !secondPaymentReferenceId.equals(that.secondPaymentReferenceId) : that.secondPaymentReferenceId != null) {
+            return false;
+        }
+        if (status != null ? !status.equals(that.status) : that.status != null) {
+            return false;
+        }
+        if (transactionType != null ? !transactionType.equals(that.transactionType) : that.transactionType != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = transactionId != null ? transactionId.hashCode() : 0;
+        result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
+        result = 31 * result + (transactionExternalKey != null ? transactionExternalKey.hashCode() : 0);
+        result = 31 * result + (transactionType != null ? transactionType.hashCode() : 0);
+        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+        result = 31 * result + (status != null ? status.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (gatewayErrorCode != null ? gatewayErrorCode.hashCode() : 0);
+        result = 31 * result + (gatewayErrorMsg != null ? gatewayErrorMsg.hashCode() : 0);
+        result = 31 * result + (firstPaymentReferenceId != null ? firstPaymentReferenceId.hashCode() : 0);
+        result = 31 * result + (secondPaymentReferenceId != null ? secondPaymentReferenceId.hashCode() : 0);
+        result = 31 * result + (properties != null ? properties.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PlanDetailJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PlanDetailJson.java
index 5f41dfb..dde8096 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PlanDetailJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PlanDetailJson.java
@@ -32,91 +32,88 @@ import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
 public class PlanDetailJson {
 
-    final String productName;
-    final String planName;
-    final BillingPeriod billingPeriod;
-    final String priceListName;
-    final List<PriceJson> finalPhasePrice;
+    final String product;
+    final String plan;
+    final BillingPeriod finalPhaseBillingPeriod;
+    final String priceList;
+    final List<PriceJson> finalPhaseRecurringPrice;
 
     @JsonCreator
-    public PlanDetailJson(@JsonProperty("product") final String productName,
-                          @JsonProperty("plan") final String planName,
-                          @JsonProperty("final_phase_billing_period") final BillingPeriod billingPeriod,
-                          @JsonProperty("priceList") final String priceListName,
-                          @JsonProperty("final_phase_recurring_price") final List<PriceJson> finalPhasePrice) {
-        this.productName = productName;
-        this.planName = planName;
-        this.billingPeriod = billingPeriod;
-        this.priceListName = priceListName;
-        this.finalPhasePrice = finalPhasePrice;
+    public PlanDetailJson(@JsonProperty("product") final String product,
+                          @JsonProperty("plan") final String plan,
+                          @JsonProperty("final_phase_billing_period") final BillingPeriod finalPhaseBillingPeriod,
+                          @JsonProperty("priceList") final String priceList,
+                          @JsonProperty("final_phase_recurring_price") final List<PriceJson> finalPhaseRecurringPrice) {
+        this.product = product;
+        this.plan = plan;
+        this.finalPhaseBillingPeriod = finalPhaseBillingPeriod;
+        this.priceList = priceList;
+        this.finalPhaseRecurringPrice = finalPhaseRecurringPrice;
     }
 
     public PlanDetailJson(final Listing listing) {
         final Plan plan = listing.getPlan();
         if (plan == null) {
-            this.productName = null;
-            this.planName = null;
-            this.billingPeriod = null;
-            this.finalPhasePrice = ImmutableList.<PriceJson>of();
+            this.product = null;
+            this.plan = null;
+            this.finalPhaseBillingPeriod = null;
+            this.finalPhaseRecurringPrice = ImmutableList.<PriceJson>of();
         } else {
-            this.productName = plan.getProduct() == null ? null : plan.getProduct().getName();
-            this.planName = plan.getName();
-            this.billingPeriod = plan.getRecurringBillingPeriod();
+            this.product = plan.getProduct() == null ? null : plan.getProduct().getName();
+            this.plan = plan.getName();
+            this.finalPhaseBillingPeriod = plan.getRecurringBillingPeriod();
             if (plan.getFinalPhase() == null ||
                 plan.getFinalPhase().getRecurring() == null ||
                 plan.getFinalPhase().getRecurring().getRecurringPrice() == null ||
                 plan.getFinalPhase().getRecurring().getRecurringPrice().getPrices() == null) {
-                this.finalPhasePrice = ImmutableList.<PriceJson>of();
+                this.finalPhaseRecurringPrice = ImmutableList.<PriceJson>of();
             } else {
-                this.finalPhasePrice = Lists.transform(ImmutableList.<Price>copyOf(plan.getFinalPhase().getRecurring().getRecurringPrice().getPrices()),
-                                                       new Function<Price, PriceJson>() {
-                                                           @Override
-                                                           public PriceJson apply(final Price price) {
-                                                               try {
-                                                                   return new PriceJson(price);
-                                                               } catch (CurrencyValueNull e) {
-                                                                   return new PriceJson(price.getCurrency().toString(), BigDecimal.ZERO);
-                                                               }
-                                                           }
-                                                       });
+                this.finalPhaseRecurringPrice = Lists.transform(ImmutableList.<Price>copyOf(plan.getFinalPhase().getRecurring().getRecurringPrice().getPrices()),
+                                                                new Function<Price, PriceJson>() {
+                                                                    @Override
+                                                                    public PriceJson apply(final Price price) {
+                                                                        try {
+                                                                            return new PriceJson(price);
+                                                                        } catch (final CurrencyValueNull e) {
+                                                                            return new PriceJson(price.getCurrency().toString(), BigDecimal.ZERO);
+                                                                        }
+                                                                    }
+                                                                });
             }
         }
-        this.priceListName = listing.getPriceList() == null ? null : listing.getPriceList().getName();
+        this.priceList = listing.getPriceList() == null ? null : listing.getPriceList().getName();
     }
 
-    public String getProductName() {
-        return productName;
+    public String getProduct() {
+        return product;
     }
 
-    public String getPlanName() {
-        return planName;
+    public String getPlan() {
+        return plan;
     }
 
-    public BillingPeriod getBillingPeriod() {
-        return billingPeriod;
+    public BillingPeriod getFinalPhaseBillingPeriod() {
+        return finalPhaseBillingPeriod;
     }
 
-    public String getPriceListName() {
-        return priceListName;
+    public String getPriceList() {
+        return priceList;
     }
 
-    public List<PriceJson> getFinalPhasePrice() {
-        return finalPhasePrice;
+    public List<PriceJson> getFinalPhaseRecurringPrice() {
+        return finalPhaseRecurringPrice;
     }
 
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("PlanDetailJson{");
-        sb.append("productName='").append(productName).append('\'');
-        sb.append(", planName='").append(planName).append('\'');
-        sb.append(", billingPeriod=").append(billingPeriod);
-        sb.append(", priceListName='").append(priceListName).append('\'');
-        sb.append(", finalPhasePrice=").append(finalPhasePrice);
+        sb.append("product='").append(product).append('\'');
+        sb.append(", plan='").append(plan).append('\'');
+        sb.append(", finalPhaseBillingPeriod=").append(finalPhaseBillingPeriod);
+        sb.append(", priceList='").append(priceList).append('\'');
+        sb.append(", finalPhaseRecurringPrice=").append(finalPhaseRecurringPrice);
         sb.append('}');
         return sb.toString();
     }
@@ -132,19 +129,19 @@ public class PlanDetailJson {
 
         final PlanDetailJson that = (PlanDetailJson) o;
 
-        if (billingPeriod != that.billingPeriod) {
+        if (finalPhaseBillingPeriod != that.finalPhaseBillingPeriod) {
             return false;
         }
-        if (finalPhasePrice != null ? !finalPhasePrice.equals(that.finalPhasePrice) : that.finalPhasePrice != null) {
+        if (finalPhaseRecurringPrice != null ? !finalPhaseRecurringPrice.equals(that.finalPhaseRecurringPrice) : that.finalPhaseRecurringPrice != null) {
             return false;
         }
-        if (planName != null ? !planName.equals(that.planName) : that.planName != null) {
+        if (plan != null ? !plan.equals(that.plan) : that.plan != null) {
             return false;
         }
-        if (priceListName != null ? !priceListName.equals(that.priceListName) : that.priceListName != null) {
+        if (priceList != null ? !priceList.equals(that.priceList) : that.priceList != null) {
             return false;
         }
-        if (productName != null ? !productName.equals(that.productName) : that.productName != null) {
+        if (product != null ? !product.equals(that.product) : that.product != null) {
             return false;
         }
 
@@ -153,11 +150,11 @@ public class PlanDetailJson {
 
     @Override
     public int hashCode() {
-        int result = productName != null ? productName.hashCode() : 0;
-        result = 31 * result + (planName != null ? planName.hashCode() : 0);
-        result = 31 * result + (billingPeriod != null ? billingPeriod.hashCode() : 0);
-        result = 31 * result + (priceListName != null ? priceListName.hashCode() : 0);
-        result = 31 * result + (finalPhasePrice != null ? finalPhasePrice.hashCode() : 0);
+        int result = product != null ? product.hashCode() : 0;
+        result = 31 * result + (plan != null ? plan.hashCode() : 0);
+        result = 31 * result + (finalPhaseBillingPeriod != null ? finalPhaseBillingPeriod.hashCode() : 0);
+        result = 31 * result + (priceList != null ? priceList.hashCode() : 0);
+        result = 31 * result + (finalPhaseRecurringPrice != null ? finalPhaseRecurringPrice.hashCode() : 0);
         return result;
     }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PluginPropertyJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PluginPropertyJson.java
new file mode 100644
index 0000000..40c02c5
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/PluginPropertyJson.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import org.killbill.billing.payment.api.PluginProperty;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PluginPropertyJson {
+
+    private final String key;
+    private final String value;
+    private final Boolean isUpdatable;
+
+    @JsonCreator
+    public PluginPropertyJson(@JsonProperty("key") final String key,
+                              @JsonProperty("value") final String value,
+                              @JsonProperty("isUpdatable") final Boolean isUpdatable) {
+        this.key = key;
+        this.value = value;
+        this.isUpdatable = isUpdatable;
+    }
+
+    public PluginPropertyJson(final PluginProperty pluginProperty) {
+        this(pluginProperty.getKey(), pluginProperty.getValue() == null ? null : pluginProperty.getValue().toString(), pluginProperty.getIsUpdatable());
+    }
+
+    public PluginProperty toPluginProperty() {
+        return new PluginProperty(key, value, isUpdatable);
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public Boolean getIsUpdatable() {
+        return isUpdatable;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("PluginPropertyJson{");
+        sb.append("key='").append(key).append('\'');
+        sb.append(", value='").append(value).append('\'');
+        sb.append(", isUpdatable=").append(isUpdatable);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final PluginPropertyJson that = (PluginPropertyJson) o;
+
+        if (isUpdatable != null ? !isUpdatable.equals(that.isUpdatable) : that.isUpdatable != null) {
+            return false;
+        }
+        if (key != null ? !key.equals(that.key) : that.key != null) {
+            return false;
+        }
+        if (value != null ? !value.equals(that.value) : that.value != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = key != null ? key.hashCode() : 0;
+        result = 31 * result + (value != null ? value.hashCode() : 0);
+        result = 31 * result + (isUpdatable != null ? isUpdatable.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ProfilingDataJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ProfilingDataJson.java
new file mode 100644
index 0000000..f3a9ce4
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/ProfilingDataJson.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Stack;
+
+import org.killbill.commons.profiling.ProfilingData;
+import org.killbill.commons.profiling.ProfilingData.LogLineType;
+import org.killbill.commons.profiling.ProfilingData.ProfilingDataItem;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+
+public class ProfilingDataJson {
+
+    private final List<ProfilingDataJsonItem> rawData;
+
+    @JsonCreator
+    public ProfilingDataJson(@JsonProperty("rawData") final List<ProfilingDataJsonItem> rawData) {
+        this.rawData = rawData;
+    }
+
+    public ProfilingDataJson(final ProfilingData input) {
+        final List<ProfilingDataItem> items = input.getRawData();
+        if (items.isEmpty()) {
+            this.rawData = Collections.emptyList();
+            return;
+        }
+
+        final List<ProfilingDataJsonItem> root = new ArrayList<ProfilingDataJsonItem>();
+
+        final Stack<ProfilingDataJsonItem> stack = new Stack<ProfilingDataJsonItem>();
+        while (items.size() > 0) {
+
+            final ProfilingDataItem cur = items.remove(0);
+
+            if (cur.getLineType() == LogLineType.START) {
+
+                // Create new element
+                final ProfilingDataJsonItem jsonItem = new ProfilingDataJsonItem(cur.getKey(), nanoToMicro(cur.getTimestampNsec()), Long.MIN_VALUE, new ArrayList<ProfilingDataJsonItem>());
+                // If stack is empty this belong to top level list, if to the parent's list
+                if (stack.isEmpty()) {
+                    root.add(jsonItem);
+                } else {
+                    final ProfilingDataJsonItem parent = stack.peek();
+                    parent.addChild(jsonItem);
+                }
+                // Add current element to the stack
+                stack.push(jsonItem);
+            } else /* LogLineType.STOP */ {
+                // Fetch current element and update its duration time
+                final ProfilingDataJsonItem jsonItem = stack.pop();
+                jsonItem.setDurationUsec(nanoToMicro(cur.getTimestampNsec()) - jsonItem.getStartUsec());
+            }
+        }
+        Preconditions.checkState(stack.isEmpty());
+        this.rawData = root;
+    }
+
+    public List<ProfilingDataJsonItem> getRawData() {
+        return rawData;
+    }
+
+    public class ProfilingDataJsonItem {
+
+        private final String name;
+        private final Long startUsec;
+        private final List<ProfilingDataJsonItem> calls;
+        // Not final so we can build the data structure in one pass
+        private Long durationUsec;
+
+        @JsonCreator
+        public ProfilingDataJsonItem(@JsonProperty("name") final String name,
+                                     @JsonProperty("startUsec") final Long startUsec,
+                                     @JsonProperty("durationUsec") final Long durationUsec,
+                                     @JsonProperty("calls") final List<ProfilingDataJsonItem> calls) {
+            this.name = name;
+            this.startUsec = startUsec;
+            this.durationUsec = durationUsec;
+            this.calls = calls;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        @JsonIgnore
+        public Long getStartUsec() {
+            return startUsec;
+        }
+
+        public Long getDurationUsec() {
+            return durationUsec;
+        }
+
+        public void addChild(final ProfilingDataJsonItem child) {
+            calls.add(child);
+        }
+
+        public List<ProfilingDataJsonItem> getCalls() {
+            return calls;
+        }
+
+        public void setDurationUsec(final Long durationUsec) {
+            this.durationUsec = durationUsec;
+        }
+    }
+
+    private static Long nanoToMicro(final Long nanoSec) {
+        return (nanoSec / 1000);
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UsageJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UsageJson.java
new file mode 100644
index 0000000..47661e6
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UsageJson.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.math.BigDecimal;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.usage.api.RolledUpUsage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UsageJson {
+
+    private final String subscriptionId;
+    private final String unitType;
+    private final DateTime startTime;
+    private final DateTime endTime;
+    private final BigDecimal amount;
+
+    @JsonCreator
+    public UsageJson(@JsonProperty("subscriptionId") final String subscriptionId,
+                     @JsonProperty("unitType") final String unitType,
+                     @JsonProperty("startTime") final DateTime startTime,
+                     @JsonProperty("endTime") final DateTime endTime,
+                     @JsonProperty("amount") final BigDecimal amount) {
+        this.subscriptionId = subscriptionId;
+        this.unitType = unitType;
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.amount = amount;
+    }
+
+    public UsageJson(final RolledUpUsage usage) {
+        this(usage.getSubscriptionId().toString(), usage.getUnitType(), usage.getStartTime(), usage.getEndTime(), usage.getAmount());
+    }
+
+    public String getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    public String getUnitType() {
+        return unitType;
+    }
+
+    public DateTime getStartTime() {
+        return startTime;
+    }
+
+    public DateTime getEndTime() {
+        return endTime;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof UsageJson)) {
+            return false;
+        }
+
+        final UsageJson usageJson = (UsageJson) o;
+
+        if (amount != null ? !amount.equals(usageJson.amount) : usageJson.amount != null) {
+            return false;
+        }
+        if (endTime != null ? !endTime.equals(usageJson.endTime) : usageJson.endTime != null) {
+            return false;
+        }
+        if (startTime != null ? !startTime.equals(usageJson.startTime) : usageJson.startTime != null) {
+            return false;
+        }
+        if (subscriptionId != null ? !subscriptionId.equals(usageJson.subscriptionId) : usageJson.subscriptionId != null) {
+            return false;
+        }
+        if (unitType != null ? !unitType.equals(usageJson.unitType) : usageJson.unitType != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = subscriptionId != null ? subscriptionId.hashCode() : 0;
+        result = 31 * result + (unitType != null ? unitType.hashCode() : 0);
+        result = 31 * result + (startTime != null ? startTime.hashCode() : 0);
+        result = 31 * result + (endTime != null ? endTime.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
index 15b367c..28fde6e 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/ExceptionMapperBase.java
@@ -178,7 +178,7 @@ public abstract class ExceptionMapperBase {
     private String exceptionToString(final Exception e) {
         try {
             return mapper.writeValueAsString(new BillingExceptionJson(e));
-        } catch (JsonProcessingException jsonException) {
+        } catch (final JsonProcessingException jsonException) {
             log.warn("Unable to serialize exception", jsonException);
         }
         return e.toString();
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
index e57921d..8e551d2 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
@@ -68,6 +68,8 @@ public class InvoiceApiExceptionMapper extends ExceptionMapperBase implements Ex
             return buildBadRequestResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
+        } else if (exception.getCode() == ErrorCode.EXTERNAL_CHARGE_CURRENCY_INVALID.getCode()) {
+            return buildBadRequestResponse(exception, uriInfo);
         } else {
             return fallback(exception, uriInfo);
         }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
index 3081d3f..9fb84ad 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/mappers/PaymentApiExceptionMapper.java
@@ -40,21 +40,9 @@ public class PaymentApiExceptionMapper extends ExceptionMapperBase implements Ex
     public Response toResponse(final PaymentApiException exception) {
         if (exception.getCode() == ErrorCode.PAYMENT_ADD_PAYMENT_METHOD.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_AMOUNT_DENIED.getCode()) {
-            return buildBadRequestResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_BAD_ACCOUNT.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_CREATE_PAYMENT.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_BAD.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_CREATE_PAYMENT_FOR_ATTEMPT_WITH_NON_POSITIVE_INV.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_CREATE_PAYMENT_PROVIDER_ACCOUNT.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_CREATE_REFUND.getCode()) {
+        } else if (exception.getCode() == ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_DEL_DEFAULT_PAYMENT_METHOD.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
@@ -62,41 +50,25 @@ public class PaymentApiExceptionMapper extends ExceptionMapperBase implements Ex
             return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_GET_PAYMENT_METHODS.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_GET_PAYMENT_PROVIDER.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_GET_PAYMENT_PROVIDER_ACCOUNT.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_INTERNAL_ERROR.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD.getCode()) {
             return buildNotFoundResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_NO_PAYMENT_METHODS.getCode()) {
-            return buildNotFoundResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_NO_SUCH_PAYMENT.getCode()) {
             return buildNotFoundResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD.getCode()) {
             return buildNotFoundResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_NO_SUCH_REFUND.getCode()) {
-            return buildNotFoundResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_NO_SUCH_SUCCESS_PAYMENT.getCode()) {
             return buildNotFoundResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_NULL_INVOICE.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_PLUGIN_ACCOUNT_INIT.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_PLUGIN_TIMEOUT.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_REFUND_AMOUNT_TOO_LARGE.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_UPD_GATEWAY_FAILED.getCode()) {
-            return buildInternalErrorResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.PAYMENT_UPD_PAYMENT_METHOD.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.PAYMENT_UPD_PAYMENT_PROVIDER_ACCOUNT.getCode()) {
+        } else if (exception.getCode() == ErrorCode.PAYMENT_INVALID_PARAMETER.getCode()) {
             return buildInternalErrorResponse(exception, uriInfo);
         } else {
             return fallback(exception, uriInfo);
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index 23d434d..56a49c8 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -49,11 +51,12 @@ import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.account.api.AccountEmail;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.account.api.MutableAccountData;
-import org.killbill.clock.Clock;
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.entitlement.api.SubscriptionApi;
 import org.killbill.billing.entitlement.api.SubscriptionApiException;
 import org.killbill.billing.entitlement.api.SubscriptionBundle;
 import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentApi;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
@@ -61,14 +64,14 @@ import org.killbill.billing.jaxrs.json.AccountEmailJson;
 import org.killbill.billing.jaxrs.json.AccountJson;
 import org.killbill.billing.jaxrs.json.AccountTimelineJson;
 import org.killbill.billing.jaxrs.json.BundleJson;
-import org.killbill.billing.jaxrs.json.ChargebackJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
+import org.killbill.billing.jaxrs.json.PaymentJson;
+import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
 import org.killbill.billing.jaxrs.json.InvoiceEmailJson;
 import org.killbill.billing.jaxrs.json.InvoiceJson;
+import org.killbill.billing.jaxrs.json.InvoicePaymentJson;
 import org.killbill.billing.jaxrs.json.OverdueStateJson;
-import org.killbill.billing.jaxrs.json.PaymentJson;
 import org.killbill.billing.jaxrs.json.PaymentMethodJson;
-import org.killbill.billing.jaxrs.json.RefundJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.overdue.OverdueApiException;
@@ -79,7 +82,8 @@ import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PaymentMethod;
-import org.killbill.billing.payment.api.Refund;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.api.AuditLevel;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldApiException;
@@ -92,14 +96,15 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.tag.ControlTagType;
+import org.killbill.clock.Clock;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
-import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Multimap;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
@@ -114,7 +119,6 @@ public class AccountResource extends JaxRsResourceBase {
     private final SubscriptionApi subscriptionApi;
     private final InvoiceUserApi invoiceApi;
     private final InvoicePaymentApi invoicePaymentApi;
-    private final PaymentApi paymentApi;
     private final OverdueUserApi overdueApi;
 
     @Inject
@@ -130,14 +134,14 @@ public class AccountResource extends JaxRsResourceBase {
                            final OverdueUserApi overdueApi,
                            final Clock clock,
                            final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountApi, paymentApi, clock, context);
         this.subscriptionApi = subscriptionApi;
         this.invoiceApi = invoiceApi;
         this.invoicePaymentApi = invoicePaymentApi;
-        this.paymentApi = paymentApi;
         this.overdueApi = overdueApi;
     }
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -153,6 +157,7 @@ public class AccountResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(accountJson).build();
     }
 
+    //@Timed
     @GET
     @Path("/" + PAGINATION)
     @Produces(APPLICATION_JSON)
@@ -175,9 +180,11 @@ public class AccountResource extends JaxRsResourceBase {
                                                         return getAccount(account, accountWithBalance, accountWithBalanceAndCBA, accountAuditLogs, tenantContext);
                                                     }
                                                 },
-                                                nextPageUri);
+                                                nextPageUri
+                                               );
     }
 
+    //@Timed
     @GET
     @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -202,9 +209,11 @@ public class AccountResource extends JaxRsResourceBase {
                                                         return getAccount(account, accountWithBalance, accountWithBalanceAndCBA, accountAuditLogs, tenantContext);
                                                     }
                                                 },
-                                                nextPageUri);
+                                                nextPageUri
+                                               );
     }
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + BUNDLES)
     @Produces(APPLICATION_JSON)
@@ -229,6 +238,7 @@ public class AccountResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(result).build();
     }
 
+    //@Timed
     @GET
     @Produces(APPLICATION_JSON)
     public Response getAccountByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
@@ -257,6 +267,7 @@ public class AccountResource extends JaxRsResourceBase {
         }
     }
 
+    //@Timed
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
@@ -271,6 +282,7 @@ public class AccountResource extends JaxRsResourceBase {
         return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", account.getId());
     }
 
+    //@Timed
     @PUT
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
@@ -288,6 +300,7 @@ public class AccountResource extends JaxRsResourceBase {
     }
 
     // Not supported
+    //@Timed
     @DELETE
     @Path("/{accountId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -305,6 +318,7 @@ public class AccountResource extends JaxRsResourceBase {
         return Response.status(Status.INTERNAL_SERVER_ERROR).build();
     }
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + TIMELINE)
     @Produces(APPLICATION_JSON)
@@ -320,21 +334,7 @@ public class AccountResource extends JaxRsResourceBase {
         final List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId(), tenantContext);
 
         // Get the payments
-        final List<Payment> payments = paymentApi.getAccountPayments(accountId, tenantContext);
-
-        // Get the refunds
-        final List<Refund> refunds = paymentApi.getAccountRefunds(account, tenantContext);
-        final Multimap<UUID, Refund> refundsByPayment = ArrayListMultimap.<UUID, Refund>create();
-        for (final Refund refund : refunds) {
-            refundsByPayment.put(refund.getPaymentId(), refund);
-        }
-
-        // Get the chargebacks
-        final List<InvoicePayment> chargebacks = invoicePaymentApi.getChargebacksByAccountId(accountId, tenantContext);
-        final Multimap<UUID, InvoicePayment> chargebacksByPayment = ArrayListMultimap.<UUID, InvoicePayment>create();
-        for (final InvoicePayment chargeback : chargebacks) {
-            chargebacksByPayment.put(chargeback.getPaymentId(), chargeback);
-        }
+        final List<Payment> payments = paymentApi.getAccountPayments(accountId, false, ImmutableList.<PluginProperty>of(), tenantContext);
 
         // Get the bundles
         final List<SubscriptionBundle> bundles = subscriptionApi.getSubscriptionBundlesForAccountId(account.getId(), tenantContext);
@@ -342,8 +342,8 @@ public class AccountResource extends JaxRsResourceBase {
         // Get all audit logs
         final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(accountId, auditMode.getLevel(), tenantContext);
 
-        final AccountTimelineJson json = new AccountTimelineJson(account, invoices, payments, bundles,
-                                                                 refundsByPayment, chargebacksByPayment,
+        final List<InvoicePayment> invoicePayments = invoicePaymentApi.getInvoicePaymentsByAccount(accountId, tenantContext);
+        final AccountTimelineJson json = new AccountTimelineJson(account, invoices, payments, invoicePayments, bundles,
                                                                  accountAuditLogs);
         return Response.status(Status.OK).entity(json).build();
     }
@@ -352,6 +352,7 @@ public class AccountResource extends JaxRsResourceBase {
     * ************************** EMAIL NOTIFICATIONS FOR INVOICES ********************************
     */
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + EMAIL_NOTIFICATIONS)
     @Produces(APPLICATION_JSON)
@@ -363,6 +364,7 @@ public class AccountResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(invoiceEmailJson).build();
     }
 
+    //@Timed
     @PUT
     @Path("/{accountId:" + UUID_PATTERN + "}/" + EMAIL_NOTIFICATIONS)
     @Consumes(APPLICATION_JSON)
@@ -388,6 +390,7 @@ public class AccountResource extends JaxRsResourceBase {
     /*
      * ************************** INVOICE CBA REBALANCING ********************************
      */
+    //@Timed
     @POST
     @Path("/{accountId:" + UUID_PATTERN + "}/" + CBA_REBALANCING)
     @Consumes(APPLICATION_JSON)
@@ -410,6 +413,7 @@ public class AccountResource extends JaxRsResourceBase {
      * ************************** INVOICES ********************************
      */
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + INVOICES)
     @Produces(APPLICATION_JSON)
@@ -442,43 +446,80 @@ public class AccountResource extends JaxRsResourceBase {
      * ************************** PAYMENTS ********************************
      */
 
+    // STEPH should refactor code since very similar to @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENTS)
+    //@Timed
     @GET
-    @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENTS)
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + INVOICE_PAYMENTS)
     @Produces(APPLICATION_JSON)
-    public Response getPayments(@PathParam("accountId") final String accountId,
-                                @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
-        final List<Payment> payments = paymentApi.getAccountPayments(UUID.fromString(accountId), context.createContext(request));
-        final List<PaymentJson> result = new ArrayList<PaymentJson>(payments.size());
+    public Response getInvoicePayments(@PathParam("accountId") final String accountIdStr,
+                                       @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                       @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                       @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final UUID accountId = UUID.fromString(accountIdStr);
+        final TenantContext tenantContext = context.createContext(request);
+        final Account account = accountUserApi.getAccountById(accountId, tenantContext);
+        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), withPluginInfo, pluginProperties, tenantContext);
+        final List<InvoicePayment> invoicePayments = invoicePaymentApi.getInvoicePaymentsByAccount(accountId, tenantContext);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(accountId, auditMode.getLevel(), tenantContext);
+        final List<InvoicePaymentJson> result = new ArrayList<InvoicePaymentJson>(payments.size());
         for (final Payment payment : payments) {
-            result.add(new PaymentJson(payment, null));
+            final UUID invoiceId = getInvoiceId(invoicePayments, payment);
+            result.add(new InvoicePaymentJson(payment, invoiceId, accountAuditLogs));
         }
         return Response.status(Status.OK).entity(result).build();
     }
 
+    //@Timed
     @POST
     @Produces(APPLICATION_JSON)
     @Consumes(APPLICATION_JSON)
-    @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENTS)
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + INVOICE_PAYMENTS)
     public Response payAllInvoices(@PathParam("accountId") final String accountId,
                                    @QueryParam(QUERY_PAYMENT_EXTERNAL) @DefaultValue("false") final Boolean externalPayment,
+                                   @QueryParam(QUERY_PAYMENT_AMOUNT) final BigDecimal paymentAmount,
+                                   @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                    @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                    @HeaderParam(HDR_REASON) final String reason,
                                    @HeaderParam(HDR_COMMENT) final String comment,
-                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
+                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException, InvoiceApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
         final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), callContext);
         final Collection<Invoice> unpaidInvoices = invoiceApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext);
+
+        BigDecimal remainingRequestPayment = paymentAmount;
+        if (remainingRequestPayment == null) {
+            remainingRequestPayment = BigDecimal.ZERO;
+            for (final Invoice invoice : unpaidInvoices) {
+                remainingRequestPayment = remainingRequestPayment.add(invoice.getBalance());
+            }
+        }
+
         for (final Invoice invoice : unpaidInvoices) {
-            if (externalPayment) {
-                paymentApi.createExternalPayment(account, invoice.getId(), invoice.getBalance(), callContext);
-            } else {
-                paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+            final BigDecimal amountToPay = (remainingRequestPayment.compareTo(invoice.getBalance()) >= 0) ?
+                                           invoice.getBalance() : remainingRequestPayment;
+            if (amountToPay.compareTo(BigDecimal.ZERO) > 0) {
+                createPurchaseForInvoice(account, invoice.getId(), amountToPay, externalPayment, callContext);
+            }
+            remainingRequestPayment = remainingRequestPayment.subtract(amountToPay);
+            if (remainingRequestPayment.compareTo(BigDecimal.ZERO) == 0) {
+                break;
             }
         }
+        //
+        // If the amount requested is greater than what had to be paid and if this an for an external payment (check, ..)
+        // then we apply some credit on the account.
+        //
+        if (externalPayment && remainingRequestPayment.compareTo(BigDecimal.ZERO) > 0) {
+            invoiceApi.insertCredit(account.getId(), remainingRequestPayment, clock.getUTCToday(), account.getCurrency(), callContext);
+        }
         return Response.status(Status.OK).build();
     }
 
+    //@Timed
     @POST
     @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENT_METHODS)
     @Consumes(APPLICATION_JSON)
@@ -487,11 +528,13 @@ public class AccountResource extends JaxRsResourceBase {
                                         @PathParam("accountId") final String accountId,
                                         @QueryParam(QUERY_PAYMENT_METHOD_IS_DEFAULT) @DefaultValue("false") final Boolean isDefault,
                                         @QueryParam(QUERY_PAY_ALL_UNPAID_INVOICES) @DefaultValue("false") final Boolean payAllUnpaidInvoices,
+                                        @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                         @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                         @HeaderParam(HDR_REASON) final String reason,
                                         @HeaderParam(HDR_COMMENT) final String comment,
                                         @javax.ws.rs.core.Context final UriInfo uriInfo,
                                         @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
         final PaymentMethod data = json.toPaymentMethod(accountId);
@@ -504,26 +547,29 @@ public class AccountResource extends JaxRsResourceBase {
             return Response.status(Status.BAD_REQUEST).build();
         }
 
-        final UUID paymentMethodId = paymentApi.addPaymentMethod(data.getPluginName(), account, isDefault, data.getPluginDetail(), callContext);
+        final UUID paymentMethodId = paymentApi.addPaymentMethod(account, data.getExternalKey(), data.getPluginName(), isDefault, data.getPluginDetail(), pluginProperties, callContext);
         if (payAllUnpaidInvoices && unpaidInvoices.size() > 0) {
             for (final Invoice invoice : unpaidInvoices) {
-                paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+                createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), false, callContext);
             }
         }
         return uriBuilder.buildResponse(PaymentMethodResource.class, "getPaymentMethod", paymentMethodId, uriInfo.getBaseUri().toString());
     }
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENT_METHODS)
     @Produces(APPLICATION_JSON)
     public Response getPaymentMethods(@PathParam("accountId") final String accountId,
-                                      @QueryParam(QUERY_PAYMENT_METHOD_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                      @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                      @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                       @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final TenantContext tenantContext = context.createContext(request);
 
         final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), tenantContext);
-        final List<PaymentMethod> methods = paymentApi.getPaymentMethods(account, withPluginInfo, tenantContext);
+        final List<PaymentMethod> methods = paymentApi.getAccountPaymentMethods(account.getId(), withPluginInfo, pluginProperties, tenantContext);
         final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(account.getId(), auditMode.getLevel(), tenantContext);
         final List<PaymentMethodJson> json = new ArrayList<PaymentMethodJson>(Collections2.transform(methods, new Function<PaymentMethod, PaymentMethodJson>() {
             @Override
@@ -535,6 +581,7 @@ public class AccountResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(json).build();
     }
 
+    //@Timed
     @PUT
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
@@ -542,67 +589,102 @@ public class AccountResource extends JaxRsResourceBase {
     public Response setDefaultPaymentMethod(@PathParam("accountId") final String accountId,
                                             @PathParam("paymentMethodId") final String paymentMethodId,
                                             @QueryParam(QUERY_PAY_ALL_UNPAID_INVOICES) @DefaultValue("false") final Boolean payAllUnpaidInvoices,
+                                            @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                             @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                             @HeaderParam(HDR_REASON) final String reason,
                                             @HeaderParam(HDR_COMMENT) final String comment,
                                             @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
         final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), callContext);
-        paymentApi.setDefaultPaymentMethod(account, UUID.fromString(paymentMethodId), callContext);
+        paymentApi.setDefaultPaymentMethod(account, UUID.fromString(paymentMethodId), pluginProperties, callContext);
 
         if (payAllUnpaidInvoices) {
             final Collection<Invoice> unpaidInvoices = invoiceApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext);
             for (final Invoice invoice : unpaidInvoices) {
-                paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+                createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), false, callContext);
             }
         }
         return Response.status(Status.OK).build();
     }
 
     /*
-     * ************************** CHARGEBACKS ********************************
+     * ************************* PAYMENTS *****************************
      */
+    //@Timed
     @GET
-    @Path("/{accountId:" + UUID_PATTERN + "}/" + CHARGEBACKS)
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENTS)
     @Produces(APPLICATION_JSON)
-    public Response getChargebacksForAccount(@PathParam("accountId") final String accountIdStr,
-                                             @javax.ws.rs.core.Context final HttpServletRequest request) {
+    public Response getPayments(@PathParam("accountId") final String accountIdStr,
+                                @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
         final UUID accountId = UUID.fromString(accountIdStr);
-        final List<InvoicePayment> chargebacks = invoicePaymentApi.getChargebacksByAccountId(accountId, context.createContext(request));
-        final List<ChargebackJson> chargebacksJson = new ArrayList<ChargebackJson>();
-        for (final InvoicePayment chargeback : chargebacks) {
-            chargebacksJson.add(new ChargebackJson(accountId, chargeback));
-        }
-        return Response.status(Response.Status.OK).entity(chargebacksJson).build();
-    }
-
-    /*
-     * ************************** REFUNDS ********************************
-     */
-    @GET
-    @Path("/{accountId:" + UUID_PATTERN + "}/" + REFUNDS)
-    @Produces(APPLICATION_JSON)
-    public Response getRefunds(@PathParam("accountId") final String accountId,
-                               @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final TenantContext tenantContext = context.createContext(request);
-
-        final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), tenantContext);
-        final List<Refund> refunds = paymentApi.getAccountRefunds(account, tenantContext);
-        final List<RefundJson> result = new ArrayList<RefundJson>(Collections2.transform(refunds, new Function<Refund, RefundJson>() {
+        final List<Payment> payments = paymentApi.getAccountPayments(accountId, withPluginInfo, pluginProperties, tenantContext);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(accountId, auditMode.getLevel(), tenantContext);
+        final List<PaymentJson> result = ImmutableList.copyOf(Iterables.transform(payments, new Function<Payment, PaymentJson>() {
             @Override
-            public RefundJson apply(Refund input) {
-                // TODO Return adjusted items and audits
-                return new RefundJson(input, null, null);
+            public PaymentJson apply(final Payment payment) {
+                return new PaymentJson(payment, accountAuditLogs);
             }
         }));
+        return Response.status(Response.Status.OK).entity(result).build();
+    }
 
-        return Response.status(Status.OK).entity(result).build();
+    //@Timed
+    @POST
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENTS)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response processPayment(final PaymentTransactionJson json,
+                                         @PathParam("accountId") final String accountIdStr,
+                                         @QueryParam("paymentMethodId") final String paymentMethodIdStr,
+                                         @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                         @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                         @HeaderParam(HDR_REASON) final String reason,
+                                         @HeaderParam(HDR_COMMENT) final String comment,
+                                         @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                         @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID accountId = UUID.fromString(accountIdStr);
+        final Account account = accountUserApi.getAccountById(accountId, callContext);
+        final UUID paymentMethodId = paymentMethodIdStr == null ? account.getPaymentMethodId() : UUID.fromString(paymentMethodIdStr);
+        final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency());
+        final UUID paymentId = json.getPaymentId() == null ? null : UUID.fromString(json.getPaymentId());
+
+        final TransactionType transactionType = TransactionType.valueOf(json.getTransactionType());
+        final Payment result;
+        switch (transactionType) {
+            case AUTHORIZE:
+                result = paymentApi.createAuthorization(account, paymentMethodId, paymentId, json.getAmount(), currency,
+                                                        json.getPaymentExternalKey(), json.getTransactionExternalKey(),
+                                                        pluginProperties, callContext);
+                break;
+            case PURCHASE:
+                result = paymentApi.createPurchase(account, paymentMethodId, paymentId, json.getAmount(), currency,
+                                                   json.getPaymentExternalKey(), json.getTransactionExternalKey(),
+                                                   pluginProperties, callContext);
+                break;
+            case CREDIT:
+                result = paymentApi.createCredit(account, paymentMethodId, paymentId, json.getAmount(), currency,
+                                                 json.getPaymentExternalKey(), json.getTransactionExternalKey(),
+                                                 pluginProperties, callContext);
+                break;
+            default:
+                return Response.status(Status.PRECONDITION_FAILED).entity("TransactionType " + transactionType + " is not allowed for an account").build();
+        }
+        return uriBuilder.buildResponse(PaymentResource.class, "getPayment", result.getId(), uriInfo.getBaseUri().toString());
     }
 
     /*
      * ************************** OVERDUE ********************************
      */
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + OVERDUE)
     @Produces(APPLICATION_JSON)
@@ -620,6 +702,7 @@ public class AccountResource extends JaxRsResourceBase {
      * *************************      CUSTOM FIELDS     *****************************
      */
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Produces(APPLICATION_JSON)
@@ -629,6 +712,7 @@ public class AccountResource extends JaxRsResourceBase {
         return super.getCustomFields(UUID.fromString(id), auditMode, context.createContext(request));
     }
 
+    //@Timed
     @POST
     @Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Consumes(APPLICATION_JSON)
@@ -644,6 +728,7 @@ public class AccountResource extends JaxRsResourceBase {
                                         context.createContext(createdBy, reason, comment, request), uriInfo);
     }
 
+    //@Timed
     @DELETE
     @Path("/{accountId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Consumes(APPLICATION_JSON)
@@ -662,6 +747,7 @@ public class AccountResource extends JaxRsResourceBase {
      * *************************     TAGS     *****************************
      */
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + TAGS)
     @Produces(APPLICATION_JSON)
@@ -673,6 +759,7 @@ public class AccountResource extends JaxRsResourceBase {
         return super.getTags(accountId, accountId, auditMode, includedDeleted, context.createContext(request));
     }
 
+    //@Timed
     @POST
     @Path("/{accountId:" + UUID_PATTERN + "}/" + TAGS)
     @Produces(APPLICATION_JSON)
@@ -687,6 +774,7 @@ public class AccountResource extends JaxRsResourceBase {
                                 context.createContext(createdBy, reason, comment, request));
     }
 
+    //@Timed
     @DELETE
     @Path("/{accountId:" + UUID_PATTERN + "}/" + TAGS)
     @Consumes(APPLICATION_JSON)
@@ -724,6 +812,7 @@ public class AccountResource extends JaxRsResourceBase {
      * *************************     EMAILS     *****************************
      */
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}/" + EMAILS)
     @Produces(APPLICATION_JSON)
@@ -739,6 +828,7 @@ public class AccountResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(emailsJson).build();
     }
 
+    //@Timed
     @POST
     @Path("/{accountId:" + UUID_PATTERN + "}/" + EMAILS)
     @Consumes(APPLICATION_JSON)
@@ -764,7 +854,8 @@ public class AccountResource extends JaxRsResourceBase {
                                                                                public boolean apply(final AccountEmail input) {
                                                                                    return input.getEmail().equals(json.getEmail());
                                                                                }
-                                                                           })
+                                                                           }
+                                                                          )
                                                     .orNull();
         if (existingEmail == null) {
             accountUserApi.addEmail(accountId, json.toAccountEmail(UUID.randomUUID()), callContext);
@@ -773,6 +864,7 @@ public class AccountResource extends JaxRsResourceBase {
         return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getEmails", json.getAccountId());
     }
 
+    //@Timed
     @DELETE
     @Path("/{accountId:" + UUID_PATTERN + "}/" + EMAILS + "/{email}")
     @Produces(APPLICATION_JSON)
@@ -785,7 +877,7 @@ public class AccountResource extends JaxRsResourceBase {
         final UUID accountId = UUID.fromString(id);
 
         final List<AccountEmail> emails = accountUserApi.getEmails(accountId, context.createContext(request));
-        for (AccountEmail cur : emails) {
+        for (final AccountEmail cur : emails) {
             if (cur.getEmail().equals(email)) {
                 final AccountEmailJson accountEmailJson = new AccountEmailJson(accountId.toString(), email);
                 final AccountEmail accountEmail = accountEmailJson.toAccountEmail(cur.getId());
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
index f3f72a3..2ca083c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/BundleResource.java
@@ -45,6 +45,7 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.clock.Clock;
 import org.killbill.billing.entitlement.api.EntitlementApi;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
@@ -66,6 +67,7 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.Pagination;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Inject;
@@ -88,13 +90,15 @@ public class BundleResource extends JaxRsResourceBase {
                           final AccountUserApi accountUserApi,
                           final SubscriptionApi subscriptionApi,
                           final EntitlementApi entitlementApi,
+                          final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.entitlementApi = entitlementApi;
         this.subscriptionApi = subscriptionApi;
     }
 
+    //@Timed
     @GET
     @Path("/{bundleId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -106,6 +110,7 @@ public class BundleResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(json).build();
     }
 
+    //@Timed
     @GET
     @Produces(APPLICATION_JSON)
     public Response getBundleByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
@@ -115,6 +120,7 @@ public class BundleResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(json).build();
     }
 
+    //@Timed
     @GET
     @Path("/" + PAGINATION)
     @Produces(APPLICATION_JSON)
@@ -140,6 +146,7 @@ public class BundleResource extends JaxRsResourceBase {
                                                 nextPageUri);
     }
 
+    //@Timed
     @GET
     @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -167,6 +174,7 @@ public class BundleResource extends JaxRsResourceBase {
                                                 nextPageUri);
     }
 
+    //@Timed
     @PUT
     @Path("/{bundleId:" + UUID_PATTERN + "}/" + PAUSE)
     @Consumes(APPLICATION_JSON)
@@ -186,6 +194,7 @@ public class BundleResource extends JaxRsResourceBase {
         return Response.status(Status.OK).build();
     }
 
+    //@Timed
     @PUT
     @Path("/{bundleId:" + UUID_PATTERN + "}/" + RESUME)
     @Consumes(APPLICATION_JSON)
@@ -205,6 +214,7 @@ public class BundleResource extends JaxRsResourceBase {
         return Response.status(Status.OK).build();
     }
 
+    //@Timed
     @GET
     @Path("/{bundleId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Produces(APPLICATION_JSON)
@@ -214,6 +224,7 @@ public class BundleResource extends JaxRsResourceBase {
         return super.getCustomFields(UUID.fromString(id), auditMode, context.createContext(request));
     }
 
+    //@Timed
     @POST
     @Path("/{bundleId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Consumes(APPLICATION_JSON)
@@ -229,6 +240,7 @@ public class BundleResource extends JaxRsResourceBase {
                                         context.createContext(createdBy, reason, comment, request), uriInfo);
     }
 
+    //@Timed
     @DELETE
     @Path("/{bundleId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Consumes(APPLICATION_JSON)
@@ -243,6 +255,7 @@ public class BundleResource extends JaxRsResourceBase {
                                         context.createContext(createdBy, reason, comment, request));
     }
 
+    //@Timed
     @GET
     @Path("/{bundleId:" + UUID_PATTERN + "}/" + TAGS)
     @Produces(APPLICATION_JSON)
@@ -256,6 +269,7 @@ public class BundleResource extends JaxRsResourceBase {
         return super.getTags(bundle.getAccountId(), bundleId, auditMode, includedDeleted, tenantContext);
     }
 
+    //@Timed
     @PUT
     @Path("/{bundleId:" + UUID_PATTERN + "}")
     @Consumes(APPLICATION_JSON)
@@ -282,6 +296,7 @@ public class BundleResource extends JaxRsResourceBase {
         return uriBuilder.buildResponse(BundleResource.class, "getBundle", newBundleId, uriInfo.getBaseUri().toString());
     }
 
+    //@Timed
     @POST
     @Path("/{bundleId:" + UUID_PATTERN + "}/" + TAGS)
     @Consumes(APPLICATION_JSON)
@@ -297,6 +312,7 @@ public class BundleResource extends JaxRsResourceBase {
                                 context.createContext(createdBy, reason, comment, request));
     }
 
+    //@Timed
     @DELETE
     @Path("/{bundleId:" + UUID_PATTERN + "}/" + TAGS)
     @Consumes(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
index 21ca4ec..ad882fc 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CatalogResource.java
@@ -32,6 +32,7 @@ import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.catalog.api.Listing;
 import org.killbill.billing.catalog.api.StaticCatalog;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.clock.Clock;
 import org.killbill.billing.jaxrs.json.CatalogJsonSimple;
 import org.killbill.billing.jaxrs.json.PlanDetailJson;
@@ -40,8 +41,9 @@ import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldUserApi;
 import org.killbill.billing.util.api.TagUserApi;
-import org.killbill.billing.util.config.catalog.XMLWriter;
+import org.killbill.xmlloader.XMLWriter;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
@@ -61,18 +63,21 @@ public class CatalogResource extends JaxRsResourceBase {
                            final CustomFieldUserApi customFieldUserApi,
                            final AuditUserApi auditUserApi,
                            final AccountUserApi accountUserApi,
+                           final PaymentApi paymentApi,
                            final Clock clock,
                            final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.catalogService = catalogService;
     }
 
+    //@Timed
     @GET
     @Produces(APPLICATION_XML)
     public Response getCatalogXml(@javax.ws.rs.core.Context final HttpServletRequest request) throws Exception {
         return Response.status(Status.OK).entity(XMLWriter.writeXML(catalogService.getCurrentCatalog(), StaticCatalog.class)).build();
     }
 
+    //@Timed
     @GET
     @Produces(APPLICATION_JSON)
     public Response getCatalogJson(@javax.ws.rs.core.Context final HttpServletRequest request) throws Exception {
@@ -95,6 +100,7 @@ public class CatalogResource extends JaxRsResourceBase {
     //        return result;
     //    }
 
+    //@Timed
     @GET
     @Path("/availableAddons")
     @Produces(APPLICATION_JSON)
@@ -109,6 +115,7 @@ public class CatalogResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(details).build();
     }
 
+    //@Timed
     @GET
     @Path("/availableBasePlans")
     @Produces(APPLICATION_JSON)
@@ -122,6 +129,7 @@ public class CatalogResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(details).build();
     }
 
+    //@Timed
     @GET
     @Path("/simpleCatalog")
     @Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java
index d570776..3af4ca5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CreditResource.java
@@ -35,6 +35,7 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.clock.Clock;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
@@ -68,9 +69,10 @@ public class CreditResource extends JaxRsResourceBase {
                           final TagUserApi tagUserApi,
                           final CustomFieldUserApi customFieldUserApi,
                           final AuditUserApi auditUserApi,
+                          final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.invoiceUserApi = invoiceUserApi;
         this.accountUserApi = accountUserApi;
     }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java
index 683240a..a4895eb 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/CustomFieldResource.java
@@ -30,6 +30,7 @@ import javax.ws.rs.core.Response;
 
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.clock.Clock;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.util.Context;
@@ -43,6 +44,7 @@ import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.customfield.CustomField;
 import org.killbill.billing.util.entity.Pagination;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Inject;
@@ -60,11 +62,13 @@ public class CustomFieldResource extends JaxRsResourceBase {
                                final CustomFieldUserApi customFieldUserApi,
                                final AuditUserApi auditUserApi,
                                final AccountUserApi accountUserApi,
+                               final PaymentApi paymentApi,
                                final Clock clock,
                                final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
     }
 
+    //@Timed
     @GET
     @Path("/" + PAGINATION)
     @Produces(APPLICATION_JSON)
@@ -88,6 +92,7 @@ public class CustomFieldResource extends JaxRsResourceBase {
                                                 nextPageUri);
     }
 
+    //@Timed
     @GET
     @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
     @Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java
index ffe7334..cf4aa7b 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ExportResource.java
@@ -31,6 +31,7 @@ import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.StreamingOutput;
 
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.clock.Clock;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
@@ -40,6 +41,7 @@ import org.killbill.billing.util.api.ExportUserApi;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.callcontext.CallContext;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.inject.Singleton;
 
 import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
@@ -57,12 +59,14 @@ public class ExportResource extends JaxRsResourceBase {
                           final CustomFieldUserApi customFieldUserApi,
                           final AuditUserApi auditUserApi,
                           final AccountUserApi accountUserApi,
+                          final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.exportUserApi = exportUserApi;
     }
 
+    //@Timed
     @GET
     @Path("/{accountId:" + UUID_PATTERN + "}")
     @Produces(TEXT_PLAIN)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
new file mode 100644
index 0000000..2ddeed1
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.resources;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.invoice.api.InvoicePaymentApi;
+import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.killbill.billing.jaxrs.json.CustomFieldJson;
+import org.killbill.billing.jaxrs.json.InvoiceItemJson;
+import org.killbill.billing.jaxrs.json.InvoicePaymentJson;
+import org.killbill.billing.jaxrs.json.InvoicePaymentTransactionJson;
+import org.killbill.billing.jaxrs.util.Context;
+import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.util.api.AuditUserApi;
+import org.killbill.billing.util.api.CustomFieldApiException;
+import org.killbill.billing.util.api.CustomFieldUserApi;
+import org.killbill.billing.util.api.TagApiException;
+import org.killbill.billing.util.api.TagDefinitionApiException;
+import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.audit.AccountAuditLogs;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.clock.Clock;
+
+import com.codahale.metrics.annotation.Timed;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Path(JaxrsResource.INVOICE_PAYMENTS_PATH)
+public class InvoicePaymentResource extends JaxRsResourceBase {
+
+    private static final String ID_PARAM_NAME = "paymentId";
+
+    private final InvoicePaymentApi invoicePaymentApi;
+
+    @Inject
+    public InvoicePaymentResource(final AccountUserApi accountUserApi,
+                                  final PaymentApi paymentApi,
+                                  final JaxrsUriBuilder uriBuilder,
+                                  final TagUserApi tagUserApi,
+                                  final CustomFieldUserApi customFieldUserApi,
+                                  final AuditUserApi auditUserApi,
+                                  final InvoicePaymentApi invoicePaymentApi,
+                                  final Clock clock,
+                                  final Context context) {
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        this.invoicePaymentApi = invoicePaymentApi;
+    }
+
+    //@Timed
+    @GET
+    @Path("/{paymentId:" + UUID_PATTERN + "}/")
+    @Produces(APPLICATION_JSON)
+    public Response getInvoicePayment(@PathParam("paymentId") final String paymentIdStr,
+                                      @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                      @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                      @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                      @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final UUID paymentIdId = UUID.fromString(paymentIdStr);
+        final TenantContext tenantContext = context.createContext(request);
+        final Payment payment = paymentApi.getPayment(paymentIdId, withPluginInfo, pluginProperties, tenantContext);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(payment.getAccountId(), auditMode.getLevel(), tenantContext);
+
+        final List<InvoicePayment> invoicePayments = invoicePaymentApi.getInvoicePayments(paymentIdId, tenantContext);
+        final InvoicePayment invoicePayment = Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
+            @Override
+            public boolean apply(final InvoicePayment input) {
+                return input.getType() == InvoicePaymentType.ATTEMPT;
+            }
+        }).orNull();
+        final UUID invoiceId = invoicePayment != null ? invoicePayment.getInvoiceId() : null;
+
+        final InvoicePaymentJson result = new InvoicePaymentJson(payment, invoiceId, accountAuditLogs);
+        return Response.status(Response.Status.OK).entity(result).build();
+    }
+
+    //@Timed
+    @POST
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createRefundWithAdjustments(final InvoicePaymentTransactionJson json,
+                                                @PathParam("paymentId") final String paymentId,
+                                                @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                                @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                                @HeaderParam(HDR_REASON) final String reason,
+                                                @HeaderParam(HDR_COMMENT) final String comment,
+                                                @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                                @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID paymentUuid = UUID.fromString(paymentId);
+        final Payment payment = paymentApi.getPayment(paymentUuid, false, ImmutableList.<PluginProperty>of(), callContext);
+        final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
+
+        final Iterable<PluginProperty> pluginProperties;
+        final String transactionExternalKey = json.getTransactionExternalKey() != null ? json.getTransactionExternalKey() : UUID.randomUUID().toString();
+        if (json.isAdjusted() != null && json.isAdjusted()) {
+            if (json.getAdjustments() != null && json.getAdjustments().size() > 0) {
+                final Map<UUID, BigDecimal> adjustments = new HashMap<UUID, BigDecimal>();
+                for (final InvoiceItemJson item : json.getAdjustments()) {
+                    adjustments.put(UUID.fromString(item.getInvoiceItemId()), item.getAmount());
+                }
+                pluginProperties = extractPluginProperties(pluginPropertiesString,
+                                                           new PluginProperty("IPCD_REFUND_WITH_ADJUSTMENTS", "true", false),
+                                                           new PluginProperty("IPCD_REFUND_IDS_AMOUNTS", adjustments, false));
+            } else {
+                pluginProperties = extractPluginProperties(pluginPropertiesString,
+                                                           new PluginProperty("IPCD_REFUND_WITH_ADJUSTMENTS", "true", false));
+            }
+        } else {
+            pluginProperties = extractPluginProperties(pluginPropertiesString);
+        }
+
+        final Payment result = paymentApi.createRefundWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(), transactionExternalKey,
+                                                                               pluginProperties, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
+        return uriBuilder.buildResponse(InvoicePaymentResource.class, "getInvoicePayment", result.getId(), uriInfo.getBaseUri().toString());
+    }
+
+    //@Timed
+    @POST
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + CHARGEBACKS)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createChargeback(final InvoicePaymentTransactionJson json,
+                                     @PathParam("paymentId") final String paymentId,
+                                     @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                     @HeaderParam(HDR_REASON) final String reason,
+                                     @HeaderParam(HDR_COMMENT) final String comment,
+                                     @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                     @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID paymentUuid = UUID.fromString(paymentId);
+        final Payment payment = paymentApi.getPayment(paymentUuid, false, ImmutableList.<PluginProperty>of(), callContext);
+        final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
+        final String transactionExternalKey = json.getTransactionExternalKey() != null ? json.getTransactionExternalKey() : UUID.randomUUID().toString();
+
+        final Payment result = paymentApi.createChargebackWithPaymentControl(account, payment.getId(), json.getAmount(), account.getCurrency(),
+                                                                                   transactionExternalKey, createInvoicePaymentControlPluginApiPaymentOptions(false), callContext);
+        return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId());
+    }
+
+    //@Timed
+    @GET
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+    @Produces(APPLICATION_JSON)
+    public Response getCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+                                    @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                    @javax.ws.rs.core.Context final HttpServletRequest request) {
+        return super.getCustomFields(UUID.fromString(id), auditMode, context.createContext(request));
+    }
+
+    //@Timed
+    @POST
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+                                       final List<CustomFieldJson> customFields,
+                                       @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                       @HeaderParam(HDR_REASON) final String reason,
+                                       @HeaderParam(HDR_COMMENT) final String comment,
+                                       @javax.ws.rs.core.Context final HttpServletRequest request,
+                                       @javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
+        return super.createCustomFields(UUID.fromString(id), customFields,
+                                        context.createContext(createdBy, reason, comment, request), uriInfo);
+    }
+
+    //@Timed
+    @DELETE
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response deleteCustomFields(@PathParam(ID_PARAM_NAME) final String id,
+                                       @QueryParam(QUERY_CUSTOM_FIELDS) final String customFieldList,
+                                       @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                       @HeaderParam(HDR_REASON) final String reason,
+                                       @HeaderParam(HDR_COMMENT) final String comment,
+                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws CustomFieldApiException {
+        return super.deleteCustomFields(UUID.fromString(id), customFieldList,
+                                        context.createContext(createdBy, reason, comment, request));
+    }
+
+    //@Timed
+    @GET
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + TAGS)
+    @Produces(APPLICATION_JSON)
+    public Response getTags(@PathParam(ID_PARAM_NAME) final String paymentIdString,
+                            @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                            @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                            @QueryParam(QUERY_TAGS_INCLUDED_DELETED) @DefaultValue("false") final Boolean includedDeleted,
+                            @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException, PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final UUID paymentId = UUID.fromString(paymentIdString);
+        final TenantContext tenantContext = context.createContext(request);
+        final Payment payment = paymentApi.getPayment(paymentId, false, pluginProperties, tenantContext);
+        return super.getTags(payment.getAccountId(), paymentId, auditMode, includedDeleted, tenantContext);
+    }
+
+    //@Timed
+    @POST
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + TAGS)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createTags(@PathParam(ID_PARAM_NAME) final String id,
+                               @QueryParam(QUERY_TAGS) final String tagList,
+                               @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                               @HeaderParam(HDR_REASON) final String reason,
+                               @HeaderParam(HDR_COMMENT) final String comment,
+                               @javax.ws.rs.core.Context final UriInfo uriInfo,
+                               @javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
+        return super.createTags(UUID.fromString(id), tagList, uriInfo,
+                                context.createContext(createdBy, reason, comment, request));
+    }
+
+    //@Timed
+    @DELETE
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + TAGS)
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response deleteTags(@PathParam(ID_PARAM_NAME) final String id,
+                               @QueryParam(QUERY_TAGS) final String tagList,
+                               @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                               @HeaderParam(HDR_REASON) final String reason,
+                               @HeaderParam(HDR_COMMENT) final String comment,
+                               @javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
+        return super.deleteTags(UUID.fromString(id), tagList,
+                                context.createContext(createdBy, reason, comment, request));
+    }
+
+    @Override
+    protected ObjectType getObjectType() {
+        return ObjectType.PAYMENT;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
index 9b81c9f..ad2e32a 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceResource.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,9 +21,12 @@ package org.killbill.billing.jaxrs.resources;
 import java.io.IOException;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -41,31 +46,29 @@ import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 
 import org.joda.time.LocalDate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.clock.Clock;
 import org.killbill.billing.entitlement.api.SubscriptionApiException;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoiceNotifier;
+import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.InvoiceItemJson;
 import org.killbill.billing.jaxrs.json.InvoiceJson;
-import org.killbill.billing.jaxrs.json.PaymentJson;
+import org.killbill.billing.jaxrs.json.InvoicePaymentJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldApiException;
 import org.killbill.billing.util.api.CustomFieldUserApi;
@@ -73,14 +76,19 @@ import org.killbill.billing.util.api.TagApiException;
 import org.killbill.billing.util.api.TagDefinitionApiException;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.audit.AccountAuditLogs;
-import org.killbill.billing.util.audit.AccountAuditLogsForObjectType;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.Pagination;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Function;
-import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.inject.Inject;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -93,7 +101,6 @@ public class InvoiceResource extends JaxRsResourceBase {
     private static final String ID_PARAM_NAME = "invoiceId";
 
     private final InvoiceUserApi invoiceApi;
-    private final PaymentApi paymentApi;
     private final InvoiceNotifier invoiceNotifier;
 
     @Inject
@@ -107,12 +114,12 @@ public class InvoiceResource extends JaxRsResourceBase {
                            final CustomFieldUserApi customFieldUserApi,
                            final AuditUserApi auditUserApi,
                            final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.invoiceApi = invoiceApi;
-        this.paymentApi = paymentApi;
         this.invoiceNotifier = invoiceNotifier;
     }
 
+    //@Timed
     @GET
     @Path("/{invoiceId:" + UUID_PATTERN + "}/")
     @Produces(APPLICATION_JSON)
@@ -132,6 +139,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
     }
 
+    //@Timed
     @GET
     @Path("/{invoiceNumber:" + NUMBER_PATTERN + "}/")
     @Produces(APPLICATION_JSON)
@@ -151,6 +159,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
     }
 
+    //@Timed
     @GET
     @Path("/{invoiceId:" + UUID_PATTERN + "}/html")
     @Produces(TEXT_HTML)
@@ -159,6 +168,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(invoiceApi.getInvoiceAsHTML(UUID.fromString(invoiceId), context.createContext(request))).build();
     }
 
+    //@Timed
     @GET
     @Path("/" + PAGINATION)
     @Produces(APPLICATION_JSON)
@@ -184,9 +194,11 @@ public class InvoiceResource extends JaxRsResourceBase {
                                                         return new InvoiceJson(invoice, withItems, accountsAuditLogs.get().get(invoice.getAccountId()));
                                                     }
                                                 },
-                                                nextPageUri);
+                                                nextPageUri
+                                               );
     }
 
+    //@Timed
     @GET
     @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -213,9 +225,11 @@ public class InvoiceResource extends JaxRsResourceBase {
                                                         return new InvoiceJson(invoice, withItems, accountsAuditLogs.get().get(invoice.getAccountId()));
                                                     }
                                                 },
-                                                nextPageUri);
+                                                nextPageUri
+                                               );
     }
 
+    //@Timed
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
@@ -239,6 +253,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         }
     }
 
+    //@Timed
     @DELETE
     @Path("/{invoiceId:" + UUID_PATTERN + "}" + "/{invoiceItemId:" + UUID_PATTERN + "}/cba")
     @Consumes(APPLICATION_JSON)
@@ -259,6 +274,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         return Response.status(Status.OK).build();
     }
 
+    //@Timed
     @POST
     @Path("/{invoiceId:" + UUID_PATTERN + "}")
     @Consumes(APPLICATION_JSON)
@@ -295,135 +311,125 @@ public class InvoiceResource extends JaxRsResourceBase {
         return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId());
     }
 
+    //@Timed
     @POST
     @Produces(APPLICATION_JSON)
     @Consumes(APPLICATION_JSON)
-    @Path("/" + CHARGES)
-    public Response createExternalCharge(final InvoiceItemJson externalChargeJson,
-                                         @QueryParam(QUERY_REQUESTED_DT) final String requestedDateTimeString,
-                                         @QueryParam(QUERY_PAY_INVOICE) @DefaultValue("false") final Boolean payInvoice,
-                                         @HeaderParam(HDR_CREATED_BY) final String createdBy,
-                                         @HeaderParam(HDR_REASON) final String reason,
-                                         @HeaderParam(HDR_COMMENT) final String comment,
-                                         @javax.ws.rs.core.Context final UriInfo uriInfo,
-                                         @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, InvoiceApiException, PaymentApiException {
+    @Path("/" + CHARGES + "/{accountId:" + UUID_PATTERN + "}")
+    public Response createExternalCharges(final Iterable<InvoiceItemJson> externalChargesJson,
+                                          @PathParam("accountId") final String accountId,
+                                          @QueryParam(QUERY_REQUESTED_DT) final String requestedDateTimeString,
+                                          @QueryParam(QUERY_PAY_INVOICE) @DefaultValue("false") final Boolean payInvoice,
+                                          @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                          @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                          @HeaderParam(HDR_REASON) final String reason,
+                                          @HeaderParam(HDR_COMMENT) final String comment,
+                                          @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                          @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, InvoiceApiException, PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
-        final Account account = accountUserApi.getAccountById(UUID.fromString(externalChargeJson.getAccountId()), callContext);
-
-        // Get the effective date of the external charge, in the account timezone
-        final LocalDate requestedDate = toLocalDate(account, requestedDateTimeString, callContext);
-
-        final Currency currency = Objects.firstNonNull(externalChargeJson.getCurrency(), account.getCurrency());
-        final InvoiceItem externalCharge;
-        if (externalChargeJson.getBundleId() != null) {
-            externalCharge = invoiceApi.insertExternalChargeForBundle(account.getId(), UUID.fromString(externalChargeJson.getBundleId()),
-                                                                      externalChargeJson.getAmount(), externalChargeJson.getDescription(),
-                                                                      requestedDate, currency, callContext);
-        } else {
-            externalCharge = invoiceApi.insertExternalCharge(account.getId(), externalChargeJson.getAmount(),
-                                                             externalChargeJson.getDescription(), requestedDate,
-                                                             currency, callContext);
-        }
+        final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), callContext);
 
-        if (payInvoice) {
-            final Invoice invoice = invoiceApi.getInvoice(externalCharge.getInvoiceId(), callContext);
-            paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+        // TODO Get rid of that check once we truly support multiple currencies per account
+        // See discussion https://github.com/killbill/killbill/commit/942e214d49e9c7ed89da76d972ee017d2d3ade58#commitcomment-6045547
+        final Set<Currency> currencies = new HashSet<Currency>(Lists.<InvoiceItemJson, Currency>transform(ImmutableList.<InvoiceItemJson>copyOf(externalChargesJson),
+                                                                                                          new Function<InvoiceItemJson, Currency>() {
+                                                                                                              @Override
+                                                                                                              public Currency apply(final InvoiceItemJson input) {
+                                                                                                                  return input.getCurrency();
+                                                                                                              }
+                                                                                                          }
+                                                                                                         ));
+        if (currencies.size() != 1 || !currencies.iterator().next().equals(account.getCurrency())) {
+            throw new InvoiceApiException(ErrorCode.EXTERNAL_CHARGE_CURRENCY_INVALID, account.getCurrency());
         }
-        return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
-    }
-
-    @POST
-    @Produces(APPLICATION_JSON)
-    @Consumes(APPLICATION_JSON)
-    @Path("/{invoiceId:" + UUID_PATTERN + "}/" + CHARGES)
-    public Response createExternalChargeForInvoice(final InvoiceItemJson externalChargeJson,
-                                                   @PathParam("invoiceId") final String invoiceIdString,
-                                                   @QueryParam(QUERY_REQUESTED_DT) final String requestedDateTimeString,
-                                                   @QueryParam(QUERY_PAY_INVOICE) @DefaultValue("false") final Boolean payInvoice,
-                                                   @HeaderParam(HDR_CREATED_BY) final String createdBy,
-                                                   @HeaderParam(HDR_REASON) final String reason,
-                                                   @HeaderParam(HDR_COMMENT) final String comment,
-                                                   @javax.ws.rs.core.Context final UriInfo uriInfo,
-                                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, InvoiceApiException, PaymentApiException {
-        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
-
-        final Account account = accountUserApi.getAccountById(UUID.fromString(externalChargeJson.getAccountId()), callContext);
 
         // Get the effective date of the external charge, in the account timezone
         final LocalDate requestedDate = toLocalDate(account, requestedDateTimeString, callContext);
 
-        final UUID invoiceId = UUID.fromString(invoiceIdString);
-        final Currency currency = Objects.firstNonNull(externalChargeJson.getCurrency(), account.getCurrency());
-        final InvoiceItem externalCharge;
-        if (externalChargeJson.getBundleId() != null) {
-            externalCharge = invoiceApi.insertExternalChargeForInvoiceAndBundle(account.getId(), invoiceId, UUID.fromString(externalChargeJson.getBundleId()),
-                                                                                externalChargeJson.getAmount(), externalChargeJson.getDescription(),
-                                                                                requestedDate, currency, callContext);
-        } else {
-            externalCharge = invoiceApi.insertExternalChargeForInvoice(account.getId(), invoiceId,
-                                                                       externalChargeJson.getAmount(), externalChargeJson.getDescription(),
-                                                                       requestedDate, currency, callContext);
-        }
+        final Iterable<InvoiceItem> externalCharges = Iterables.<InvoiceItemJson, InvoiceItem>transform(externalChargesJson,
+                                                                                                        new Function<InvoiceItemJson, InvoiceItem>() {
+                                                                                                            @Override
+                                                                                                            public InvoiceItem apply(final InvoiceItemJson invoiceItemJson) {
+                                                                                                                return invoiceItemJson.toInvoiceItem();
+                                                                                                            }
+                                                                                                        }
+                                                                                                       );
+        final List<InvoiceItem> createdExternalCharges = invoiceApi.insertExternalCharges(account.getId(), requestedDate, externalCharges, callContext);
 
         if (payInvoice) {
-            final Invoice invoice = invoiceApi.getInvoice(externalCharge.getInvoiceId(), callContext);
-            paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+            final Collection<UUID> paidInvoices = new HashSet<UUID>();
+            for (final InvoiceItem externalCharge : createdExternalCharges) {
+                if (!paidInvoices.contains(externalCharge.getInvoiceId())) {
+                    paidInvoices.add(externalCharge.getInvoiceId());
+                    final Invoice invoice = invoiceApi.getInvoice(externalCharge.getInvoiceId(), callContext);
+                    createPurchaseForInvoice(account, invoice.getId(), invoice.getBalance(), false, callContext);
+                }
+            }
         }
 
-        return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
+        final List<InvoiceItemJson> createdExternalChargesJson = Lists.<InvoiceItem, InvoiceItemJson>transform(createdExternalCharges,
+                                                                                                               new Function<InvoiceItem, InvoiceItemJson>() {
+                                                                                                                   @Override
+                                                                                                                   public InvoiceItemJson apply(final InvoiceItem input) {
+                                                                                                                       return new InvoiceItemJson(input);
+                                                                                                                   }
+                                                                                                               }
+                                                                                                              );
+        return Response.status(Status.OK).entity(createdExternalChargesJson).build();
     }
 
+    //@Timed
     @GET
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
     @Produces(APPLICATION_JSON)
     public Response getPayments(@PathParam("invoiceId") final String invoiceId,
                                 @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
-                                @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+                                @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, InvoiceApiException {
         final TenantContext tenantContext = context.createContext(request);
-        final List<Payment> payments = paymentApi.getInvoicePayments(UUID.fromString(invoiceId), tenantContext);
-        final List<PaymentJson> result = new ArrayList<PaymentJson>(payments.size());
-        if (payments.size() == 0) {
+
+        final Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId), tenantContext);
+        final List<Payment> payments = new ArrayList<Payment>();
+        for (InvoicePayment cur : invoice.getPayments()) {
+            final Payment payment = paymentApi.getPayment(cur.getPaymentId(), withPluginInfo, ImmutableList.<PluginProperty>of(), tenantContext);
+            payments.add(payment);
+        }
+        final List<InvoicePaymentJson> result = new ArrayList<InvoicePaymentJson>(payments.size());
+        if (payments.isEmpty()) {
             return Response.status(Status.OK).entity(result).build();
         }
-
-        final AccountAuditLogsForObjectType auditLogsForPayments = auditUserApi.getAccountAuditLogs(payments.get(0).getAccountId(),
-                                                                                                    ObjectType.PAYMENT,
-                                                                                                    auditMode.getLevel(),
-                                                                                                    tenantContext);
-
         for (final Payment cur : payments) {
-            result.add(new PaymentJson(cur, auditLogsForPayments.getAuditLogs(cur.getId())));
+            result.add(new InvoicePaymentJson(cur, invoice.getId(), null));
         }
-
         return Response.status(Status.OK).entity(result).build();
     }
 
+    //@Timed
     @POST
     @Produces(APPLICATION_JSON)
     @Consumes(APPLICATION_JSON)
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
-    public Response createInstantPayment(final PaymentJson payment,
+    public Response createInstantPayment(final InvoicePaymentJson payment,
                                          @QueryParam(QUERY_PAYMENT_EXTERNAL) @DefaultValue("false") final Boolean externalPayment,
+                                         @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                          @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                          @HeaderParam(HDR_REASON) final String reason,
                                          @HeaderParam(HDR_COMMENT) final String comment,
                                          @javax.ws.rs.core.Context final HttpServletRequest request,
                                          @javax.ws.rs.core.Context final UriInfo uriInfo) throws AccountApiException, PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
         final Account account = accountUserApi.getAccountById(UUID.fromString(payment.getAccountId()), callContext);
-
-        final UUID invoiceId = UUID.fromString(payment.getInvoiceId());
-        if (externalPayment) {
-            paymentApi.createExternalPayment(account, invoiceId, payment.getAmount(), callContext);
-        } else {
-            paymentApi.createPayment(account, invoiceId, payment.getAmount(), callContext);
-        }
-
-        return uriBuilder.buildResponse(uriInfo, InvoiceResource.class, "getPayments", payment.getInvoiceId());
+        final UUID invoiceId = UUID.fromString(payment.getTargetInvoiceId());
+        final Payment result = createPurchaseForInvoice(account, invoiceId, payment.getPurchasedAmount(), externalPayment, callContext);
+        // STEPH should that live in InvoicePayment instead?
+        return uriBuilder.buildResponse(uriInfo, InvoicePaymentResource.class, "getInvoicePayment", result.getId());
     }
 
+    //@Timed
     @POST
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + EMAIL_NOTIFICATIONS)
     @Consumes(APPLICATION_JSON)
@@ -448,6 +454,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         return Response.status(Status.OK).build();
     }
 
+    //@Timed
     @GET
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Produces(APPLICATION_JSON)
@@ -457,6 +464,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         return super.getCustomFields(UUID.fromString(id), auditMode, context.createContext(request));
     }
 
+    //@Timed
     @POST
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Consumes(APPLICATION_JSON)
@@ -472,6 +480,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                                         context.createContext(createdBy, reason, comment, request), uriInfo);
     }
 
+    //@Timed
     @DELETE
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Consumes(APPLICATION_JSON)
@@ -486,6 +495,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                                         context.createContext(createdBy, reason, comment, request));
     }
 
+    //@Timed
     @GET
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + TAGS)
     @Produces(APPLICATION_JSON)
@@ -499,6 +509,7 @@ public class InvoiceResource extends JaxRsResourceBase {
         return super.getTags(invoice.getAccountId(), invoiceId, auditMode, includedDeleted, tenantContext);
     }
 
+    //@Timed
     @POST
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + TAGS)
     @Consumes(APPLICATION_JSON)
@@ -514,6 +525,7 @@ public class InvoiceResource extends JaxRsResourceBase {
                                 context.createContext(createdBy, reason, comment, request));
     }
 
+    //@Timed
     @DELETE
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + TAGS)
     @Consumes(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index 27e781a..5fa2d62 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -79,6 +81,7 @@ public interface JaxrsResource {
     public static final String QUERY_UNPAID_INVOICES_ONLY = "unpaidInvoicesOnly";
 
     public static final String QUERY_PAYMENT_EXTERNAL = "externalPayment";
+    public static final String QUERY_PAYMENT_AMOUNT = "paymentAmount";
     public static final String QUERY_PAYMENT_WITH_REFUNDS_AND_CHARGEBACKS = "withRefundsAndChargebacks";
     public static final String QUERY_PAYMENT_PLUGIN_NAME = "pluginName";
 
@@ -87,12 +90,17 @@ public interface JaxrsResource {
     public static final String QUERY_CUSTOM_FIELDS = "customFieldList";
 
     public static final String QUERY_PAYMENT_METHOD_PLUGIN_NAME = "pluginName";
-    public static final String QUERY_PAYMENT_METHOD_PLUGIN_INFO = "withPluginInfo";
+    public static final String QUERY_WITH_PLUGIN_INFO = "withPluginInfo";
     public static final String QUERY_PAYMENT_METHOD_IS_DEFAULT = "isDefault";
 
     public static final String QUERY_PAY_ALL_UNPAID_INVOICES = "payAllUnpaidInvoices";
     public static final String QUERY_PAY_INVOICE = "payInvoice";
 
+    public static final String QUERY_PLUGIN_PROPERTY = "pluginProperty";
+
+    public static final String QUERY_START_TIME = "startTime";
+    public static final String QUERY_END_TIME = "endTime";
+
     public static final String QUERY_BUNDLE_TRANSFER_ADDON = "transferAddOn";
     public static final String QUERY_BUNDLE_TRANSFER_CANCEL_IMM = "cancelImmediately";
 
@@ -134,8 +142,13 @@ public interface JaxrsResource {
     public static final String PAYMENTS = "payments";
     public static final String PAYMENTS_PATH = PREFIX + "/" + PAYMENTS;
 
+    public static final String PAYMENT_TRANSACTIONS = "paymentTransactions";
+    public static final String PAYMENT_TRANSACTIONS_PATH = PREFIX + "/" + PAYMENT_TRANSACTIONS;
+
+    public static final String PAYMENT_GATEWAYS = "paymentGateways";
+    public static final String PAYMENT_GATEWAYS_PATH = PREFIX + "/" + PAYMENT_GATEWAYS;
+
     public static final String REFUNDS = "refunds";
-    public static final String REFUNDS_PATH = PREFIX + "/" + "refunds";
 
     public static final String PAYMENT_METHODS = "paymentMethods";
     public static final String PAYMENT_METHODS_PATH = PREFIX + "/" + PAYMENT_METHODS;
@@ -144,6 +157,9 @@ public interface JaxrsResource {
     public static final String CREDITS = "credits";
     public static final String CREDITS_PATH = PREFIX + "/" + CREDITS;
 
+    public static final String INVOICE_PAYMENTS = "invoicePayments";
+    public static final String INVOICE_PAYMENTS_PATH = PREFIX + "/" + INVOICE_PAYMENTS;
+
     public static final String CHARGEBACKS = "chargebacks";
     public static final String CHARGEBACKS_PATH = PREFIX + "/" + CHARGEBACKS;
 
@@ -165,6 +181,9 @@ public interface JaxrsResource {
     public static final String TENANTS = "tenants";
     public static final String TENANTS_PATH = PREFIX + "/" + TENANTS;
 
+    public static final String USAGES = "usages";
+    public static final String USAGES_PATH = PREFIX + "/" + USAGES;
+
     public static final String EXPORT = "export";
     public static final String EXPORT_PATH = PREFIX + "/" + EXPORT;
 
@@ -177,4 +196,10 @@ public interface JaxrsResource {
     public static final String PAUSE = "pause";
     public static final String RESUME = "resume";
 
+    public static final String AUTHORIZATION = "authorization";
+    public static final String CAPTURE = "capture";
+
+    public static final String HOSTED = "hosted";
+    public static final String FORM = "form";
+    public static final String NOTIFICATION = "notification";
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 44d66cb..e3d8de4 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -18,7 +20,9 @@ package org.killbill.billing.jaxrs.resources;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.math.BigDecimal;
 import java.net.URI;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -39,19 +43,24 @@ import org.joda.time.LocalDate;
 import org.joda.time.format.DateTimeFormat;
 import org.joda.time.format.DateTimeFormatter;
 import org.joda.time.format.ISODateTimeFormat;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
-import org.killbill.clock.Clock;
+import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.invoice.api.InvoicePaymentType;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.JsonBase;
 import org.killbill.billing.jaxrs.json.TagJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PaymentOptions;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldApiException;
 import org.killbill.billing.util.api.CustomFieldUserApi;
@@ -69,9 +78,13 @@ import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.jackson.ObjectMapper;
 import org.killbill.billing.util.tag.Tag;
 import org.killbill.billing.util.tag.TagDefinition;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.JsonGenerator;
 import com.google.common.base.Function;
+import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
@@ -88,6 +101,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
     protected final CustomFieldUserApi customFieldUserApi;
     protected final AuditUserApi auditUserApi;
     protected final AccountUserApi accountUserApi;
+    protected final PaymentApi paymentApi;
     protected final Context context;
     protected final Clock clock;
 
@@ -99,6 +113,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
                              final CustomFieldUserApi customFieldUserApi,
                              final AuditUserApi auditUserApi,
                              final AccountUserApi accountUserApi,
+                             final PaymentApi paymentApi,
                              final Clock clock,
                              final Context context) {
         this.uriBuilder = uriBuilder;
@@ -106,6 +121,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         this.customFieldUserApi = customFieldUserApi;
         this.auditUserApi = auditUserApi;
         this.accountUserApi = accountUserApi;
+        this.paymentApi = paymentApi;
         this.clock = clock;
         this.context = context;
     }
@@ -239,7 +255,10 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
 
                 generator.writeStartArray();
                 for (final E entity : entities) {
-                    generator.writeObject(toJson.apply(entity));
+                    final J asJson = toJson.apply(entity);
+                    if (asJson != null) {
+                        generator.writeObject(asJson);
+                    }
                 }
                 generator.writeEndArray();
                 generator.close();
@@ -265,7 +284,7 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         Account account = null;
         try {
             account = accountId != null ? accountUserApi.getAccountById(accountId, context) : null;
-        } catch (AccountApiException e) {
+        } catch (final AccountApiException e) {
             log.info("Failed to retrieve account for id " + accountId);
         }
         final DateTime inputDateTime = inputDate != null ? DATE_TIME_FORMATTER.parseDateTime(inputDate) : clock.getUTCNow();
@@ -303,9 +322,81 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
             try {
                 final LocalDate localDate = LocalDate.parse(inputDate, LOCAL_DATE_FORMATTER);
                 return localDate;
-            } catch (IllegalArgumentException expectedAndIgnore) {
+            } catch (final IllegalArgumentException expectedAndIgnore) {
             }
         }
         return null;
     }
+
+    protected Iterable<PluginProperty> extractPluginProperties(@Nullable final Iterable<String> pluginProperties, PluginProperty... additionalProperties) {
+        final Collection<PluginProperty> properties = new LinkedList<PluginProperty>();
+        if (pluginProperties == null) {
+            return properties;
+        }
+
+        for (final String pluginProperty : pluginProperties) {
+            final List<String> property = ImmutableList.<String>copyOf(pluginProperty.split("="));
+            final String key = property.get(0);
+            final String value = property.size() == 1 ? null : Joiner.on("=").join(property.subList(1, property.size()));
+            properties.add(new PluginProperty(key, value, false));
+        }
+        for (PluginProperty cur : additionalProperties) {
+            properties.add(cur);
+        }
+        return properties;
+    }
+
+    protected Payment createPurchaseForInvoice(final Account account, final UUID invoiceId, final BigDecimal amountToPay, final Boolean externalPayment, final CallContext callContext) throws PaymentApiException {
+
+        final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String transactionExternalKey = UUID.randomUUID().toString();
+        final PluginProperty invoiceProperty = new PluginProperty("IPCD_INVOICE_ID" /* InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID (contract with plugin)  */,
+                                                                  invoiceId.toString(), false);
+        properties.add(invoiceProperty);
+
+        final UUID paymentMethodId = externalPayment ? null : account.getPaymentMethodId();
+        return paymentApi.createPurchaseWithPaymentControl(account, paymentMethodId, null, amountToPay, account.getCurrency(), paymentExternalKey, transactionExternalKey,
+                                                           properties, createInvoicePaymentControlPluginApiPaymentOptions(externalPayment), callContext);
+    }
+
+    protected PaymentOptions createInvoicePaymentControlPluginApiPaymentOptions(final boolean isExternalPayment) {
+        return new PaymentOptions() {
+            @Override
+            public boolean isExternalPayment() {
+                return isExternalPayment;
+            }
+
+            @Override
+            public String getPaymentControlPluginName() {
+                /* Contract with plugin */
+                return "__INVOICE_PAYMENT_CONTROL_PLUGIN__";
+            }
+        };
+    }
+
+    public static Iterable<PaymentTransaction> getPaymentTransactions(final List<Payment> payments, final TransactionType transactionType) {
+        return Iterables.concat(Iterables.transform(payments, new Function<Payment, Iterable<PaymentTransaction>>() {
+            @Override
+            public Iterable<PaymentTransaction> apply(final Payment input) {
+                return Iterables.filter(input.getTransactions(), new Predicate<PaymentTransaction>() {
+                    @Override
+                    public boolean apply(final PaymentTransaction input) {
+                        return input.getTransactionType() == transactionType;
+                    }
+                });
+            }
+        }));
+    }
+
+    public static UUID getInvoiceId(final List<InvoicePayment> invoicePayments, final Payment payment) {
+        final InvoicePayment invoicePayment = Iterables.tryFind(invoicePayments, new Predicate<InvoicePayment>() {
+            @Override
+            public boolean apply(final InvoicePayment input) {
+                return input.getPaymentId().equals(payment.getId()) && input.getType() == InvoicePaymentType.ATTEMPT;
+            }
+        }).orNull();
+        return invoicePayment != null ? invoicePayment.getInvoiceId() : null;
+    }
+
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
new file mode 100644
index 0000000..7282bce
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentGatewayResource.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.resources;
+
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.jaxrs.json.GatewayNotificationJson;
+import org.killbill.billing.jaxrs.json.HostedPaymentPageFieldsJson;
+import org.killbill.billing.jaxrs.json.HostedPaymentPageFormDescriptorJson;
+import org.killbill.billing.jaxrs.json.PluginPropertyJson;
+import org.killbill.billing.jaxrs.util.Context;
+import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PaymentGatewayApi;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.plugin.api.GatewayNotification;
+import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
+import org.killbill.billing.util.api.AuditUserApi;
+import org.killbill.billing.util.api.CustomFieldUserApi;
+import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.clock.Clock;
+
+import com.codahale.metrics.annotation.Timed;
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.WILDCARD;
+
+@Path(JaxrsResource.PAYMENT_GATEWAYS_PATH)
+public class PaymentGatewayResource extends JaxRsResourceBase {
+
+    private final PaymentGatewayApi paymentGatewayApi;
+
+    @Inject
+    public PaymentGatewayResource(final JaxrsUriBuilder uriBuilder,
+                                  final TagUserApi tagUserApi,
+                                  final CustomFieldUserApi customFieldUserApi,
+                                  final AuditUserApi auditUserApi,
+                                  final AccountUserApi accountUserApi,
+                                  final PaymentGatewayApi paymentGatewayApi,
+                                  final PaymentApi paymentApi,
+                                  final Clock clock,
+                                  final Context context) {
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        this.paymentGatewayApi = paymentGatewayApi;
+    }
+
+    //@Timed
+    @POST
+    @Path("/" + HOSTED + "/" + FORM + "/{" + QUERY_ACCOUNT_ID + ":" + UUID_PATTERN + "}")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    // Generate form data to redirect the customer to the gateway
+    public Response buildFormDescriptor(final HostedPaymentPageFieldsJson json,
+                                        @PathParam(QUERY_ACCOUNT_ID) final String accountIdString,
+                                        @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                        @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                        @HeaderParam(HDR_REASON) final String reason,
+                                        @HeaderParam(HDR_COMMENT) final String comment,
+                                        @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                        @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID accountId = UUID.fromString(accountIdString);
+        final Account account = accountUserApi.getAccountById(accountId, callContext);
+
+        final Iterable<PluginProperty> customFields;
+        if (json == null) {
+            customFields = ImmutableList.<PluginProperty>of();
+        } else {
+            customFields = Iterables.<PluginPropertyJson, PluginProperty>transform(json.getCustomFields(),
+                                                                                   new Function<PluginPropertyJson, PluginProperty>() {
+                                                                                       @Override
+                                                                                       public PluginProperty apply(final PluginPropertyJson pluginPropertyJson) {
+                                                                                           return pluginPropertyJson.toPluginProperty();
+                                                                                       }
+                                                                                   }
+                                                                                  );
+        }
+        final HostedPaymentPageFormDescriptor descriptor = paymentGatewayApi.buildFormDescriptor(account, customFields, pluginProperties, callContext);
+        final HostedPaymentPageFormDescriptorJson result = new HostedPaymentPageFormDescriptorJson(descriptor);
+
+        return Response.status(Response.Status.OK).entity(result).build();
+    }
+
+    //@Timed
+    @POST
+    @Path("/" + NOTIFICATION + "/{" + QUERY_PAYMENT_PLUGIN_NAME + ":" + ANYTHING_PATTERN + "}")
+    @Consumes(WILDCARD)
+    @Produces(APPLICATION_JSON)
+    public Response processNotification(final String body,
+                                        @PathParam(QUERY_PAYMENT_PLUGIN_NAME) final String pluginName,
+                                        @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                        @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                        @HeaderParam(HDR_REASON) final String reason,
+                                        @HeaderParam(HDR_COMMENT) final String comment,
+                                        @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                        @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+
+        final String notificationPayload;
+        if (Strings.emptyToNull(body) == null && uriInfo.getRequestUri() != null) {
+            notificationPayload = uriInfo.getRequestUri().getRawQuery();
+        } else {
+            notificationPayload = body;
+        }
+
+        // Note: the body is opaque here, as it comes from the gateway. The associated payment plugin will know how to deserialize it though
+        final GatewayNotification notification = paymentGatewayApi.processNotification(notificationPayload, pluginName, pluginProperties, callContext);
+        final GatewayNotificationJson result = new GatewayNotificationJson(notification);
+
+        // The plugin told us how to build the response
+        return result.toResponse();
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
index e181224..6f6a597 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentMethodResource.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -18,6 +20,7 @@ package org.killbill.billing.jaxrs.resources;
 
 import java.net.URI;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicReference;
@@ -38,13 +41,13 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
-import org.killbill.clock.Clock;
 import org.killbill.billing.jaxrs.json.PaymentMethodJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PaymentMethod;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldUserApi;
 import org.killbill.billing.util.api.TagUserApi;
@@ -52,7 +55,9 @@ import org.killbill.billing.util.audit.AccountAuditLogs;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.Pagination;
+import org.killbill.clock.Clock;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Function;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
@@ -65,31 +70,31 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 @Path(JaxrsResource.PAYMENT_METHODS_PATH)
 public class PaymentMethodResource extends JaxRsResourceBase {
 
-    private final PaymentApi paymentApi;
-
     @Inject
-    public PaymentMethodResource(final PaymentApi paymentApi,
-                                 final AccountUserApi accountUserApi,
+    public PaymentMethodResource(final AccountUserApi accountUserApi,
                                  final JaxrsUriBuilder uriBuilder,
                                  final TagUserApi tagUserApi,
                                  final CustomFieldUserApi customFieldUserApi,
                                  final AuditUserApi auditUserApi,
+                                 final PaymentApi paymentApi,
                                  final Clock clock,
                                  final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
-        this.paymentApi = paymentApi;
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
     }
 
+    //@Timed
     @GET
     @Path("/{paymentMethodId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
     public Response getPaymentMethod(@PathParam("paymentMethodId") final String paymentMethodId,
-                                     @QueryParam(QUERY_PAYMENT_METHOD_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                     @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                      @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                     @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
                                      @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final TenantContext tenantContext = context.createContext(request);
 
-        final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(UUID.fromString(paymentMethodId), false, withPluginInfo, tenantContext);
+        final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(UUID.fromString(paymentMethodId), false, withPluginInfo, pluginProperties, tenantContext);
         final Account account = accountUserApi.getAccountById(paymentMethod.getAccountId(), tenantContext);
         final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(paymentMethod.getAccountId(), auditMode.getLevel(), tenantContext);
         final PaymentMethodJson json = PaymentMethodJson.toPaymentMethodJson(account, paymentMethod, accountAuditLogs);
@@ -97,21 +102,43 @@ public class PaymentMethodResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(json).build();
     }
 
+    //@Timed
+    @GET
+    @Produces(APPLICATION_JSON)
+    public Response getPaymentMethodByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey,
+                                          @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                          @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                          @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                                          @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final TenantContext tenantContext = context.createContext(request);
+
+        final PaymentMethod paymentMethod = paymentApi.getPaymentMethodByExternalKey(externalKey, false, withPluginInfo, pluginProperties, tenantContext);
+        final Account account = accountUserApi.getAccountById(paymentMethod.getAccountId(), tenantContext);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(paymentMethod.getAccountId(), auditMode.getLevel(), tenantContext);
+        final PaymentMethodJson json = PaymentMethodJson.toPaymentMethodJson(account, paymentMethod, accountAuditLogs);
+        return Response.status(Status.OK).entity(json).build();
+    }
+
+    //@Timed
     @GET
     @Path("/" + PAGINATION)
     @Produces(APPLICATION_JSON)
     public Response getPaymentMethods(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
                                       @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
                                       @QueryParam(QUERY_PAYMENT_METHOD_PLUGIN_NAME) final String pluginName,
+                                      @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                       @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                      @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final TenantContext tenantContext = context.createContext(request);
 
         final Pagination<PaymentMethod> paymentMethods;
         if (Strings.isNullOrEmpty(pluginName)) {
-            paymentMethods = paymentApi.getPaymentMethods(offset, limit, tenantContext);
+            paymentMethods = paymentApi.getPaymentMethods(offset, limit, withPluginInfo, pluginProperties, tenantContext);
         } else {
-            paymentMethods = paymentApi.getPaymentMethods(offset, limit, pluginName, tenantContext);
+            paymentMethods = paymentApi.getPaymentMethods(offset, limit, pluginName, withPluginInfo, pluginProperties, tenantContext);
         }
 
         final URI nextPageUri = uriBuilder.nextPage(PaymentMethodResource.class, "getPaymentMethods", paymentMethods.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_PAYMENT_METHOD_PLUGIN_NAME, Strings.nullToEmpty(pluginName),
@@ -143,9 +170,11 @@ public class PaymentMethodResource extends JaxRsResourceBase {
                                                         return PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod, accountsAuditLogs.get().get(paymentMethod.getAccountId()));
                                                     }
                                                 },
-                                                nextPageUri);
+                                                nextPageUri
+                                               );
     }
 
+    //@Timed
     @GET
     @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -153,16 +182,19 @@ public class PaymentMethodResource extends JaxRsResourceBase {
                                          @QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
                                          @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
                                          @QueryParam(QUERY_PAYMENT_METHOD_PLUGIN_NAME) final String pluginName,
+                                         @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                          @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                         @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
                                          @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final TenantContext tenantContext = context.createContext(request);
 
         // Search the plugin(s)
         final Pagination<PaymentMethod> paymentMethods;
         if (Strings.isNullOrEmpty(pluginName)) {
-            paymentMethods = paymentApi.searchPaymentMethods(searchKey, offset, limit, tenantContext);
+            paymentMethods = paymentApi.searchPaymentMethods(searchKey, offset, limit, withPluginInfo, pluginProperties, tenantContext);
         } else {
-            paymentMethods = paymentApi.searchPaymentMethods(searchKey, offset, limit, pluginName, tenantContext);
+            paymentMethods = paymentApi.searchPaymentMethods(searchKey, offset, limit, pluginName, withPluginInfo, pluginProperties, tenantContext);
         }
 
         final URI nextPageUri = uriBuilder.nextPage(PaymentMethodResource.class, "searchPaymentMethods", paymentMethods.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
@@ -195,24 +227,28 @@ public class PaymentMethodResource extends JaxRsResourceBase {
                                                         return PaymentMethodJson.toPaymentMethodJson(accounts.get(paymentMethod.getAccountId()), paymentMethod, accountsAuditLogs.get().get(paymentMethod.getAccountId()));
                                                     }
                                                 },
-                                                nextPageUri);
+                                                nextPageUri
+                                               );
     }
 
+    //@Timed
     @DELETE
     @Produces(APPLICATION_JSON)
     @Path("/{paymentMethodId:" + UUID_PATTERN + "}")
     public Response deletePaymentMethod(@PathParam("paymentMethodId") final String paymentMethodId,
                                         @QueryParam(QUERY_DELETE_DEFAULT_PM_WITH_AUTO_PAY_OFF) @DefaultValue("false") final Boolean deleteDefaultPaymentMethodWithAutoPayOff,
+                                        @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                         @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                         @HeaderParam(HDR_REASON) final String reason,
                                         @HeaderParam(HDR_COMMENT) final String comment,
                                         @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
 
-        final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(UUID.fromString(paymentMethodId), false, false, callContext);
+        final PaymentMethod paymentMethod = paymentApi.getPaymentMethodById(UUID.fromString(paymentMethodId), false, false, pluginProperties, callContext);
         final Account account = accountUserApi.getAccountById(paymentMethod.getAccountId(), callContext);
 
-        paymentApi.deletedPaymentMethod(account, UUID.fromString(paymentMethodId), deleteDefaultPaymentMethodWithAutoPayOff, callContext);
+        paymentApi.deletePaymentMethod(account, UUID.fromString(paymentMethodId), deleteDefaultPaymentMethodWithAutoPayOff, pluginProperties, callContext);
 
         return Response.status(Status.OK).build();
     }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
index 9896791..2ccf734 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PaymentResource.java
@@ -1,7 +1,8 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,15 +17,14 @@
 
 package org.killbill.billing.jaxrs.resources;
 
-import java.math.BigDecimal;
 import java.net.URI;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicReference;
 
+import javax.inject.Inject;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -32,125 +32,95 @@ import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountUserApi;
-import org.killbill.clock.Clock;
-import org.killbill.billing.invoice.api.InvoiceApiException;
-import org.killbill.billing.invoice.api.InvoicePayment;
-import org.killbill.billing.invoice.api.InvoicePaymentApi;
-import org.killbill.billing.jaxrs.json.ChargebackJson;
-import org.killbill.billing.jaxrs.json.CustomFieldJson;
-import org.killbill.billing.jaxrs.json.InvoiceItemJson;
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.jaxrs.json.PaymentJson;
-import org.killbill.billing.jaxrs.json.RefundJson;
+import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.Refund;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.util.api.AuditUserApi;
-import org.killbill.billing.util.api.CustomFieldApiException;
 import org.killbill.billing.util.api.CustomFieldUserApi;
-import org.killbill.billing.util.api.TagApiException;
-import org.killbill.billing.util.api.TagDefinitionApiException;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.audit.AccountAuditLogs;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.Pagination;
+import org.killbill.clock.Clock;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Function;
 import com.google.common.base.Strings;
-import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableMap;
-import com.google.inject.Inject;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
 @Path(JaxrsResource.PAYMENTS_PATH)
 public class PaymentResource extends JaxRsResourceBase {
 
-    private static final String ID_PARAM_NAME = "paymentId";
-
-    private final PaymentApi paymentApi;
-    private final InvoicePaymentApi invoicePaymentApi;
-
     @Inject
-    public PaymentResource(final AccountUserApi accountUserApi,
-                           final PaymentApi paymentApi,
-                           final InvoicePaymentApi invoicePaymentApi,
-                           final JaxrsUriBuilder uriBuilder,
+    public PaymentResource(final JaxrsUriBuilder uriBuilder,
                            final TagUserApi tagUserApi,
                            final CustomFieldUserApi customFieldUserApi,
                            final AuditUserApi auditUserApi,
+                           final AccountUserApi accountUserApi,
+                           final PaymentApi paymentApi,
                            final Clock clock,
                            final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
-        this.paymentApi = paymentApi;
-        this.invoicePaymentApi = invoicePaymentApi;
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
     }
 
+    //@Timed
     @GET
-    @Path("/{paymentId:" + UUID_PATTERN + "}")
+    @Path("/{paymentId:" + UUID_PATTERN + "}/")
     @Produces(APPLICATION_JSON)
-    public Response getPayment(@PathParam(ID_PARAM_NAME) final String paymentIdString,
-                               @QueryParam(QUERY_PAYMENT_WITH_REFUNDS_AND_CHARGEBACKS) @DefaultValue("false") final Boolean withRefundsAndChargebacks,
+    public Response getPayment(@PathParam("paymentId") final String paymentIdStr,
+                               @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
+                               @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                               @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
                                @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final UUID paymentIdId = UUID.fromString(paymentIdStr);
         final TenantContext tenantContext = context.createContext(request);
+        final Payment payment = paymentApi.getPayment(paymentIdId, withPluginInfo, pluginProperties, tenantContext);
+        final AccountAuditLogs accountAuditLogs = auditUserApi.getAccountAuditLogs(payment.getAccountId(), auditMode.getLevel(), tenantContext);
+        final PaymentJson result = new PaymentJson(payment, accountAuditLogs);
 
-        final UUID paymentId = UUID.fromString(paymentIdString);
-        final Payment payment = paymentApi.getPayment(paymentId, false, tenantContext);
-
-        final PaymentJson paymentJson;
-        if (withRefundsAndChargebacks) {
-            final List<RefundJson> refunds = new ArrayList<RefundJson>();
-            for (final Refund refund : paymentApi.getPaymentRefunds(paymentId, tenantContext)) {
-                refunds.add(new RefundJson(refund));
-            }
-
-            final List<ChargebackJson> chargebacks = new ArrayList<ChargebackJson>();
-            for (final InvoicePayment chargeback : invoicePaymentApi.getChargebacksByPaymentId(paymentId, tenantContext)) {
-                chargebacks.add(new ChargebackJson(payment.getAccountId(), chargeback));
-            }
-
-            paymentJson = new PaymentJson(payment,
-                                          null, // TODO - the keys are really only used for the timeline
-                                          refunds,
-                                          chargebacks);
-        } else {
-            paymentJson = new PaymentJson(payment, null);
-        }
-
-        return Response.status(Status.OK).entity(paymentJson).build();
+        return Response.status(Response.Status.OK).entity(result).build();
     }
 
+    //@Timed
     @GET
     @Path("/" + PAGINATION)
     @Produces(APPLICATION_JSON)
     public Response getPayments(@QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
                                 @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
                                 @QueryParam(QUERY_PAYMENT_PLUGIN_NAME) final String pluginName,
+                                @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                 @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
                                 @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final TenantContext tenantContext = context.createContext(request);
 
         final Pagination<Payment> payments;
         if (Strings.isNullOrEmpty(pluginName)) {
-            payments = paymentApi.getPayments(offset, limit, tenantContext);
+            payments = paymentApi.getPayments(offset, limit, withPluginInfo, pluginProperties, tenantContext);
         } else {
-            payments = paymentApi.getPayments(offset, limit, pluginName, tenantContext);
+            payments = paymentApi.getPayments(offset, limit, pluginName, withPluginInfo, pluginProperties, tenantContext);
         }
 
         final URI nextPageUri = uriBuilder.nextPage(PaymentResource.class, "getPayments", payments.getNextOffset(), limit, ImmutableMap.<String, String>of(QUERY_PAYMENT_METHOD_PLUGIN_NAME, Strings.nullToEmpty(pluginName),
@@ -165,12 +135,15 @@ public class PaymentResource extends JaxRsResourceBase {
                                                         if (accountsAuditLogs.get().get(payment.getAccountId()) == null) {
                                                             accountsAuditLogs.get().put(payment.getAccountId(), auditUserApi.getAccountAuditLogs(payment.getAccountId(), auditMode.getLevel(), tenantContext));
                                                         }
-                                                        return new PaymentJson(payment, accountsAuditLogs.get().get(payment.getAccountId()).getAuditLogsForPayment(payment.getId()));
+                                                        final AccountAuditLogs accountAuditLogs = accountsAuditLogs.get().get(payment.getAccountId());
+                                                        return new PaymentJson(payment, accountAuditLogs);
                                                     }
                                                 },
-                                                nextPageUri);
+                                                nextPageUri
+                                               );
     }
 
+    //@Timed
     @GET
     @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -178,16 +151,19 @@ public class PaymentResource extends JaxRsResourceBase {
                                    @QueryParam(QUERY_SEARCH_OFFSET) @DefaultValue("0") final Long offset,
                                    @QueryParam(QUERY_SEARCH_LIMIT) @DefaultValue("100") final Long limit,
                                    @QueryParam(QUERY_PAYMENT_PLUGIN_NAME) final String pluginName,
+                                   @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
                                    @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
+                                   @QueryParam(QUERY_WITH_PLUGIN_INFO) @DefaultValue("false") final Boolean withPluginInfo,
                                    @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final TenantContext tenantContext = context.createContext(request);
 
         // Search the plugin(s)
         final Pagination<Payment> payments;
         if (Strings.isNullOrEmpty(pluginName)) {
-            payments = paymentApi.searchPayments(searchKey, offset, limit, tenantContext);
+            payments = paymentApi.searchPayments(searchKey, offset, limit, withPluginInfo, pluginProperties, tenantContext);
         } else {
-            payments = paymentApi.searchPayments(searchKey, offset, limit, pluginName, tenantContext);
+            payments = paymentApi.searchPayments(searchKey, offset, limit, pluginName, withPluginInfo, pluginProperties, tenantContext);
         }
 
         final URI nextPageUri = uriBuilder.nextPage(PaymentResource.class, "searchPayments", payments.getNextOffset(), limit, ImmutableMap.<String, String>of("searchKey", searchKey,
@@ -203,189 +179,120 @@ public class PaymentResource extends JaxRsResourceBase {
                                                         if (accountsAuditLogs.get().get(payment.getAccountId()) == null) {
                                                             accountsAuditLogs.get().put(payment.getAccountId(), auditUserApi.getAccountAuditLogs(payment.getAccountId(), auditMode.getLevel(), tenantContext));
                                                         }
-                                                        return new PaymentJson(payment, accountsAuditLogs.get().get(payment.getAccountId()).getAuditLogsForPayment(payment.getId()));
+                                                        final AccountAuditLogs accountAuditLogs = accountsAuditLogs.get().get(payment.getAccountId());
+                                                        return new PaymentJson(payment, accountAuditLogs);
                                                     }
                                                 },
-                                                nextPageUri);
+                                                nextPageUri
+                                               );
     }
 
-    @PUT
-    @Path("/{paymentId:" + UUID_PATTERN + "}")
+    //@Timed
+    @POST
+    @Path("/{paymentId:" + UUID_PATTERN + "}/")
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    public Response retryFailedPayment(@PathParam(ID_PARAM_NAME) final String paymentIdString,
-                                       @HeaderParam(HDR_CREATED_BY) final String createdBy,
-                                       @HeaderParam(HDR_REASON) final String reason,
-                                       @HeaderParam(HDR_COMMENT) final String comment,
-                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException, PaymentApiException {
-
+    public Response captureAuthorization(final PaymentTransactionJson json,
+                                         @PathParam("paymentId") final String paymentIdStr,
+                                         @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                         @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                         @HeaderParam(HDR_REASON) final String reason,
+                                         @HeaderParam(HDR_COMMENT) final String comment,
+                                         @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                         @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID paymentId = UUID.fromString(paymentIdStr);
+        final Payment initialPayment = paymentApi.getPayment(paymentId, false, pluginProperties, callContext);
 
-        final UUID paymentId = UUID.fromString(paymentIdString);
-        final Payment payment = paymentApi.getPayment(paymentId, false, callContext);
-        final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
-        final Payment newPayment = paymentApi.retryPayment(account, paymentId, callContext);
-
-        return Response.status(Status.OK).entity(new PaymentJson(newPayment, null)).build();
-    }
-
-    @GET
-    @Path("/{paymentId:" + UUID_PATTERN + "}/" + CHARGEBACKS)
-    @Produces(APPLICATION_JSON)
-    public Response getChargebacksForPayment(@PathParam("paymentId") final String paymentId,
-                                             @javax.ws.rs.core.Context final HttpServletRequest request) throws InvoiceApiException {
-        final TenantContext tenantContext = context.createContext(request);
-
-        final List<InvoicePayment> chargebacks = invoicePaymentApi.getChargebacksByPaymentId(UUID.fromString(paymentId), tenantContext);
-        if (chargebacks.size() == 0) {
-            return Response.status(Response.Status.NO_CONTENT).build();
-        }
+        final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
+        final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency());
 
-        final UUID invoicePaymentId = chargebacks.get(0).getId();
-        final UUID accountId = invoicePaymentApi.getAccountIdFromInvoicePaymentId(invoicePaymentId, tenantContext);
-        final List<ChargebackJson> chargebacksJson = new ArrayList<ChargebackJson>();
-        for (final InvoicePayment chargeback : chargebacks) {
-            chargebacksJson.add(new ChargebackJson(accountId, chargeback));
-        }
-        return Response.status(Response.Status.OK).entity(chargebacksJson).build();
-    }
-
-    @GET
-    @Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
-    @Produces(APPLICATION_JSON)
-    public Response getRefunds(@PathParam("paymentId") final String paymentId,
-                               @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException {
-        final List<Refund> refunds = paymentApi.getPaymentRefunds(UUID.fromString(paymentId), context.createContext(request));
-        final List<RefundJson> result = new ArrayList<RefundJson>(Collections2.transform(refunds, new Function<Refund, RefundJson>() {
-            @Override
-            public RefundJson apply(final Refund input) {
-                // TODO Return adjusted items and audits
-                return new RefundJson(input, null, null);
-            }
-        }));
-
-        return Response.status(Status.OK).entity(result).build();
+        final Payment payment = paymentApi.createCapture(account, paymentId, json.getAmount(), currency,
+                                                         json.getTransactionExternalKey(), pluginProperties, callContext);
+        return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
     }
 
+    //@Timed
     @POST
     @Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    public Response createRefund(final RefundJson json,
-                                 @PathParam("paymentId") final String paymentId,
-                                 @HeaderParam(HDR_CREATED_BY) final String createdBy,
-                                 @HeaderParam(HDR_REASON) final String reason,
-                                 @HeaderParam(HDR_COMMENT) final String comment,
-                                 @javax.ws.rs.core.Context final UriInfo uriInfo,
-                                 @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+    public Response refundPayment(final PaymentTransactionJson json,
+                                  @PathParam("paymentId") final String paymentIdStr,
+                                  @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                  @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                  @HeaderParam(HDR_REASON) final String reason,
+                                  @HeaderParam(HDR_COMMENT) final String comment,
+                                  @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                  @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID paymentId = UUID.fromString(paymentIdStr);
+        final Payment initialPayment = paymentApi.getPayment(paymentId, false, pluginProperties, callContext);
 
-        final UUID paymentUuid = UUID.fromString(paymentId);
-        final Payment payment = paymentApi.getPayment(paymentUuid, false, callContext);
-        final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
-
-        final Refund result;
-        if (json.isAdjusted()) {
-            if (json.getAdjustments() != null && json.getAdjustments().size() > 0) {
-                final Map<UUID, BigDecimal> adjustments = new HashMap<UUID, BigDecimal>();
-                for (final InvoiceItemJson item : json.getAdjustments()) {
-                    adjustments.put(UUID.fromString(item.getInvoiceItemId()), item.getAmount());
-                }
-                result = paymentApi.createRefundWithItemsAdjustments(account, paymentUuid, adjustments, callContext);
-            } else {
-                // Invoice adjustment
-                result = paymentApi.createRefundWithAdjustment(account, paymentUuid, json.getAmount(), callContext);
-            }
-        } else {
-            // Refund without adjustment
-            result = paymentApi.createRefund(account, paymentUuid, json.getAmount(), callContext);
-        }
+        final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
+        final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency());
 
-        return uriBuilder.buildResponse(RefundResource.class, "getRefund", result.getId(), uriInfo.getBaseUri().toString());
-    }
-
-    @GET
-    @Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
-    @Produces(APPLICATION_JSON)
-    public Response getCustomFields(@PathParam(ID_PARAM_NAME) final String id,
-                                    @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
-                                    @javax.ws.rs.core.Context final HttpServletRequest request) {
-        return super.getCustomFields(UUID.fromString(id), auditMode, context.createContext(request));
-    }
-
-    @POST
-    @Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
-    @Consumes(APPLICATION_JSON)
-    @Produces(APPLICATION_JSON)
-    public Response createCustomFields(@PathParam(ID_PARAM_NAME) final String id,
-                                       final List<CustomFieldJson> customFields,
-                                       @HeaderParam(HDR_CREATED_BY) final String createdBy,
-                                       @HeaderParam(HDR_REASON) final String reason,
-                                       @HeaderParam(HDR_COMMENT) final String comment,
-                                       @javax.ws.rs.core.Context final HttpServletRequest request,
-                                       @javax.ws.rs.core.Context final UriInfo uriInfo) throws CustomFieldApiException {
-        return super.createCustomFields(UUID.fromString(id), customFields,
-                                        context.createContext(createdBy, reason, comment, request), uriInfo);
+        final Payment payment = paymentApi.createRefund(account, paymentId, json.getAmount(), currency,
+                                                        json.getTransactionExternalKey(), pluginProperties, callContext);
+        return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
     }
 
+    //@Timed
     @DELETE
-    @Path("/{paymentId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
+    @Path("/{paymentId:" + UUID_PATTERN + "}/")
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    public Response deleteCustomFields(@PathParam(ID_PARAM_NAME) final String id,
-                                       @QueryParam(QUERY_CUSTOM_FIELDS) final String customFieldList,
-                                       @HeaderParam(HDR_CREATED_BY) final String createdBy,
-                                       @HeaderParam(HDR_REASON) final String reason,
-                                       @HeaderParam(HDR_COMMENT) final String comment,
-                                       @javax.ws.rs.core.Context final HttpServletRequest request) throws CustomFieldApiException {
-        return super.deleteCustomFields(UUID.fromString(id), customFieldList,
-                                        context.createContext(createdBy, reason, comment, request));
-    }
+    public Response voidPayment(final PaymentTransactionJson json,
+                                @PathParam("paymentId") final String paymentIdStr,
+                                @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                @HeaderParam(HDR_REASON) final String reason,
+                                @HeaderParam(HDR_COMMENT) final String comment,
+                                @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID paymentId = UUID.fromString(paymentIdStr);
+        final Payment initialPayment = paymentApi.getPayment(paymentId, false, pluginProperties, callContext);
 
-    @GET
-    @Path("/{paymentId:" + UUID_PATTERN + "}/" + TAGS)
-    @Produces(APPLICATION_JSON)
-    public Response getTags(@PathParam(ID_PARAM_NAME) final String paymentIdString,
-                            @QueryParam(QUERY_AUDIT) @DefaultValue("NONE") final AuditMode auditMode,
-                            @QueryParam(QUERY_TAGS_INCLUDED_DELETED) @DefaultValue("false") final Boolean includedDeleted,
-                            @javax.ws.rs.core.Context final HttpServletRequest request) throws TagDefinitionApiException, PaymentApiException {
-        final UUID paymentId = UUID.fromString(paymentIdString);
-        final TenantContext tenantContext = context.createContext(request);
-        final Payment payment = paymentApi.getPayment(paymentId, false, tenantContext);
-        return super.getTags(payment.getAccountId(), paymentId, auditMode, includedDeleted, tenantContext);
+        final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
+
+        final String transactionExternalKey = json != null ? json.getTransactionExternalKey() : null;
+        final Payment payment = paymentApi.createVoid(account, paymentId, transactionExternalKey, pluginProperties, callContext);
+        return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
     }
 
+    //@Timed
     @POST
-    @Path("/{paymentId:" + UUID_PATTERN + "}/" + TAGS)
+    @Path("/{paymentId:" + UUID_PATTERN + "}/" + CHARGEBACKS)
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    public Response createTags(@PathParam(ID_PARAM_NAME) final String id,
-                               @QueryParam(QUERY_TAGS) final String tagList,
-                               @HeaderParam(HDR_CREATED_BY) final String createdBy,
-                               @HeaderParam(HDR_REASON) final String reason,
-                               @HeaderParam(HDR_COMMENT) final String comment,
-                               @javax.ws.rs.core.Context final UriInfo uriInfo,
-                               @javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
-        return super.createTags(UUID.fromString(id), tagList, uriInfo,
-                                context.createContext(createdBy, reason, comment, request));
-    }
+    public Response chargebackPayment(final PaymentTransactionJson json,
+                                      @PathParam("paymentId") final String paymentIdStr,
+                                      @QueryParam(QUERY_PLUGIN_PROPERTY) final List<String> pluginPropertiesString,
+                                      @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                      @HeaderParam(HDR_REASON) final String reason,
+                                      @HeaderParam(HDR_COMMENT) final String comment,
+                                      @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                      @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+        final Iterable<PluginProperty> pluginProperties = extractPluginProperties(pluginPropertiesString);
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+        final UUID paymentId = UUID.fromString(paymentIdStr);
+        final Payment initialPayment = paymentApi.getPayment(paymentId, false, pluginProperties, callContext);
 
-    @DELETE
-    @Path("/{paymentId:" + UUID_PATTERN + "}/" + TAGS)
-    @Consumes(APPLICATION_JSON)
-    @Produces(APPLICATION_JSON)
-    public Response deleteTags(@PathParam(ID_PARAM_NAME) final String id,
-                               @QueryParam(QUERY_TAGS) final String tagList,
-                               @HeaderParam(HDR_CREATED_BY) final String createdBy,
-                               @HeaderParam(HDR_REASON) final String reason,
-                               @HeaderParam(HDR_COMMENT) final String comment,
-                               @javax.ws.rs.core.Context final HttpServletRequest request) throws TagApiException {
-        return super.deleteTags(UUID.fromString(id), tagList,
-                                context.createContext(createdBy, reason, comment, request));
+        final Account account = accountUserApi.getAccountById(initialPayment.getAccountId(), callContext);
+        final Currency currency = json.getCurrency() == null ? account.getCurrency() : Currency.valueOf(json.getCurrency());
+
+        final Payment payment = paymentApi.createChargeback(account, paymentId, json.getAmount(), currency,
+                                                            json.getTransactionExternalKey(), callContext);
+        return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", payment.getId());
     }
 
     @Override
     protected ObjectType getObjectType() {
         return ObjectType.PAYMENT;
     }
+
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
index 17a30f5..afd9406 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
@@ -45,6 +45,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 
+import org.killbill.billing.payment.api.PaymentApi;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -56,6 +57,7 @@ import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldUserApi;
 import org.killbill.billing.util.api.TagUserApi;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.io.ByteStreams;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -78,12 +80,14 @@ public class PluginResource extends JaxRsResourceBase {
                           final CustomFieldUserApi customFieldUserApi,
                           final AuditUserApi auditUserApi,
                           final AccountUserApi accountUserApi,
+                          final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.osgiServlet = osgiServlet;
     }
 
+    //@Timed
     @DELETE
     public Response doDELETE(@javax.ws.rs.core.Context final HttpServletRequest request,
                              @javax.ws.rs.core.Context final HttpServletResponse response,
@@ -92,6 +96,7 @@ public class PluginResource extends JaxRsResourceBase {
         return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
     }
 
+    //@Timed
     @GET
     public Response doGET(@javax.ws.rs.core.Context final HttpServletRequest request,
                           @javax.ws.rs.core.Context final HttpServletResponse response,
@@ -100,6 +105,7 @@ public class PluginResource extends JaxRsResourceBase {
         return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
     }
 
+    //@Timed
     @OPTIONS
     public Response doOPTIONS(@javax.ws.rs.core.Context final HttpServletRequest request,
                               @javax.ws.rs.core.Context final HttpServletResponse response,
@@ -108,6 +114,7 @@ public class PluginResource extends JaxRsResourceBase {
         return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
     }
 
+    //@Timed
     @POST
     @Consumes("application/x-www-form-urlencoded")
     public Response doFormPOST(final MultivaluedMap<String, String> form,
@@ -118,6 +125,7 @@ public class PluginResource extends JaxRsResourceBase {
         return serviceViaOSGIPlugin(form, request, response, servletContext, servletConfig);
     }
 
+    //@Timed
     @POST
     public Response doPOST(@javax.ws.rs.core.Context final HttpServletRequest request,
                            @javax.ws.rs.core.Context final HttpServletResponse response,
@@ -126,6 +134,7 @@ public class PluginResource extends JaxRsResourceBase {
         return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
     }
 
+    //@Timed
     @PUT
     public Response doPUT(@javax.ws.rs.core.Context final HttpServletRequest request,
                           @javax.ws.rs.core.Context final HttpServletResponse response,
@@ -134,6 +143,7 @@ public class PluginResource extends JaxRsResourceBase {
         return serviceViaOSGIPlugin(request, response, servletContext, servletConfig);
     }
 
+    //@Timed
     @HEAD
     public Response doHEAD(@javax.ws.rs.core.Context final HttpServletRequest request,
                            @javax.ws.rs.core.Context final HttpServletResponse response,
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
index 94e561f..730dd3c 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
@@ -31,6 +31,7 @@ import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.subject.Subject;
 
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.clock.Clock;
 import org.killbill.billing.jaxrs.json.SubjectJson;
 import org.killbill.billing.jaxrs.util.Context;
@@ -41,6 +42,7 @@ import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldUserApi;
 import org.killbill.billing.util.api.TagUserApi;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Functions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -61,12 +63,14 @@ public class SecurityResource extends JaxRsResourceBase {
                             final CustomFieldUserApi customFieldUserApi,
                             final AuditUserApi auditUserApi,
                             final AccountUserApi accountUserApi,
+                            final PaymentApi paymentApi,
                             final Clock clock,
                             final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.securityApi = securityApi;
     }
 
+    //@Timed
     @GET
     @Path("/permissions")
     @Produces(APPLICATION_JSON)
@@ -76,6 +80,7 @@ public class SecurityResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(json).build();
     }
 
+    //@Timed
     @GET
     @Path("/subject")
     @Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
index c98ecea..f7c22bd 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SubscriptionResource.java
@@ -38,6 +38,7 @@ import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 
 import org.joda.time.LocalDate;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -77,6 +78,7 @@ import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.userrequest.CompletionUserRequestBase;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.inject.Inject;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -100,14 +102,16 @@ public class SubscriptionResource extends JaxRsResourceBase {
                                 final EntitlementApi entitlementApi,
                                 final SubscriptionApi subscriptionApi,
                                 final AccountUserApi accountUserApi,
+                                final PaymentApi paymentApi,
                                 final Clock clock,
                                 final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.killbillHandler = killbillHandler;
         this.entitlementApi = entitlementApi;
         this.subscriptionApi = subscriptionApi;
     }
 
+    //@Timed
     @GET
     @Path("/{subscriptionId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -119,6 +123,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(json).build();
     }
 
+    //@Timed
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
@@ -163,6 +168,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
         return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
     }
 
+    //@Timed
     @PUT
     @Path("/{subscriptionId:" + UUID_PATTERN + "}/uncancel")
     @Produces(APPLICATION_JSON)
@@ -177,6 +183,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
         return Response.status(Status.OK).build();
     }
 
+    //@Timed
     @PUT
     @Produces(APPLICATION_JSON)
     @Consumes(APPLICATION_JSON)
@@ -237,6 +244,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
         return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, callContext);
     }
 
+    //@Timed
     @DELETE
     @Path("/{subscriptionId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -377,9 +385,9 @@ public class SubscriptionResource extends JaxRsResourceBase {
                     waiter.waitForCompletion(timeoutSec * 1000);
                 }
                 return callback.doResponseOk(operationValue);
-            } catch (InterruptedException e) {
+            } catch (final InterruptedException e) {
                 return Response.status(Status.INTERNAL_SERVER_ERROR).build();
-            } catch (TimeoutException e) {
+            } catch (final TimeoutException e) {
                 return Response.status(Status.fromStatusCode(408)).build();
             } finally {
                 if (waiter != null) {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
index 4c51f68..7238170 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagDefinitionResource.java
@@ -38,6 +38,7 @@ import javax.ws.rs.core.UriInfo;
 
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.clock.Clock;
 import org.killbill.billing.jaxrs.json.TagDefinitionJson;
 import org.killbill.billing.jaxrs.util.Context;
@@ -50,6 +51,7 @@ import org.killbill.billing.util.audit.AuditLog;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.tag.TagDefinition;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Preconditions;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -66,11 +68,13 @@ public class TagDefinitionResource extends JaxRsResourceBase {
                                  final CustomFieldUserApi customFieldUserApi,
                                  final AuditUserApi auditUserApi,
                                  final AccountUserApi accountUserApi,
+                                 final PaymentApi paymentApi,
                                  final Clock clock,
                                  final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
     }
 
+    //@Timed
     @GET
     @Produces(APPLICATION_JSON)
     public Response getTagDefinitions(@javax.ws.rs.core.Context final HttpServletRequest request,
@@ -87,6 +91,7 @@ public class TagDefinitionResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(result).build();
     }
 
+    //@Timed
     @GET
     @Path("/{tagDefinitionId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -100,6 +105,7 @@ public class TagDefinitionResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(json).build();
     }
 
+    //@Timed
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
@@ -117,6 +123,7 @@ public class TagDefinitionResource extends JaxRsResourceBase {
         return uriBuilder.buildResponse(uriInfo, TagDefinitionResource.class, "getTagDefinition", createdTagDef.getId());
     }
 
+    //@Timed
     @DELETE
     @Path("/{tagDefinitionId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java
index b07d467..8c68487 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TagResource.java
@@ -33,6 +33,7 @@ import javax.ws.rs.core.Response;
 
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.clock.Clock;
 import org.killbill.billing.jaxrs.json.TagJson;
 import org.killbill.billing.jaxrs.util.Context;
@@ -47,6 +48,7 @@ import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.tag.Tag;
 import org.killbill.billing.util.tag.TagDefinition;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Inject;
@@ -64,11 +66,13 @@ public class TagResource extends JaxRsResourceBase {
                        final CustomFieldUserApi customFieldUserApi,
                        final AuditUserApi auditUserApi,
                        final AccountUserApi accountUserApi,
+                       final PaymentApi paymentApi,
                        final Clock clock,
                        final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
     }
 
+    //@Timed
     @GET
     @Path("/" + PAGINATION)
     @Produces(APPLICATION_JSON)
@@ -99,6 +103,7 @@ public class TagResource extends JaxRsResourceBase {
                                                 nextPageUri);
     }
 
+    //@Timed
     @GET
     @Path("/" + SEARCH + "/{searchKey:" + ANYTHING_PATTERN + "}")
     @Produces(APPLICATION_JSON)
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
index e20cfce..901801e 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TenantResource.java
@@ -37,6 +37,7 @@ import javax.ws.rs.core.UriInfo;
 
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.clock.Clock;
 import org.killbill.billing.jaxrs.json.TenantJson;
 import org.killbill.billing.jaxrs.json.TenantKeyJson;
@@ -53,6 +54,7 @@ import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 
+import com.codahale.metrics.annotation.Timed;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
@@ -71,12 +73,14 @@ public class TenantResource extends JaxRsResourceBase {
                           final CustomFieldUserApi customFieldUserApi,
                           final AuditUserApi auditUserApi,
                           final AccountUserApi accountUserApi,
+                          final PaymentApi paymentApi,
                           final Clock clock,
                           final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.tenantApi = tenantApi;
     }
 
+    //@Timed
     @GET
     @Path("/{tenantId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
@@ -85,6 +89,7 @@ public class TenantResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(new TenantJson(tenant)).build();
     }
 
+    //@Timed
     @GET
     @Produces(APPLICATION_JSON)
     public Response getTenantByApiKey(@QueryParam(QUERY_API_KEY) final String externalKey) throws TenantApiException {
@@ -92,6 +97,7 @@ public class TenantResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(new TenantJson(tenant)).build();
     }
 
+    //@Timed
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
@@ -106,6 +112,7 @@ public class TenantResource extends JaxRsResourceBase {
         return uriBuilder.buildResponse(uriInfo, TenantResource.class, "getTenant", tenant.getId());
     }
 
+    //@Timed
     @POST
     @Path("/" + REGISTER_NOTIFICATION_CALLBACK)
     @Consumes(APPLICATION_JSON)
@@ -122,6 +129,7 @@ public class TenantResource extends JaxRsResourceBase {
         return Response.created(uri).build();
     }
 
+    //@Timed
     @GET
     @Path("/" + REGISTER_NOTIFICATION_CALLBACK)
     @Produces(APPLICATION_JSON)
@@ -133,6 +141,7 @@ public class TenantResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(result).build();
     }
 
+    //@Timed
     @DELETE
     @Path("/REGISTER_NOTIFICATION_CALLBACK")
     public Response deletePushNotificationCallbacks(@PathParam("tenantId") final String tenantId,
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
index 0ce90a1..1063470 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TestResource.java
@@ -34,22 +34,22 @@ import javax.ws.rs.core.Response.Status;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.AccountUserApi;
-import org.killbill.clock.Clock;
-import org.killbill.clock.ClockMock;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
-import org.killbill.notificationq.api.NotificationQueue;
-import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldUserApi;
 import org.killbill.billing.util.api.RecordIdApi;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.clock.Clock;
+import org.killbill.clock.ClockMock;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -79,14 +79,13 @@ public class TestResource extends JaxRsResourceBase {
     @Inject
     public TestResource(final JaxrsUriBuilder uriBuilder, final TagUserApi tagUserApi, final CustomFieldUserApi customFieldUserApi,
                         final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final RecordIdApi recordIdApi,
-                        final NotificationQueueService notificationQueueService,
+                        final NotificationQueueService notificationQueueService, final PaymentApi paymentApi,
                         final Clock clock, final Context context) {
-        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, clock, context);
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
         this.notificationQueueService = notificationQueueService;
         this.recordIdApi = recordIdApi;
     }
 
-
     public final class ClockResource {
 
         private final DateTime currentUtcTime;
@@ -148,7 +147,6 @@ public class TestResource extends JaxRsResourceBase {
         return getCurrentTime(timeZoneStr);
     }
 
-
     @PUT
     @Path("/clock")
     @Produces(APPLICATION_JSON)
@@ -176,9 +174,7 @@ public class TestResource extends JaxRsResourceBase {
         return getCurrentTime(timeZoneStr);
     }
 
-
     private void waitForNotificationToComplete(final HttpServletRequest request, final Long timeoutSec) {
-
         final TenantContext tenantContext = context.createContext(request);
         final Long tenantRecordId = recordIdApi.getRecordId(tenantContext.getTenantId(), ObjectType.TENANT, tenantContext);
         final List<NotificationQueue> queues = notificationQueueService.getNotificationQueues();
@@ -193,17 +189,15 @@ public class TestResource extends JaxRsResourceBase {
                     nbTryLeft--;
                 }
             }
-            ;
-        } catch (InterruptedException ignore) {
+        } catch (final InterruptedException ignore) {
         }
     }
 
-    private boolean areAllNotificationsProcessed(final List<NotificationQueue> queues, final Long tenantRecordId) {
-
+    private boolean areAllNotificationsProcessed(final Iterable<NotificationQueue> queues, final Long tenantRecordId) {
         final Iterable<NotificationQueue> filtered = Iterables.filter(queues, new Predicate<NotificationQueue>() {
             @Override
             public boolean apply(@Nullable final NotificationQueue input) {
-                return input.getReadyNotificationEntriesForSearchKey2(tenantRecordId) > 0;
+                return input.getFutureNotificationForSearchKey2(tenantRecordId).size() > 0;
             }
         });
         return !filtered.iterator().hasNext();
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
new file mode 100644
index 0000000..24fb0bb
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/TransactionResource.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.resources;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.jaxrs.json.PaymentJson;
+import org.killbill.billing.jaxrs.json.PaymentTransactionJson;
+import org.killbill.billing.jaxrs.util.Context;
+import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.util.api.AuditUserApi;
+import org.killbill.billing.util.api.CustomFieldUserApi;
+import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.audit.AccountAuditLogs;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.entity.Pagination;
+import org.killbill.clock.Clock;
+
+import com.codahale.metrics.annotation.Timed;
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Path(JaxrsResource.PAYMENT_TRANSACTIONS)
+public class TransactionResource extends JaxRsResourceBase {
+
+    @Inject
+    public TransactionResource(final JaxrsUriBuilder uriBuilder,
+                               final TagUserApi tagUserApi,
+                               final CustomFieldUserApi customFieldUserApi,
+                               final AuditUserApi auditUserApi,
+                               final AccountUserApi accountUserApi,
+                               final PaymentApi paymentApi,
+                               final Clock clock,
+                               final Context context) {
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+    }
+
+    //@Timed
+    @POST
+    @Path("/{transactionId:" + UUID_PATTERN + "}/")
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response notifyStateChanged(final PaymentTransactionJson json,
+                                         @PathParam("transactionId") final String transactionIdStr,
+                                         @HeaderParam(HDR_CREATED_BY) final String createdBy,
+                                         @HeaderParam(HDR_REASON) final String reason,
+                                         @HeaderParam(HDR_COMMENT) final String comment,
+                                         @javax.ws.rs.core.Context final UriInfo uriInfo,
+                                         @javax.ws.rs.core.Context final HttpServletRequest request) throws PaymentApiException, AccountApiException {
+
+        final CallContext callContext = context.createContext(createdBy, reason, comment, request);
+
+        final UUID paymentId = UUID.fromString(json.getPaymentId());
+        final Payment payment = paymentApi.getPayment(paymentId, false, ImmutableList.<PluginProperty>of(), callContext);
+        final Account account = accountUserApi.getAccountById(payment.getAccountId(), callContext);
+
+        final boolean success = json.getStatus().equals(TransactionStatus.SUCCESS.name());
+        final Payment result = paymentApi.notifyPendingTransactionOfStateChanged(account, UUID.fromString(transactionIdStr), success, callContext);
+        return uriBuilder.buildResponse(uriInfo, PaymentResource.class, "getPayment", result.getId());
+    }
+
+
+
+    @Override
+    protected ObjectType getObjectType() {
+        return ObjectType.TRANSACTION;
+    }
+
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/Context.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/Context.java
index 1da2c3b..1ce9665 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/Context.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/Context.java
@@ -51,7 +51,7 @@ public class Context {
             final Tenant tenant = getTenantFromRequest(request);
             return contextFactory.createCallContext(tenant == null ? null : tenant.getId(), createdBy, origin, userType, reason,
                                                     comment, UUID.randomUUID());
-        } catch (NullPointerException e) {
+        } catch (final NullPointerException e) {
             throw new IllegalArgumentException(e.getMessage());
         }
     }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
index cd1842b..e4f8827 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -19,6 +19,7 @@ package org.killbill.billing.jaxrs.util;
 import java.net.URI;
 import java.util.Map;
 
+import javax.ws.rs.Path;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
@@ -29,14 +30,11 @@ import org.killbill.billing.jaxrs.resources.JaxrsResource;
 public class JaxrsUriBuilder {
 
     public Response buildResponse(final UriInfo uriInfo, final Class<? extends JaxrsResource> theClass, final String getMethodName, final Object objectId) {
-        final UriBuilder uriBuilder = UriBuilder.fromResource(theClass)
-                                                .path(theClass, getMethodName)
-                                                .scheme(uriInfo.getAbsolutePath().getScheme())
-                                                .host(uriInfo.getAbsolutePath().getHost())
-                                                .port(uriInfo.getAbsolutePath().getPort());
+        final UriBuilder uriBuilder = getUriBuilder(theClass, getMethodName).scheme(uriInfo.getAbsolutePath().getScheme())
+                                                                            .host(uriInfo.getAbsolutePath().getHost())
+                                                                            .port(uriInfo.getAbsolutePath().getPort());
 
         final URI location = objectId != null ? uriBuilder.build(objectId) : uriBuilder.build();
-
         return Response.created(location).build();
     }
 
@@ -46,10 +44,8 @@ public class JaxrsUriBuilder {
             return null;
         }
 
-        final UriBuilder uriBuilder = UriBuilder.fromResource(theClass)
-                                                .path(theClass, getMethodName)
-                                                .queryParam(JaxRsResourceBase.QUERY_SEARCH_OFFSET, nextOffset)
-                                                .queryParam(JaxRsResourceBase.QUERY_SEARCH_LIMIT, limit);
+        final UriBuilder uriBuilder = getUriBuilder(theClass, getMethodName).queryParam(JaxRsResourceBase.QUERY_SEARCH_OFFSET, nextOffset)
+                                                                            .queryParam(JaxRsResourceBase.QUERY_SEARCH_LIMIT, limit);
         for (final String key : params.keySet()) {
             uriBuilder.queryParam(key, params.get(key));
         }
@@ -61,15 +57,31 @@ public class JaxrsUriBuilder {
         // Let's build a n absolute location for cross resources
         // See Jersey ContainerResponse.setHeaders
         final StringBuilder tmp = new StringBuilder(baseUri.substring(0, baseUri.length() - 1));
-        tmp.append(UriBuilder.fromResource(theClass).path(theClass, getMethodName).build(objectId).toString());
+        tmp.append(getUriBuilder(theClass, getMethodName).build(objectId).toString());
         final URI newUriFromResource = UriBuilder.fromUri(tmp.toString()).build();
         final Response.ResponseBuilder ri = Response.created(newUriFromResource);
-        return ri.entity(new Object() {
+        final Object obj = new Object() {
             @SuppressWarnings(value = "all")
             public URI getUri() {
-
                 return newUriFromResource;
             }
-        }).build();
+        };
+        return ri.entity(obj).build();
+    }
+
+    private UriBuilder getUriBuilder(final Class<? extends JaxrsResource> theClassMaybeEnhanced, final String getMethodName) {
+        final Class theClass = getNonEnhancedClass(theClassMaybeEnhanced);
+        return UriBuilder.fromResource(theClass).path(theClass, getMethodName);
+    }
+
+    private Class getNonEnhancedClass(final Class<? extends JaxrsResource> theClassMaybeEnhanced) {
+        // If Guice is proxying the class for example ($EnhancerByGuice$), we want the real class.
+        // See https://java.net/projects/jersey/lists/users/archive/2008-10/message/291
+        Class theClass = theClassMaybeEnhanced;
+        while (theClass.getAnnotation(Path.class) == null && JaxRsResourceBase.class.isAssignableFrom(theClass)) {
+            theClass = theClass.getSuperclass();
+        }
+
+        return theClass;
     }
 }
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/KillbillEventHandler.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/KillbillEventHandler.java
index 07dd7f8..cae76d5 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/KillbillEventHandler.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/util/KillbillEventHandler.java
@@ -28,7 +28,6 @@ import com.google.common.eventbus.Subscribe;
 
 public class KillbillEventHandler {
 
-
     private final List<CompletionUserRequest> activeWaiters;
 
     public KillbillEventHandler() {
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModule.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModule.java
index 0c7d52d..73c1645 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModule.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,11 +18,15 @@
 
 package org.killbill.billing.jaxrs.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.billing.util.jackson.ObjectMapper;
 
-import com.google.inject.AbstractModule;
+public class TestJaxrsModule extends KillBillModule {
 
-public class TestJaxrsModule extends AbstractModule {
+    public TestJaxrsModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     private void installObjectMapper() {
         bind(com.fasterxml.jackson.databind.ObjectMapper.class).toInstance(new ObjectMapper());
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModuleNoDB.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModuleNoDB.java
index d2501ba..fa2dd35 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModuleNoDB.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/glue/TestJaxrsModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,12 +19,17 @@
 package org.killbill.billing.jaxrs.glue;
 
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestJaxrsModuleNoDB extends TestJaxrsModule {
 
+    public TestJaxrsModuleNoDB(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
+
     @Override
     public void configure() {
         super.configure();
-        install(new GuicyKillbillTestNoDBModule());
+        install(new GuicyKillbillTestNoDBModule(configSource));
     }
 }
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/JaxrsTestSuiteNoDB.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/JaxrsTestSuiteNoDB.java
index 56bb2d2..ee72582 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/JaxrsTestSuiteNoDB.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/JaxrsTestSuiteNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,10 +18,9 @@
 
 package org.killbill.billing.jaxrs;
 
-import org.testng.annotations.BeforeClass;
-
 import org.killbill.billing.GuicyKillbillTestSuiteNoDB;
 import org.killbill.billing.jaxrs.glue.TestJaxrsModuleNoDB;
+import org.testng.annotations.BeforeClass;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.inject.Guice;
@@ -33,7 +34,7 @@ public abstract class JaxrsTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
-        final Injector injector = Guice.createInjector(new TestJaxrsModuleNoDB());
+        final Injector injector = Guice.createInjector(new TestJaxrsModuleNoDB(configSource));
         injector.injectMembers(this);
     }
 }
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBillingExceptionJson.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBillingExceptionJson.java
index 0625e6f..86b605c 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBillingExceptionJson.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBillingExceptionJson.java
@@ -55,7 +55,7 @@ public class TestBillingExceptionJson extends JaxrsTestSuiteNoDB {
         try {
             nil.toString();
             Assert.fail();
-        } catch (NullPointerException e) {
+        } catch (final NullPointerException e) {
             final BillingExceptionJson exceptionJson = new BillingExceptionJson(e);
             Assert.assertEquals(exceptionJson.getClassName(), e.getClass().getName());
             Assert.assertNull(exceptionJson.getCode());
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleJsonWithSubscriptions.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleJsonWithSubscriptions.java
index c014465..71e7838 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleJsonWithSubscriptions.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleJsonWithSubscriptions.java
@@ -41,7 +41,7 @@ public class TestBundleJsonWithSubscriptions extends JaxrsTestSuiteNoDB {
         final String externalKey = UUID.randomUUID().toString();
         final List<AuditLogJson> auditLogs = createAuditLogsJson(clock.getUTCNow());
 
-        EventSubscriptionJson event = new EventSubscriptionJson(someUUID, BillingPeriod.NO_BILLING_PERIOD.toString(), new LocalDate(), new LocalDate(), "product", "priceList", "eventType", "phase", null);
+        final EventSubscriptionJson event = new EventSubscriptionJson(someUUID, BillingPeriod.NO_BILLING_PERIOD.toString(), new LocalDate(), new LocalDate(), "product", "priceList", "eventType", "phase", null);
         final SubscriptionJson subscription = new SubscriptionJson(someUUID, someUUID, someUUID, externalKey,
                                                                                        new LocalDate(), someUUID, someUUID, someUUID, someUUID, new LocalDate(), new LocalDate(),
                                                                                        new LocalDate(), new LocalDate(),
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleTimelineJson.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleTimelineJson.java
index ad73b3a..a225588 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleTimelineJson.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestBundleTimelineJson.java
@@ -38,12 +38,12 @@ public class TestBundleTimelineJson extends JaxrsTestSuiteNoDB {
 
         final BundleJson bundleJson = createBundleWithSubscriptions();
         final InvoiceJson invoiceJson = createInvoice();
-        final PaymentJson paymentJson = createPayment(UUID.fromString(invoiceJson.getAccountId()),
+        final InvoicePaymentJson paymentJson = createPayment(UUID.fromString(invoiceJson.getAccountId()),
                                                                   UUID.fromString(invoiceJson.getInvoiceId()));
 
         final BundleTimelineJson bundleTimelineJson = new BundleTimelineJson(viewId,
                                                                              bundleJson,
-                                                                             ImmutableList.<PaymentJson>of(paymentJson),
+                                                                             ImmutableList.<InvoicePaymentJson>of(paymentJson),
                                                                              ImmutableList.<InvoiceJson>of(invoiceJson),
                                                                              reason);
 
@@ -81,21 +81,20 @@ public class TestBundleTimelineJson extends JaxrsTestSuiteNoDB {
                                      targetDate, invoiceNumber, balance, accountId.toString(), null, null, null, null);
     }
 
-    private PaymentJson createPayment(final UUID accountId, final UUID invoiceId) {
+    private InvoicePaymentJson createPayment(final UUID accountId, final UUID invoiceId) {
         final UUID paymentId = UUID.randomUUID();
         final Integer paymentNumber = 17;
-        final UUID paymentMethodId = UUID.randomUUID();
-        final BigDecimal paidAmount = BigDecimal.TEN;
-        final BigDecimal amount = BigDecimal.ZERO;
-        final DateTime paymentRequestedDate = clock.getUTCNow();
-        final DateTime paymentEffectiveDate = clock.getUTCNow();
-        final Integer retryCount = Integer.MAX_VALUE;
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final BigDecimal authAmount = BigDecimal.TEN;
+        final BigDecimal captureAmount = BigDecimal.ZERO;
+        final BigDecimal purchasedAMount = BigDecimal.ZERO;
+        final BigDecimal creditAmount = BigDecimal.ZERO;
+        final BigDecimal refundAmount = BigDecimal.ZERO;
         final String currency = "USD";
-        final String status = UUID.randomUUID().toString();
-        final String gatewayErrorCode = "OK";
-        final String gatewayErrorMsg = "Excellent...";
-        return new PaymentJson(amount, paidAmount, accountId.toString(), invoiceId.toString(), paymentId.toString(), paymentNumber.toString(),
-                                     paymentMethodId.toString(), paymentRequestedDate, paymentEffectiveDate, retryCount, currency, status,
-                                     gatewayErrorCode, gatewayErrorMsg, null, null, null, null);
+
+        return new InvoicePaymentJson(invoiceId.toString(), accountId.toString(), paymentId.toString(), paymentNumber.toString(),
+                                      paymentExternalKey, authAmount, captureAmount, purchasedAMount, refundAmount, creditAmount, currency,
+                                      UUID.randomUUID().toString(),
+                                      null, null);
     }
 }
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestPlanDetailJson.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestPlanDetailJson.java
index cc82b5d..f3e0934 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestPlanDetailJson.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/json/TestPlanDetailJson.java
@@ -18,11 +18,6 @@ package org.killbill.billing.jaxrs.json;
 
 import java.util.UUID;
 
-import org.killbill.billing.catalog.api.Recurring;
-import org.mockito.Mockito;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.InternationalPrice;
 import org.killbill.billing.catalog.api.Listing;
@@ -30,7 +25,11 @@ import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PlanPhase;
 import org.killbill.billing.catalog.api.PriceList;
 import org.killbill.billing.catalog.api.Product;
+import org.killbill.billing.catalog.api.Recurring;
 import org.killbill.billing.jaxrs.JaxrsTestSuiteNoDB;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestPlanDetailJson extends JaxrsTestSuiteNoDB {
 
@@ -41,18 +40,18 @@ public class TestPlanDetailJson extends JaxrsTestSuiteNoDB {
         final BillingPeriod billingPeriod = BillingPeriod.ANNUAL;
         final String priceListName = UUID.randomUUID().toString();
         final PlanDetailJson planDetailJason = new PlanDetailJson(productName, planName, billingPeriod, priceListName, null);
-        Assert.assertEquals(planDetailJason.getProductName(), productName);
-        Assert.assertEquals(planDetailJason.getPlanName(), planName);
-        Assert.assertEquals(planDetailJason.getBillingPeriod(), billingPeriod);
-        Assert.assertEquals(planDetailJason.getPriceListName(), priceListName);
-        Assert.assertEquals(planDetailJason.getFinalPhasePrice(), null);
+        Assert.assertEquals(planDetailJason.getProduct(), productName);
+        Assert.assertEquals(planDetailJason.getPlan(), planName);
+        Assert.assertEquals(planDetailJason.getFinalPhaseBillingPeriod(), billingPeriod);
+        Assert.assertEquals(planDetailJason.getPriceList(), priceListName);
+        Assert.assertEquals(planDetailJason.getFinalPhaseRecurringPrice(), null);
 
         final String asJson = mapper.writeValueAsString(planDetailJason);
-        Assert.assertEquals(asJson, "{\"productName\":\"" + planDetailJason.getProductName() + "\"," +
-                                    "\"planName\":\"" + planDetailJason.getPlanName() + "\"," +
-                                    "\"billingPeriod\":\"" + planDetailJason.getBillingPeriod().toString() + "\"," +
-                                    "\"priceListName\":\"" + planDetailJason.getPriceListName() + "\"," +
-                                    "\"finalPhasePrice\":null}");
+        Assert.assertEquals(asJson, "{\"product\":\"" + planDetailJason.getProduct() + "\"," +
+                                    "\"plan\":\"" + planDetailJason.getPlan() + "\"," +
+                                    "\"priceList\":\"" + planDetailJason.getPriceList() + "\"," +
+                                    "\"finalPhaseBillingPeriod\":\"" + planDetailJason.getFinalPhaseBillingPeriod().toString() + "\"," +
+                                    "\"finalPhaseRecurringPrice\":null}");
 
         final PlanDetailJson fromJson = mapper.readValue(asJson, PlanDetailJson.class);
         Assert.assertEquals(fromJson, planDetailJason);
@@ -83,10 +82,10 @@ public class TestPlanDetailJson extends JaxrsTestSuiteNoDB {
         Mockito.when(listing.getPriceList()).thenReturn(priceList);
 
         final PlanDetailJson planDetailJson = new PlanDetailJson(listing);
-        Assert.assertEquals(planDetailJson.getProductName(), plan.getProduct().getName());
-        Assert.assertEquals(planDetailJson.getPlanName(), plan.getName());
-        Assert.assertEquals(planDetailJson.getBillingPeriod(), plan.getRecurringBillingPeriod());
-        Assert.assertEquals(planDetailJson.getPriceListName(), priceList.getName());
-        Assert.assertEquals(planDetailJson.getFinalPhasePrice().size(), 0);
+        Assert.assertEquals(planDetailJson.getProduct(), plan.getProduct().getName());
+        Assert.assertEquals(planDetailJson.getPlan(), plan.getName());
+        Assert.assertEquals(planDetailJson.getFinalPhaseBillingPeriod(), plan.getRecurringBillingPeriod());
+        Assert.assertEquals(planDetailJson.getPriceList(), priceList.getName());
+        Assert.assertEquals(planDetailJson.getFinalPhaseRecurringPrice().size(), 0);
     }
 }
diff --git a/jaxrs/src/test/java/org/killbill/billing/jaxrs/TestDateConversion.java b/jaxrs/src/test/java/org/killbill/billing/jaxrs/TestDateConversion.java
index 62e9ef0..320ac1c 100644
--- a/jaxrs/src/test/java/org/killbill/billing/jaxrs/TestDateConversion.java
+++ b/jaxrs/src/test/java/org/killbill/billing/jaxrs/TestDateConversion.java
@@ -38,7 +38,7 @@ public class TestDateConversion extends JaxRsResourceBase {
     final DateTimeZone dateTimeZone = DateTimeZone.forOffsetHours(-8);
 
     public TestDateConversion() throws AccountApiException {
-        super(null, null, null, null, Mockito.mock(AccountUserApi.class), new ClockMock(), null);
+        super(null, null, null, null, Mockito.mock(AccountUserApi.class), null, new ClockMock(),  null);
         final Account account = Mockito.mock(Account.class);
         Mockito.when(account.getTimeZone()).thenReturn(dateTimeZone);
         Mockito.when(accountUserApi.getAccountById(accountId, null)).thenReturn(account);

junction/pom.xml 37(+33 -4)

diff --git a/junction/pom.xml b/junction/pom.xml
index d45b017..0fd4150 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-junction</artifactId>
@@ -59,8 +59,15 @@
             <scope>runtime</scope>
         </dependency>
         <dependency>
-            <groupId>org.jdbi</groupId>
-            <artifactId>jdbi</artifactId>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
@@ -94,6 +101,25 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-lifecycle</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-subscription</artifactId>
             <scope>test</scope>
         </dependency>
@@ -146,7 +172,10 @@
             <groupId>org.skife.config</groupId>
             <artifactId>config-magic</artifactId>
         </dependency>
-        <!-- TEST SCOPE -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
diff --git a/junction/src/main/java/org/killbill/billing/junction/glue/DefaultJunctionModule.java b/junction/src/main/java/org/killbill/billing/junction/glue/DefaultJunctionModule.java
index 08a7259..c850f29 100644
--- a/junction/src/main/java/org/killbill/billing/junction/glue/DefaultJunctionModule.java
+++ b/junction/src/main/java/org/killbill/billing/junction/glue/DefaultJunctionModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,19 +18,17 @@
 
 package org.killbill.billing.junction.glue;
 
-import com.google.inject.AbstractModule;
 import org.killbill.billing.glue.JunctionModule;
+import org.killbill.billing.junction.BillingInternalApi;
 import org.killbill.billing.junction.plumbing.billing.BlockingCalculator;
 import org.killbill.billing.junction.plumbing.billing.DefaultInternalBillingApi;
-import org.killbill.billing.junction.BillingInternalApi;
-import org.skife.config.ConfigSource;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 
-public class DefaultJunctionModule extends AbstractModule implements JunctionModule {
+public class DefaultJunctionModule extends KillBillModule implements JunctionModule {
 
-    protected final ConfigSource configSource;
-
-    public DefaultJunctionModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public DefaultJunctionModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     @Override
@@ -42,7 +42,6 @@ public class DefaultJunctionModule extends AbstractModule implements JunctionMod
         bind(BillingInternalApi.class).to(DefaultInternalBillingApi.class).asEagerSingleton();
     }
 
-
     public void installBlockingCalculator() {
         bind(BlockingCalculator.class).asEagerSingleton();
     }
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java
index 87b8231..4586db9 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultBillingEventSet.java
@@ -17,15 +17,28 @@
 package org.killbill.billing.junction.plumbing.billing;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.junction.BillingEvent;
 import org.killbill.billing.junction.BillingEventSet;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
 public class DefaultBillingEventSet extends TreeSet<BillingEvent> implements SortedSet<BillingEvent>, BillingEventSet {
 
     private static final long serialVersionUID = 1L;
@@ -55,6 +68,24 @@ public class DefaultBillingEventSet extends TreeSet<BillingEvent> implements Sor
         return subscriptionIdsWithAutoInvoiceOff;
     }
 
+    @Override
+    public Map<String, Usage> getUsages() {
+        final Iterable<Usage> allUsages = Iterables.concat(Iterables.transform(this, new Function<BillingEvent, List<Usage>>() {
+            @Override
+            public List<Usage> apply(final BillingEvent input) {
+                return input.getUsages();
+            }
+        }));
+        if (!allUsages.iterator().hasNext()) {
+            return Collections.emptyMap();
+        }
+        final Map<String, Usage> result = new HashMap<String, Usage>();
+        for (Usage cur : Sets.<Usage>newHashSet(allUsages)) {
+            result.put(cur.getName(), cur);
+        }
+        return result;
+    }
+
     public void setAccountAutoInvoiceIsOff(final boolean accountAutoInvoiceIsOff) {
         this.accountAutoInvoiceOff = accountAutoInvoiceIsOff;
     }
diff --git a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
index 1cbcbf6..9b4d9a2 100644
--- a/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
+++ b/junction/src/main/java/org/killbill/billing/junction/plumbing/billing/DefaultInternalBillingApi.java
@@ -22,6 +22,13 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.killbill.billing.catalog.api.BillingMode;
+import org.killbill.billing.catalog.api.Usage;
+import org.killbill.billing.util.timezone.DateAndTimeZoneContext;
+import org.killbill.clock.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -57,19 +64,23 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
     private final CatalogService catalogService;
     private final BlockingCalculator blockCalculator;
     private final TagInternalApi tagApi;
+    private final Clock clock;
 
     @Inject
     public DefaultInternalBillingApi(final AccountInternalApi accountApi,
                                      final BillCycleDayCalculator bcdCalculator,
                                      final SubscriptionBaseInternalApi subscriptionApi,
                                      final BlockingCalculator blockCalculator,
-                                     final CatalogService catalogService, final TagInternalApi tagApi) {
+                                     final CatalogService catalogService,
+                                     final TagInternalApi tagApi,
+                                     final Clock clock) {
         this.accountApi = accountApi;
         this.bcdCalculator = bcdCalculator;
         this.subscriptionApi = subscriptionApi;
         this.catalogService = catalogService;
         this.blockCalculator = blockCalculator;
         this.tagApi = tagApi;
+        this.clock = clock;
     }
 
     @Override
@@ -133,6 +144,12 @@ public class DefaultInternalBillingApi implements BillingInternalApi {
 
         boolean updatedAccountBCD = false;
         for (final SubscriptionBase subscription : subscriptions) {
+
+            // The subscription did not even start, so there is nothing to do yet, we can skip and avoid some NPE down the line when calculating the BCD
+            if (subscription.getState() == null) {
+                continue;
+            }
+
             for (final EffectiveSubscriptionInternalEvent transition : subscriptionApi.getBillingTransitions(subscription, context)) {
                 try {
                     final int bcdLocal = bcdCalculator.calculateBcd(bundle, subscription, transition, account, context);
diff --git a/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModule.java b/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModule.java
index 9f84e7d..308c776 100644
--- a/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModule.java
+++ b/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,8 +18,6 @@
 
 package org.killbill.billing.junction.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.entitlement.api.svcs.DefaultInternalBlockingApi;
 import org.killbill.billing.entitlement.block.BlockingChecker;
 import org.killbill.billing.entitlement.block.MockBlockingChecker;
@@ -25,13 +25,13 @@ import org.killbill.billing.entitlement.dao.BlockingStateDao;
 import org.killbill.billing.entitlement.dao.MockBlockingStateDao;
 import org.killbill.billing.junction.BlockingInternalApi;
 import org.killbill.billing.mock.glue.MockEntitlementModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
-import org.killbill.billing.util.glue.MetricsModule;
 
 public class TestJunctionModule extends DefaultJunctionModule {
 
-    public TestJunctionModule(final ConfigSource configSource) {
+    public TestJunctionModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -39,13 +39,16 @@ public class TestJunctionModule extends DefaultJunctionModule {
     protected void configure() {
         super.configure();
 
-        install(new MetricsModule());
         install(new CacheModule(configSource));
-        install(new CallContextModule());
+        install(new CallContextModule(configSource));
     }
 
     public class MockEntitlementModuleForJunction extends MockEntitlementModule {
 
+        public MockEntitlementModuleForJunction(final KillbillConfigSource configSource) {
+            super(configSource);
+        }
+
         @Override
         public void installBlockingApi() {
             bind(BlockingInternalApi.class).to(DefaultInternalBlockingApi.class).asEagerSingleton();
diff --git a/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModuleNoDB.java b/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModuleNoDB.java
index ec3b0af..79d60e3 100644
--- a/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModuleNoDB.java
+++ b/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,25 +18,17 @@
 
 package org.killbill.billing.junction.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
 import org.killbill.billing.catalog.MockCatalogModule;
 import org.killbill.billing.mock.glue.MockAccountModule;
 import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
 import org.killbill.billing.mock.glue.MockSubscriptionModule;
 import org.killbill.billing.mock.glue.MockTagModule;
-import org.killbill.notificationq.MockNotificationQueueService;
-import org.killbill.notificationq.api.NotificationQueueConfig;
-import org.killbill.notificationq.api.NotificationQueueService;
-import org.killbill.billing.util.bus.InMemoryBusModule;
-
-import com.google.common.collect.ImmutableMap;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestJunctionModuleNoDB extends TestJunctionModule {
 
-    public TestJunctionModuleNoDB(final ConfigSource configSource) {
+    public TestJunctionModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -42,25 +36,12 @@ public class TestJunctionModuleNoDB extends TestJunctionModule {
     protected void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestNoDBModule());
-        install(new MockNonEntityDaoModule());
-        install(new InMemoryBusModule(configSource));
-        install(new MockAccountModule());
-        install(new MockCatalogModule());
-        install(new MockSubscriptionModule());
-        install(new MockEntitlementModuleForJunction());
-        install(new MockTagModule());
-        installNotificationQueue();
-    }
-
-    private void installNotificationQueue() {
-        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
-        configureNotificationQueueConfig();
-    }
-
-    protected void configureNotificationQueueConfig() {
-        final NotificationQueueConfig config = new ConfigurationObjectFactory(configSource).buildWithReplacements(NotificationQueueConfig.class,
-                                                                                                                  ImmutableMap.<String, String>of("instanceName", "main"));
-        bind(NotificationQueueConfig.class).toInstance(config);
+        install(new GuicyKillbillTestNoDBModule(configSource));
+        install(new MockNonEntityDaoModule(configSource));
+        install(new MockAccountModule(configSource));
+        install(new MockCatalogModule(configSource));
+        install(new MockSubscriptionModule(configSource));
+        install(new MockEntitlementModuleForJunction(configSource));
+        install(new MockTagModule(configSource));
     }
 }
diff --git a/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModuleWithEmbeddedDB.java b/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModuleWithEmbeddedDB.java
index 30583cd..f90fcd4 100644
--- a/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModuleWithEmbeddedDB.java
+++ b/junction/src/test/java/org/killbill/billing/junction/glue/TestJunctionModuleWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,23 +18,19 @@
 
 package org.killbill.billing.junction.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
 import org.killbill.billing.account.glue.DefaultAccountModule;
 import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.catalog.glue.CatalogModule;
 import org.killbill.billing.entitlement.glue.DefaultEntitlementModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.glue.DefaultSubscriptionModule;
-import org.killbill.billing.util.glue.BusModule;
-import org.killbill.billing.util.glue.MetricsModule;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
-import org.killbill.billing.util.glue.NotificationQueueModule;
 import org.killbill.billing.util.glue.TagStoreModule;
 
 public class TestJunctionModuleWithEmbeddedDB extends TestJunctionModule {
 
-    public TestJunctionModuleWithEmbeddedDB(final ConfigSource configSource) {
+    public TestJunctionModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -40,16 +38,13 @@ public class TestJunctionModuleWithEmbeddedDB extends TestJunctionModule {
     protected void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
-        install(new NonEntityDaoModule());
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
+        install(new NonEntityDaoModule(configSource));
         install(new CatalogModule(configSource));
         install(new DefaultAccountModule(configSource));
         install(new DefaultEntitlementModule(configSource));
-        install(new NotificationQueueModule(configSource));
         install(new DefaultSubscriptionModule(configSource));
-        install(new BusModule(configSource));
-        install(new MetricsModule());
-        install(new TagStoreModule());
+        install(new TagStoreModule(configSource));
 
         bind(TestApiListener.class).asEagerSingleton();
     }
diff --git a/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java b/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
index 8a4835f..15389f2 100644
--- a/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
+++ b/junction/src/test/java/org/killbill/billing/junction/JunctionTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,11 @@
 
 package org.killbill.billing.junction;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
-import org.killbill.billing.TestKillbillConfigSource;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.api.TestApiListener;
@@ -35,13 +34,13 @@ import org.killbill.billing.entitlement.DefaultEntitlementService;
 import org.killbill.billing.entitlement.EntitlementService;
 import org.killbill.billing.entitlement.api.EntitlementApi;
 import org.killbill.billing.junction.glue.TestJunctionModuleWithEmbeddedDB;
+import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.mock.MockAccountBuilder;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseService;
 import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseService;
-import org.killbill.billing.util.KillbillConfigSource;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.svcsapi.bus.BusService;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.ClockMock;
 import org.slf4j.Logger;
@@ -89,8 +88,8 @@ public abstract class JunctionTestSuiteWithEmbeddedDB extends GuicyKillbillTestS
     protected Catalog catalog;
 
     @Override
-    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
-        return new TestKillbillConfigSource("/junction.properties");
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/junction.properties");
     }
 
     @BeforeClass(groups = "slow")

NEWS 55(+55 -0)

diff --git a/NEWS b/NEWS
index 196936b..6dd213b 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,58 @@
+0.11.10
+    Fix broken logging (war artifacts)
+    https://github.com/killbill/killbill/issues/210
+    Update killbill-oss-parent to 0.7.22
+
+0.11.9
+    payment: rework Janitor shutdown sequence
+    jdbc: integrate log4jdbc-log4j2
+    profiling: add support for JAX-RS and EhCache
+    Shiro integration bugfixes
+    Fix tests failures on H2
+    https://github.com/killbill/killbill/issues/204
+    Update killbill-oss-parent to 0.7.20
+
+0.11.8
+    Add support for profiling data
+    Harden code to fix sporadic failure in integration tests
+
+0.11.6
+    Payment subsystem (complete merge payment and direct payment, and does renaming)
+
+0.11.5
+    Extract killbill-platform
+    Implement state machines for payments
+    Merge invoice and direct payments flows
+    https://github.com/killbill/killbill/issues/170
+    https://github.com/killbill/killbill/issues/201
+    Update killbill-oss-parent to 0.7.14
+
+0.11.4
+    Upgrade JRuby to 1.7.12
+    Fix NPE in direct payment APIs for accounts without a default payment method
+
+0.11.3
+    Iteration on new direct payment APIs (unstable)
+
+0.11.2
+    Iteration on new direct payment APIs (unstable)
+    invoice: change external charges APIs to allow bulk insertion
+    osgi: introduce OSGIConfigProperties as an an osgi service to allow plugins to read system properties
+
+0.11.1
+    Introduction of new direct payment APIs (unstable)
+
+0.10.2
+    Add paymentAmount parameter to payAllInvoices API
+    Fix OSGI dependencies issue
+
+0.10.1
+    Allow configuration of EhCache by specifying a URI
+
+0.10.0
+    Merge usage branch (consumable in arrear)
+    Upgrade osgi felix framework and resolve osgi errors
+
 0.9.2
     Add org.killbill.server.properties property
     Add default invoice HTML template

overdue/pom.xml 43(+40 -3)

diff --git a/overdue/pom.xml b/overdue/pom.xml
index ebb9392..788e1f8 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-overdue</artifactId>
@@ -27,6 +27,10 @@
     <name>killbill-overdue</name>
     <dependencies>
         <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
             <scope>provided</scope>
@@ -46,6 +50,15 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>com.samskivert</groupId>
+            <artifactId>jmustache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
@@ -79,6 +92,24 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-lifecycle</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
@@ -118,6 +149,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-xmlloader</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-all</artifactId>
             <scope>test</scope>
@@ -128,11 +163,13 @@
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
             <scope>test</scope>
         </dependency>
-
-        <!-- TEST SCOPE -->
         <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
index 1e8b674..cf3e203 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/applicator/OverdueStateApplicator.java
@@ -27,20 +27,15 @@ import javax.inject.Named;
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.joda.time.Period;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.bus.api.PersistentBus;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.ProductCategory;
-import org.killbill.clock.Clock;
 import org.killbill.billing.entitlement.api.BlockingApiException;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.entitlement.api.Entitlement;
@@ -62,6 +57,8 @@ import org.killbill.billing.overdue.notification.OverdueCheckNotifier;
 import org.killbill.billing.overdue.notification.OverduePoster;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.api.TagApiException;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.email.DefaultEmailSender;
 import org.killbill.billing.util.email.EmailApiException;
@@ -69,6 +66,10 @@ import org.killbill.billing.util.email.EmailConfig;
 import org.killbill.billing.util.email.EmailSender;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
@@ -90,6 +91,7 @@ public class OverdueStateApplicator {
     private final TagInternalApi tagApi;
     private final EmailSender emailSender;
     private final NonEntityDao nonEntityDao;
+    private final CacheControllerDispatcher controllerDispatcher;
 
     @Inject
     public OverdueStateApplicator(final BlockingInternalApi accessApi,
@@ -101,7 +103,8 @@ public class OverdueStateApplicator {
                                   final EmailConfig config,
                                   final PersistentBus bus,
                                   final NonEntityDao nonEntityDao,
-                                  final TagInternalApi tagApi) {
+                                  final TagInternalApi tagApi,
+                                  final CacheControllerDispatcher controllerDispatcher) {
 
         this.blockingApi = accessApi;
         this.accountApi = accountApi;
@@ -113,6 +116,7 @@ public class OverdueStateApplicator {
         this.nonEntityDao = nonEntityDao;
         this.emailSender = new DefaultEmailSender(config);
         this.bus = bus;
+        this.controllerDispatcher = controllerDispatcher;
     }
 
     public void apply(final OverdueStateSet overdueStateSet, final BillingState billingState,
@@ -294,7 +298,7 @@ public class OverdueStateApplicator {
             final List<Entitlement> toBeCancelled = new LinkedList<Entitlement>();
             computeEntitlementsToCancel(account, toBeCancelled, context);
 
-            final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
+            final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID));
             for (final Entitlement cur : toBeCancelled) {
                 try {
                     cur.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(clock.getUTCNow(), account.getTimeZone()), actionPolicy, context.toCallContext(tenantId));
@@ -311,7 +315,7 @@ public class OverdueStateApplicator {
     }
 
     private void computeEntitlementsToCancel(final Account account, final List<Entitlement> result, final InternalTenantContext context) throws EntitlementApiException {
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
+        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID));
         final List<Entitlement> allEntitlementsForAccountId = entitlementApi.getAllEntitlementsForAccountId(account.getId(), context.toTenantContext(tenantId));
         // Entitlement is smart enough and will cancel the associated add-ons. See also discussion in https://github.com/killbill/killbill/issues/94
         final Collection<Entitlement> allEntitlementsButAddonsForAccountId = Collections2.<Entitlement>filter(allEntitlementsForAccountId,
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultCondition.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultCondition.java
index a4e6bb0..59a1aec 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultCondition.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultCondition.java
@@ -31,8 +31,8 @@ import org.killbill.billing.catalog.api.TimeUnit;
 import org.killbill.billing.overdue.Condition;
 import org.killbill.billing.overdue.config.api.BillingState;
 import org.killbill.billing.overdue.config.api.PaymentResponse;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
index 9dbe657..18bd8d7 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultDuration.java
@@ -25,8 +25,8 @@ import org.joda.time.Period;
 
 import org.killbill.billing.catalog.api.Duration;
 import org.killbill.billing.catalog.api.TimeUnit;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultDuration extends ValidatingConfig<OverdueConfig> implements Duration {
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
index a4d1338..13fd5cc 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueState.java
@@ -30,9 +30,9 @@ import org.killbill.billing.overdue.EmailNotification;
 import org.killbill.billing.overdue.OverdueApiException;
 import org.killbill.billing.overdue.OverdueCancellationPolicy;
 import org.killbill.billing.overdue.OverdueState;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationError;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationError;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlAccessorType(XmlAccessType.NONE)
 public class DefaultOverdueState extends ValidatingConfig<OverdueConfig> implements OverdueState {
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
index 6c007fb..abf0518 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueStateSet.java
@@ -27,8 +27,8 @@ import org.killbill.billing.overdue.OverdueApiException;
 import org.killbill.billing.overdue.OverdueState;
 import org.killbill.billing.overdue.config.api.BillingState;
 import org.killbill.billing.overdue.config.api.OverdueStateSet;
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 import org.killbill.billing.junction.DefaultBlockingState;
 
 @XmlAccessorType(XmlAccessType.NONE)
@@ -37,7 +37,7 @@ public abstract class DefaultOverdueStateSet extends ValidatingConfig<OverdueCon
     private static final Period ZERO_PERIOD = new Period();
     private final DefaultOverdueState clearState = new DefaultOverdueState().setName(DefaultBlockingState.CLEAR_STATE_NAME).setClearState(true);
 
-    protected abstract DefaultOverdueState[] getStates();
+    public abstract DefaultOverdueState[] getStates();
 
     @Override
     public OverdueState findState(final String stateName) throws OverdueApiException {
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/OverdueConfig.java b/overdue/src/main/java/org/killbill/billing/overdue/config/OverdueConfig.java
index 0bc3a48..dadf498 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/OverdueConfig.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/OverdueConfig.java
@@ -22,8 +22,8 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import java.net.URI;
 
-import org.killbill.billing.util.config.catalog.ValidatingConfig;
-import org.killbill.billing.util.config.catalog.ValidationErrors;
+import org.killbill.xmlloader.ValidatingConfig;
+import org.killbill.xmlloader.ValidationErrors;
 
 @XmlRootElement(name = "overdueConfig")
 @XmlAccessorType(XmlAccessType.NONE)
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/OverdueStatesAccount.java b/overdue/src/main/java/org/killbill/billing/overdue/config/OverdueStatesAccount.java
index afca345..fb3db68 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/OverdueStatesAccount.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/OverdueStatesAccount.java
@@ -32,7 +32,7 @@ public class OverdueStatesAccount extends DefaultOverdueStateSet {
     private DefaultOverdueState[] accountOverdueStates = new DefaultOverdueState[0];
 
     @Override
-    protected DefaultOverdueState[] getStates() {
+    public DefaultOverdueState[] getStates() {
         return accountOverdueStates;
     }
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/CreateOverdueConfigSchema.java b/overdue/src/main/java/org/killbill/billing/overdue/CreateOverdueConfigSchema.java
index 9effa3c..7ed3f66 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/CreateOverdueConfigSchema.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/CreateOverdueConfigSchema.java
@@ -21,7 +21,7 @@ import java.io.FileWriter;
 import java.io.Writer;
 
 import org.killbill.billing.overdue.config.OverdueConfig;
-import org.killbill.billing.util.config.catalog.XMLSchemaGenerator;
+import org.killbill.xmlloader.XMLSchemaGenerator;
 
 public class CreateOverdueConfigSchema {
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
index 5dda68d..d3a5b46 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/glue/DefaultOverdueModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,16 +18,7 @@
 
 package org.killbill.billing.overdue.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
 import org.killbill.billing.glue.OverdueModule;
-import org.killbill.billing.overdue.notification.OverdueAsyncBusNotifier;
-import org.killbill.billing.overdue.notification.OverdueAsyncBusPoster;
-import org.killbill.billing.overdue.notification.OverdueCheckNotifier;
-import org.killbill.billing.overdue.notification.OverdueCheckPoster;
-import org.killbill.billing.overdue.notification.OverduePoster;
-import org.killbill.billing.overdue.notification.OverdueNotifier;
 import org.killbill.billing.overdue.OverdueProperties;
 import org.killbill.billing.overdue.OverdueService;
 import org.killbill.billing.overdue.OverdueUserApi;
@@ -33,21 +26,28 @@ import org.killbill.billing.overdue.api.DefaultOverdueUserApi;
 import org.killbill.billing.overdue.applicator.OverdueEmailGenerator;
 import org.killbill.billing.overdue.applicator.formatters.DefaultOverdueEmailFormatterFactory;
 import org.killbill.billing.overdue.applicator.formatters.OverdueEmailFormatterFactory;
+import org.killbill.billing.overdue.listener.OverdueListener;
+import org.killbill.billing.overdue.notification.OverdueAsyncBusNotifier;
+import org.killbill.billing.overdue.notification.OverdueAsyncBusPoster;
+import org.killbill.billing.overdue.notification.OverdueCheckNotifier;
+import org.killbill.billing.overdue.notification.OverdueCheckPoster;
+import org.killbill.billing.overdue.notification.OverdueNotifier;
+import org.killbill.billing.overdue.notification.OverduePoster;
 import org.killbill.billing.overdue.service.DefaultOverdueService;
 import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.AbstractModule;
 import com.google.inject.name.Names;
 
-public class DefaultOverdueModule extends AbstractModule implements OverdueModule {
-
-    protected final ConfigSource configSource;
+public class DefaultOverdueModule extends KillBillModule implements OverdueModule {
 
     public static final String OVERDUE_NOTIFIER_CHECK_NAMED = "overdueNotifierCheck";
     public static final String OVERDUE_NOTIFIER_ASYNC_BUS_NAMED = "overdueNotifierAsyncBus";
 
-    public DefaultOverdueModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public DefaultOverdueModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     @Override
@@ -59,9 +59,11 @@ public class DefaultOverdueModule extends AbstractModule implements OverdueModul
         installOverdueWrapperFactory();
         installOverdueEmail();
 
-        final OverdueProperties config = new ConfigurationObjectFactory(configSource).build(OverdueProperties.class);
+        final OverdueProperties config = new ConfigurationObjectFactory(skifeConfigSource).build(OverdueProperties.class);
         bind(OverdueProperties.class).toInstance(config);
 
+        bind(OverdueListener.class).asEagerSingleton();
+
         bind(OverdueNotifier.class).annotatedWith(Names.named(OVERDUE_NOTIFIER_CHECK_NAMED)).to(OverdueCheckNotifier.class).asEagerSingleton();
         bind(OverdueNotifier.class).annotatedWith(Names.named(OVERDUE_NOTIFIER_ASYNC_BUS_NAMED)).to(OverdueAsyncBusNotifier.class).asEagerSingleton();
 
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
index dfb24f2..2bcd2b8 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/listener/OverdueListener.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -20,46 +22,46 @@ import java.util.UUID;
 
 import javax.inject.Named;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ObjectType;
-import org.killbill.bus.api.BusEvent;
-import org.killbill.clock.Clock;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.events.ControlTagCreationInternalEvent;
+import org.killbill.billing.events.ControlTagDeletionInternalEvent;
+import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
+import org.killbill.billing.events.PaymentErrorInternalEvent;
+import org.killbill.billing.events.PaymentInfoInternalEvent;
+import org.killbill.billing.overdue.config.DefaultOverdueState;
+import org.killbill.billing.overdue.config.OverdueConfig;
+import org.killbill.billing.overdue.glue.DefaultOverdueModule;
 import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey;
 import org.killbill.billing.overdue.notification.OverdueAsyncBusNotificationKey.OverdueAsyncBusNotificationAction;
 import org.killbill.billing.overdue.notification.OverdueAsyncBusNotifier;
 import org.killbill.billing.overdue.notification.OverduePoster;
-import org.killbill.billing.overdue.glue.DefaultOverdueModule;
 import org.killbill.billing.util.callcontext.CallOrigin;
-import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.UserType;
-import org.killbill.billing.events.ControlTagCreationInternalEvent;
-import org.killbill.billing.events.ControlTagDeletionInternalEvent;
-import org.killbill.billing.events.InvoiceAdjustmentInternalEvent;
-import org.killbill.billing.events.PaymentErrorInternalEvent;
-import org.killbill.billing.events.PaymentInfoInternalEvent;
 import org.killbill.billing.util.tag.ControlTagType;
+import org.killbill.bus.api.BusEvent;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
 
 public class OverdueListener {
 
-    private final OverdueDispatcher dispatcher;
     private final InternalCallContextFactory internalCallContextFactory;
     private final OverduePoster asyncPoster;
     private final Clock clock;
 
+    private OverdueConfig config;
+
     private static final Logger log = LoggerFactory.getLogger(OverdueListener.class);
 
     @Inject
-    public OverdueListener(final OverdueDispatcher dispatcher,
-                           final Clock clock,
-                           @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED)final OverduePoster asyncPoster,
+    public OverdueListener(final Clock clock,
+                           @Named(DefaultOverdueModule.OVERDUE_NOTIFIER_ASYNC_BUS_NAMED) final OverduePoster asyncPoster,
                            final InternalCallContextFactory internalCallContextFactory) {
-        this.dispatcher = dispatcher;
         this.asyncPoster = asyncPoster;
         this.clock = clock;
         this.internalCallContextFactory = internalCallContextFactory;
@@ -79,7 +81,6 @@ public class OverdueListener {
         }
     }
 
-
     @Subscribe
     public void handlePaymentInfoEvent(final PaymentInfoInternalEvent event) {
         log.debug("Received PaymentInfo event {}", event);
@@ -99,12 +100,34 @@ public class OverdueListener {
     }
 
     private void insertBusEventIntoNotificationQueue(final UUID accountId, final BusEvent event, final OverdueAsyncBusNotificationAction action) {
-        final OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
-        asyncPoster.insertOverdueNotification(accountId, clock.getUTCNow(), OverdueAsyncBusNotifier.OVERDUE_ASYNC_BUS_NOTIFIER_QUEUE, notificationKey, createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2()));
+        final boolean shouldInsertNotification = shouldInsertNotification();
 
+        if (shouldInsertNotification) {
+            final OverdueAsyncBusNotificationKey notificationKey = new OverdueAsyncBusNotificationKey(accountId, action);
+            asyncPoster.insertOverdueNotification(accountId, clock.getUTCNow(), OverdueAsyncBusNotifier.OVERDUE_ASYNC_BUS_NOTIFIER_QUEUE, notificationKey, createCallContext(event.getUserToken(), event.getSearchKey1(), event.getSearchKey2()));
+        }
+    }
+
+    // Optimization: don't bother running the Overdue machinery if it's disabled
+    private boolean shouldInsertNotification() {
+        if (config == null || config.getStateSet() == null || config.getStateSet().getStates() == null) {
+            return false;
+        }
+
+        for (final DefaultOverdueState state : config.getStateSet().getStates()) {
+            if (state.getCondition() != null) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     private InternalCallContext createCallContext(final UUID userToken, final Long accountRecordId, final Long tenantRecordId) {
         return internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, "OverdueService", CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
     }
+
+    public void setOverdueConfig(final OverdueConfig config) {
+        this.config = config;
+    }
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
index 42e54a7..96e3053 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/DefaultOverduePosterBase.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,20 +19,10 @@
 package org.killbill.billing.overdue.notification;
 
 import java.util.Collection;
-import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.IDBI;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.clock.Clock;
-import org.killbill.notificationq.api.NotificationEventWithMetadata;
-import org.killbill.notificationq.api.NotificationQueue;
-import org.killbill.notificationq.api.NotificationQueueService;
-import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
 import org.killbill.billing.overdue.service.DefaultOverdueService;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.dao.NonEntityDao;
@@ -38,6 +30,14 @@ import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationEventWithMetadata;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
 
@@ -57,7 +57,6 @@ public abstract class DefaultOverduePosterBase implements OverduePoster {
 
     @Override
     public <T extends OverdueCheckNotificationKey> void insertOverdueNotification(final UUID accountId, final DateTime futureNotificationTime, final String overdueQueueName, final T notificationKey, final InternalCallContext context) {
-
         try {
             final NotificationQueue overdueQueue = notificationQueueService.getNotificationQueue(DefaultOverdueService.OVERDUE_SERVICE_NAME,
                                                                                                  overdueQueueName);
@@ -65,13 +64,12 @@ public abstract class DefaultOverduePosterBase implements OverduePoster {
             transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
                 @Override
                 public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-
                     // Check if we already have notifications for that key
                     final Class<T> clazz = (Class<T>) notificationKey.getClass();
-                    final Collection<NotificationEventWithMetadata<T>> futureNotifications = getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, overdueQueue, accountId,
+                    final Collection<NotificationEventWithMetadata<T>> futureNotifications = getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, overdueQueue,
                                                                                                                                            clazz, context);
 
-                    boolean shouldInsertNewNotification = cleanupFutureNotificationsFormTransaction(entitySqlDaoWrapperFactory, futureNotifications, futureNotificationTime, overdueQueue);
+                    final boolean shouldInsertNewNotification = cleanupFutureNotificationsFormTransaction(entitySqlDaoWrapperFactory, futureNotifications, futureNotificationTime, overdueQueue);
                     if (shouldInsertNewNotification) {
                         log.debug("Queuing overdue check notification. Account id: {}, timestamp: {}", accountId.toString(), futureNotificationTime.toString());
                         overdueQueue.recordFutureNotificationFromTransaction(entitySqlDaoWrapperFactory.getSqlDao(), futureNotificationTime, notificationKey, context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
@@ -81,13 +79,11 @@ public abstract class DefaultOverduePosterBase implements OverduePoster {
                     return null;
                 }
             });
-        } catch (NoSuchNotificationQueue e) {
+        } catch (final NoSuchNotificationQueue e) {
             log.error("Attempting to put items on a non-existent queue (DefaultOverdueCheck).", e);
-            return;
         }
     }
 
-
     @Override
     public <T extends OverdueCheckNotificationKey> void clearOverdueCheckNotifications(final UUID accountId, final String overdueQueueName, final Class<T> clazz, final InternalCallContext context) {
         try {
@@ -96,15 +92,16 @@ public abstract class DefaultOverduePosterBase implements OverduePoster {
             transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
                 @Override
                 public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                    final Collection<NotificationEventWithMetadata<T>> futureNotifications = getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, checkOverdueQueue, accountId,
+                    final Collection<NotificationEventWithMetadata<T>> futureNotifications = getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, checkOverdueQueue,
                                                                                                                                            clazz, context);
                     for (final NotificationEventWithMetadata<T> notification : futureNotifications) {
                         checkOverdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getSqlDao(), notification.getRecordId());
                     }
+
                     return null;
                 }
             });
-        } catch (NoSuchNotificationQueue e) {
+        } catch (final NoSuchNotificationQueue e) {
             log.error("Attempting to clear items from a non-existent queue (DefaultOverdueCheck).", e);
         }
     }
@@ -112,29 +109,13 @@ public abstract class DefaultOverduePosterBase implements OverduePoster {
     @VisibleForTesting
     <T extends OverdueCheckNotificationKey> Collection<NotificationEventWithMetadata<T>> getFutureNotificationsForAccountInTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
                                                                                                                                        final NotificationQueue checkOverdueQueue,
-                                                                                                                                       final UUID accountId,
                                                                                                                                        final Class<T> clazz,
                                                                                                                                        final InternalCallContext context) {
-
-        final List<NotificationEventWithMetadata<T>> notifications = checkOverdueQueue.getFutureNotificationFromTransactionForSearchKey1(clazz, context.getAccountRecordId(), entitySqlDaoWrapperFactory.getSqlDao());
-
-        /*
-        final Collection<NotificationEventWithMetadata<T>> notificationsFiltered = Collections2.filter(notifications, new Predicate<NotificationEventWithMetadata<T>>() {
-            @Override
-            public boolean apply(@Nullable final NotificationEventWithMetadata<T> input) {
-                final OverdueCheckNotificationKey notificationKey = input.getEvent();
-                return (accountId.equals(notificationKey.getUuidKey()));
-            }
-        });
-        return notificationsFiltered;
-        */
-        return notifications;
+        return checkOverdueQueue.getFutureNotificationFromTransactionForSearchKeys(context.getAccountRecordId(), context.getTenantRecordId(), entitySqlDaoWrapperFactory.getSqlDao());
     }
 
-
     protected abstract <T extends OverdueCheckNotificationKey> boolean cleanupFutureNotificationsFormTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
-                                                                                                      final Collection<NotificationEventWithMetadata<T>> futureNotifications,
-                                                                                                      final DateTime futureNotificationTime, final NotificationQueue overdueQueue);
-
+                                                                                                                 final Collection<NotificationEventWithMetadata<T>> futureNotifications,
+                                                                                                                 final DateTime futureNotificationTime, final NotificationQueue overdueQueue);
 
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusPoster.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusPoster.java
index 0a29f57..750c9ee 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusPoster.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueAsyncBusPoster.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,16 +21,15 @@ package org.killbill.billing.overdue.notification;
 import java.util.Collection;
 
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.IDBI;
-
-import org.killbill.clock.Clock;
-import org.killbill.notificationq.api.NotificationEventWithMetadata;
-import org.killbill.notificationq.api.NotificationQueue;
-import org.killbill.notificationq.api.NotificationQueueService;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationEventWithMetadata;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.skife.jdbi.v2.IDBI;
 
 import com.google.inject.Inject;
 
@@ -49,8 +50,6 @@ public class OverdueAsyncBusPoster extends DefaultOverduePosterBase {
         // If we already have notification for that account we don't insert the new one
         // Note that this is slightly incorrect because we could for instance already have a REFRESH and insert a CLEAR, but if that were the case,
         // if means overdue state would change very rapidly and the behavior would anyway be non deterministic
-        //
-        return futureNotifications.size() == 0;
+        return futureNotifications.isEmpty();
     }
-
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
index f01094f..308de2c 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverdueCheckPoster.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,19 +19,17 @@
 package org.killbill.billing.overdue.notification;
 
 import java.util.Collection;
-import java.util.Iterator;
 
 import org.joda.time.DateTime;
-import org.skife.jdbi.v2.IDBI;
-
-import org.killbill.clock.Clock;
-import org.killbill.notificationq.api.NotificationEventWithMetadata;
-import org.killbill.notificationq.api.NotificationQueue;
-import org.killbill.notificationq.api.NotificationQueueService;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.clock.Clock;
+import org.killbill.notificationq.api.NotificationEventWithMetadata;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.skife.jdbi.v2.IDBI;
 
 import com.google.inject.Inject;
 
@@ -37,8 +37,8 @@ public class OverdueCheckPoster extends DefaultOverduePosterBase {
 
     @Inject
     public OverdueCheckPoster(final NotificationQueueService notificationQueueService,
-                                    final IDBI dbi, final Clock clock,
-                                    final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
+                              final IDBI dbi, final Clock clock,
+                              final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
         super(notificationQueueService, dbi, clock, cacheControllerDispatcher, nonEntityDao);
     }
 
@@ -48,7 +48,7 @@ public class OverdueCheckPoster extends DefaultOverduePosterBase {
                                                                                                         final DateTime futureNotificationTime, final NotificationQueue overdueQueue) {
 
         boolean shouldInsertNewNotification = true;
-        if (futureNotifications.size() > 0) {
+        if (!futureNotifications.isEmpty()) {
             // Results are ordered by effective date asc
             final DateTime earliestExistingNotificationDate = futureNotifications.iterator().next().getEffectiveDate();
 
@@ -63,9 +63,7 @@ public class OverdueCheckPoster extends DefaultOverduePosterBase {
             }
 
             int index = 0;
-            final Iterator<NotificationEventWithMetadata<T>> it = futureNotifications.iterator();
-            while (it.hasNext()) {
-                final NotificationEventWithMetadata<T> cur = it.next();
+            for (final NotificationEventWithMetadata<T> cur : futureNotifications) {
                 if (minIndexToDeleteFrom <= index) {
                     overdueQueue.removeNotificationFromTransaction(entitySqlDaoWrapperFactory.getSqlDao(), cur.getRecordId());
                 }
@@ -74,5 +72,4 @@ public class OverdueCheckPoster extends DefaultOverduePosterBase {
         }
         return shouldInsertNewNotification;
     }
-
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverduePoster.java b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverduePoster.java
index 517f17b..7a0949f 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/notification/OverduePoster.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/notification/OverduePoster.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,12 +21,11 @@ package org.killbill.billing.overdue.notification;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.callcontext.InternalCallContext;
 
 public interface OverduePoster {
 
-    public  <T extends OverdueCheckNotificationKey> void insertOverdueNotification(final UUID accountId, final DateTime futureNotificationTime, final String overdueQueueName, final T notificationKey, final InternalCallContext context);
+    public <T extends OverdueCheckNotificationKey> void insertOverdueNotification(final UUID accountId, final DateTime futureNotificationTime, final String overdueQueueName, final T notificationKey, final InternalCallContext context);
 
-    public  <T extends OverdueCheckNotificationKey> void clearOverdueCheckNotifications(UUID accountId, final String overdueQueueName, final Class<T> clazz, final InternalCallContext context);
+    public <T extends OverdueCheckNotificationKey> void clearOverdueCheckNotifications(UUID accountId, final String overdueQueueName, final Class<T> clazz, final InternalCallContext context);
 }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java b/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
index bbe603d..b60fc57 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/service/DefaultOverdueService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -21,13 +23,7 @@ import java.net.URISyntaxException;
 
 import javax.inject.Named;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.killbill.bus.api.PersistentBus.EventBusException;
-import org.killbill.billing.lifecycle.LifecycleHandlerType;
-import org.killbill.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import org.killbill.billing.overdue.notification.OverdueNotifier;
+import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.overdue.OverdueProperties;
 import org.killbill.billing.overdue.OverdueService;
 import org.killbill.billing.overdue.OverdueUserApi;
@@ -35,9 +31,14 @@ import org.killbill.billing.overdue.api.DefaultOverdueUserApi;
 import org.killbill.billing.overdue.config.OverdueConfig;
 import org.killbill.billing.overdue.glue.DefaultOverdueModule;
 import org.killbill.billing.overdue.listener.OverdueListener;
+import org.killbill.billing.overdue.notification.OverdueNotifier;
 import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
-import org.killbill.billing.util.config.catalog.XMLLoader;
-import org.killbill.billing.util.svcsapi.bus.BusService;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.xmlloader.XMLLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 
@@ -95,20 +96,24 @@ public class DefaultOverdueService implements OverdueService {
                 overdueConfig = XMLLoader.getObjectFromUri(u, OverdueConfig.class);
                 // File not found?
                 if (overdueConfig == null) {
-                    log.warn("Unable to load the overdue config from " + properties.getConfigURI());
+                    log.warn("Overdue system disabled: unable to load the overdue config from " + properties.getConfigURI());
                     overdueConfig = new OverdueConfig();
                 }
 
                 isConfigLoaded = true;
             } catch (final URISyntaxException e) {
+                log.warn("Overdue system disabled: unable to load the overdue config from " + properties.getConfigURI(), e);
                 overdueConfig = new OverdueConfig();
             } catch (final IllegalArgumentException e) {
+                log.warn("Overdue system disabled: unable to load the overdue config from " + properties.getConfigURI(), e);
                 overdueConfig = new OverdueConfig();
             } catch (final Exception e) {
+                log.warn("Unable to load the overdue config from " + properties.getConfigURI(), e);
                 throw new ServiceException(e);
             }
 
             factory.setOverdueConfig(overdueConfig);
+            listener.setOverdueConfig(overdueConfig);
             ((DefaultOverdueUserApi) userApi).setOverdueConfig(overdueConfig);
         }
     }
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
index 72e6250..5f8323b 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/wrapper/OverdueWrapperFactory.java
@@ -87,7 +87,7 @@ public class OverdueWrapperFactory {
 
                 @SuppressWarnings("unchecked")
                 @Override
-                protected DefaultOverdueState[] getStates() {
+                public DefaultOverdueState[] getStates() {
                     return new DefaultOverdueState[0];
                 }
 
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
index 247b843..11c2201 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/applicator/TestOverdueStateApplicator.java
@@ -31,7 +31,7 @@ import org.killbill.billing.overdue.OverdueState;
 import org.killbill.billing.overdue.OverdueTestSuiteWithEmbeddedDB;
 import org.killbill.billing.overdue.config.OverdueConfig;
 import org.killbill.billing.overdue.config.api.OverdueStateSet;
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.XMLLoader;
 import org.killbill.billing.events.OverdueChangeInternalEvent;
 import org.killbill.billing.junction.DefaultBlockingState;
 
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/config/io/TestReadConfig.java b/overdue/src/test/java/org/killbill/billing/overdue/config/io/TestReadConfig.java
index b61db01..f3cdfe5 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/config/io/TestReadConfig.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/config/io/TestReadConfig.java
@@ -20,7 +20,7 @@ import org.testng.annotations.Test;
 
 import org.killbill.billing.overdue.OverdueTestSuiteNoDB;
 import org.killbill.billing.overdue.config.OverdueConfig;
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.XMLLoader;
 
 import com.google.common.io.Resources;
 
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/config/TestCondition.java b/overdue/src/test/java/org/killbill/billing/overdue/config/TestCondition.java
index c34f30e..c856e6a 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/config/TestCondition.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/config/TestCondition.java
@@ -32,7 +32,7 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.overdue.OverdueTestSuiteNoDB;
 import org.killbill.billing.overdue.config.api.BillingState;
 import org.killbill.billing.overdue.config.api.PaymentResponse;
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.XMLLoader;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.DefaultControlTag;
 import org.killbill.billing.util.tag.DescriptiveTag;
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/config/TestOverdueConfig.java b/overdue/src/test/java/org/killbill/billing/overdue/config/TestOverdueConfig.java
index 533d905..48dc0f3 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/config/TestOverdueConfig.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/config/TestOverdueConfig.java
@@ -25,7 +25,7 @@ import org.testng.annotations.Test;
 import org.killbill.billing.catalog.api.TimeUnit;
 import org.killbill.billing.overdue.EmailNotification;
 import org.killbill.billing.overdue.OverdueTestSuiteNoDB;
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.XMLLoader;
 
 public class TestOverdueConfig extends OverdueTestSuiteNoDB {
 
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/ApplicatorMockJunctionModule.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/ApplicatorMockJunctionModule.java
index 45cd9b0..5c36746 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/ApplicatorMockJunctionModule.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/ApplicatorMockJunctionModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -21,15 +23,19 @@ import java.util.UUID;
 
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.clock.ClockMock;
 import org.killbill.billing.entitlement.api.BlockingState;
 import org.killbill.billing.entitlement.api.BlockingStateType;
 import org.killbill.billing.junction.BlockingInternalApi;
 import org.killbill.billing.junction.DefaultBlockingState;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.killbill.clock.ClockMock;
 
-import com.google.inject.AbstractModule;
+public class ApplicatorMockJunctionModule extends KillBillModule {
 
-public class ApplicatorMockJunctionModule extends AbstractModule {
+    public ApplicatorMockJunctionModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
index 7b41126..131e9f4 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,15 +18,13 @@
 
 package org.killbill.billing.overdue.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.mock.glue.MockAccountModule;
 import org.killbill.billing.mock.glue.MockEntitlementModule;
-import org.killbill.billing.mock.glue.MockSubscriptionModule;
 import org.killbill.billing.mock.glue.MockInvoiceModule;
 import org.killbill.billing.mock.glue.MockTagModule;
 import org.killbill.billing.overdue.TestOverdueHelper;
 import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.email.EmailModule;
 import org.killbill.billing.util.email.templates.TemplateModule;
 import org.killbill.billing.util.glue.AuditModule;
@@ -34,7 +34,7 @@ import org.killbill.billing.util.glue.CustomFieldModule;
 
 public class TestOverdueModule extends DefaultOverdueModule {
 
-    public TestOverdueModule(final ConfigSource configSource) {
+    public TestOverdueModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -42,19 +42,19 @@ public class TestOverdueModule extends DefaultOverdueModule {
     protected void configure() {
         super.configure();
 
-        install(new AuditModule());
+        install(new AuditModule(configSource));
         install(new CacheModule(configSource));
-        install(new CallContextModule());
-        install(new CustomFieldModule());
+        install(new CallContextModule(configSource));
+        install(new CustomFieldModule(configSource));
         install(new EmailModule(configSource));
-        install(new MockAccountModule());
-        install(new MockEntitlementModule());
-        install(new MockInvoiceModule());
-        install(new MockTagModule());
-        install(new TemplateModule());
+        install(new MockAccountModule(configSource));
+        install(new MockEntitlementModule(configSource));
+        install(new MockInvoiceModule(configSource));
+        install(new MockTagModule(configSource));
+        install(new TemplateModule(configSource));
 
         // We can't use the dumb mocks in MockJunctionModule here
-        install(new ApplicatorMockJunctionModule());
+        install(new ApplicatorMockJunctionModule(configSource));
 
         bind(OverdueBusListenerTester.class).asEagerSingleton();
         bind(TestOverdueHelper.class).asEagerSingleton();
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModuleNoDB.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModuleNoDB.java
index 4fb7678..498bd46 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModuleNoDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,16 +18,13 @@
 
 package org.killbill.billing.overdue.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
 import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
-import org.killbill.billing.mock.glue.MockNotificationQueueModule;
-import org.killbill.billing.util.bus.InMemoryBusModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestOverdueModuleNoDB extends TestOverdueModule {
 
-    public TestOverdueModuleNoDB(final ConfigSource configSource) {
+    public TestOverdueModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -33,9 +32,7 @@ public class TestOverdueModuleNoDB extends TestOverdueModule {
     public void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestNoDBModule());
-        install(new MockNonEntityDaoModule());
-        install(new MockNotificationQueueModule(configSource));
-        install(new InMemoryBusModule(configSource));
+        install(new GuicyKillbillTestNoDBModule(configSource));
+        install(new MockNonEntityDaoModule(configSource));
     }
 }
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModuleWithEmbeddedDB.java b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModuleWithEmbeddedDB.java
index 1e719a9..bf62d22 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModuleWithEmbeddedDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/glue/TestOverdueModuleWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,17 +18,13 @@
 
 package org.killbill.billing.overdue.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
-import org.killbill.billing.util.glue.BusModule;
-import org.killbill.billing.util.glue.MetricsModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
-import org.killbill.billing.util.glue.NotificationQueueModule;
 
 public class TestOverdueModuleWithEmbeddedDB extends TestOverdueModule {
 
-    public TestOverdueModuleWithEmbeddedDB(final ConfigSource configSource) {
+    public TestOverdueModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -34,10 +32,7 @@ public class TestOverdueModuleWithEmbeddedDB extends TestOverdueModule {
     public void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
-        install(new NonEntityDaoModule());
-        install(new NotificationQueueModule(configSource));
-        install(new MetricsModule());
-        install(new BusModule(configSource));
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
+        install(new NonEntityDaoModule(configSource));
     }
 }
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
index d128df7..c01ef70 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/notification/TestDefaultOverdueCheckPoster.java
@@ -30,9 +30,6 @@ import org.killbill.billing.account.api.Account;
 import org.killbill.notificationq.api.NotificationEventWithMetadata;
 import org.killbill.notificationq.api.NotificationQueue;
 import org.killbill.billing.overdue.OverdueTestSuiteWithEmbeddedDB;
-import org.killbill.billing.overdue.notification.OverdueCheckNotificationKey;
-import org.killbill.billing.overdue.notification.OverdueCheckNotifier;
-import org.killbill.billing.overdue.notification.OverdueCheckPoster;
 import org.killbill.billing.overdue.service.DefaultOverdueService;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
@@ -72,7 +69,7 @@ public class TestDefaultOverdueCheckPoster extends OverdueTestSuiteWithEmbeddedD
         insertOverdueCheckAndVerifyQueueContent(overdueable, 15, 5);
 
         // Verify the final content of the queue
-        Assert.assertEquals(overdueQueue.getFutureNotificationForSearchKey1(OverdueCheckNotificationKey.class, internalCallContext.getAccountRecordId()).size(), 1);
+        Assert.assertEquals(overdueQueue.getFutureNotificationForSearchKeys(internalCallContext.getAccountRecordId(), internalCallContext.getTenantRecordId()).size(), 1);
     }
 
     private void insertOverdueCheckAndVerifyQueueContent(final Account account, final int nbDaysInFuture, final int expectedNbDaysInFuture) throws IOException {
@@ -92,7 +89,7 @@ public class TestDefaultOverdueCheckPoster extends OverdueTestSuiteWithEmbeddedD
         return entitySqlDaoTransactionalJdbiWrapper.execute(new EntitySqlDaoTransactionWrapper<Collection<NotificationEventWithMetadata<OverdueCheckNotificationKey>>>() {
             @Override
             public Collection<NotificationEventWithMetadata<OverdueCheckNotificationKey>> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return ((OverdueCheckPoster)checkPoster).getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, overdueQueue, account.getId(), OverdueCheckNotificationKey.class, internalCallContext);
+                return ((OverdueCheckPoster)checkPoster).getFutureNotificationsForAccountInTransaction(entitySqlDaoWrapperFactory, overdueQueue, OverdueCheckNotificationKey.class, internalCallContext);
             }
         });
     }
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java
index 96533ab..0ecdf61 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -18,27 +20,26 @@ package org.killbill.billing.overdue;
 
 import javax.inject.Named;
 
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-
 import org.killbill.billing.GuicyKillbillTestSuiteNoDB;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.billing.overdue.notification.OverduePoster;
-import org.killbill.billing.overdue.calculator.BillingStateCalculator;
-import org.killbill.notificationq.api.NotificationQueueService;
-import org.killbill.billing.overdue.notification.OverdueNotifier;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
 import org.killbill.billing.overdue.applicator.OverdueStateApplicator;
+import org.killbill.billing.overdue.calculator.BillingStateCalculator;
 import org.killbill.billing.overdue.glue.DefaultOverdueModule;
 import org.killbill.billing.overdue.glue.TestOverdueModuleNoDB;
+import org.killbill.billing.overdue.notification.OverdueNotifier;
+import org.killbill.billing.overdue.notification.OverduePoster;
 import org.killbill.billing.overdue.service.DefaultOverdueService;
 import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.billing.invoice.api.InvoiceInternalApi;
-import org.killbill.billing.junction.BlockingInternalApi;
-import org.killbill.billing.util.svcsapi.bus.BusService;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 
 import com.google.inject.Guice;
 import com.google.inject.Inject;
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
index c77b503..4a27afc 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -18,29 +20,28 @@ package org.killbill.billing.overdue;
 
 import javax.inject.Named;
 
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-
 import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.billing.overdue.notification.OverduePoster;
-import org.killbill.billing.overdue.calculator.BillingStateCalculator;
-import org.killbill.notificationq.api.NotificationQueueService;
-import org.killbill.billing.overdue.notification.OverdueNotifier;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.overdue.applicator.OverdueBusListenerTester;
 import org.killbill.billing.overdue.applicator.OverdueStateApplicator;
+import org.killbill.billing.overdue.calculator.BillingStateCalculator;
 import org.killbill.billing.overdue.glue.DefaultOverdueModule;
 import org.killbill.billing.overdue.glue.TestOverdueModuleWithEmbeddedDB;
+import org.killbill.billing.overdue.notification.OverdueNotifier;
+import org.killbill.billing.overdue.notification.OverduePoster;
 import org.killbill.billing.overdue.service.DefaultOverdueService;
 import org.killbill.billing.overdue.wrapper.OverdueWrapperFactory;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.dao.NonEntityDao;
-import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.billing.invoice.api.InvoiceInternalApi;
-import org.killbill.billing.junction.BlockingInternalApi;
-import org.killbill.billing.util.svcsapi.bus.BusService;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 
 import com.google.inject.Guice;
 import com.google.inject.Inject;
diff --git a/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java b/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java
index ab34456..dcfe139 100644
--- a/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java
+++ b/overdue/src/test/java/org/killbill/billing/overdue/wrapper/TestOverdueWrapper.java
@@ -26,7 +26,7 @@ import org.killbill.billing.account.api.Account;
 import org.killbill.billing.overdue.OverdueState;
 import org.killbill.billing.overdue.OverdueTestSuiteWithEmbeddedDB;
 import org.killbill.billing.overdue.config.OverdueConfig;
-import org.killbill.billing.util.config.catalog.XMLLoader;
+import org.killbill.xmlloader.XMLLoader;
 import org.killbill.billing.junction.DefaultBlockingState;
 
 public class TestOverdueWrapper extends OverdueTestSuiteWithEmbeddedDB {

payment/pom.xml 49(+47 -2)

diff --git a/payment/pom.xml b/payment/pom.xml
index 4c8af7f..8cc8597 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>
@@ -27,6 +27,15 @@
     <name>killbill-payment</name>
     <dependencies>
         <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
             <scope>provided</scope>
@@ -52,6 +61,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>com.ning</groupId>
+            <artifactId>compress-lzf</artifactId>
+        </dependency>
+        <dependency>
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
@@ -83,6 +96,23 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-lifecycle</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-osgi-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
@@ -96,6 +126,14 @@
             <artifactId>killbill-plugin-api-payment</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.billing.plugin</groupId>
+            <artifactId>killbill-plugin-api-retry</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-automaton</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-clock</artifactId>
         </dependency>
@@ -107,6 +145,10 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-concurrent</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-embeddeddb-h2</artifactId>
             <scope>test</scope>
         </dependency>
@@ -126,6 +168,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-xmlloader</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-all</artifactId>
             <scope>test</scope>
@@ -134,7 +180,6 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
-
         <!--  TEST SCOPE -->
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
index 631d2df..f6c86e6 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPayment.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * Groupon 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:
  *
@@ -17,75 +17,86 @@
 package org.killbill.billing.payment.api;
 
 import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.entity.EntityBase;
-import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
-import org.killbill.billing.payment.dao.PaymentModelDao;
-import org.killbill.billing.payment.dao.RefundModelDao;
-import org.killbill.billing.payment.plugin.api.PaymentInfoPlugin;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 
 public class DefaultPayment extends EntityBase implements Payment {
 
     private final UUID accountId;
-    private final UUID invoiceId;
     private final UUID paymentMethodId;
-    private final BigDecimal amount;
-    private final BigDecimal paidAmount;
-    private final Currency currency;
-    private final DateTime effectiveDate;
     private final Integer paymentNumber;
-    private final PaymentStatus paymentStatus;
-    private final List<PaymentAttempt> attempts;
-    private final PaymentInfoPlugin paymentPluginInfo;
-
-    private DefaultPayment(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate, final UUID accountId, final UUID invoiceId,
-                           final UUID paymentMethodId, final BigDecimal amount, final BigDecimal paidAmount, final Currency currency,
-                           final DateTime effectiveDate, final Integer paymentNumber,
-                           final PaymentStatus paymentStatus,
-                           @Nullable final PaymentInfoPlugin paymentPluginInfo,
-                           final List<PaymentAttempt> attempts) {
+    private final String externalKey;
+    private final BigDecimal authAmount;
+    private final BigDecimal captureAmount;
+    private final BigDecimal purchasedAmount;
+    private final BigDecimal creditAmount;
+    private final BigDecimal refundAmount;
+    private final Boolean isVoided;
+
+    private final Currency currency;
+    private final List<PaymentTransaction> transactions;
+
+    public DefaultPayment(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate, final UUID accountId,
+                          final UUID paymentMethodId,
+                          final Integer paymentNumber,
+                          final String externalKey,
+                          final List<PaymentTransaction> transactions) {
         super(id, createdDate, updatedDate);
         this.accountId = accountId;
-        this.invoiceId = invoiceId;
         this.paymentMethodId = paymentMethodId;
-        this.amount = amount;
-        this.paidAmount = paidAmount;
-        this.currency = currency;
-        this.effectiveDate = effectiveDate;
         this.paymentNumber = paymentNumber;
-        this.paymentStatus = paymentStatus;
-        this.attempts = attempts;
-        this.paymentPluginInfo = paymentPluginInfo;
-    }
-
-    public DefaultPayment(final PaymentModelDao src, @Nullable final PaymentInfoPlugin paymentPluginInfo, final List<PaymentAttemptModelDao> attempts, final List<RefundModelDao> refunds) {
-        this(src.getId(),
-             src.getCreatedDate(),
-             src.getUpdatedDate(),
-             src.getAccountId(),
-             src.getInvoiceId(),
-             src.getPaymentMethodId(),
-             src.getAmount(),
-             toPaidAmount(src.getPaymentStatus(), src.getAmount(), refunds),
-             src.getCurrency(),
-             src.getEffectiveDate(),
-             src.getPaymentNumber(),
-             src.getPaymentStatus(),
-             paymentPluginInfo,
-             toPaymentAttempts(attempts));
+        this.externalKey = externalKey;
+        this.transactions = transactions;
+        this.authAmount = getAmountForType(transactions, TransactionType.AUTHORIZE);
+        this.captureAmount = getAmountForType(transactions, TransactionType.CAPTURE);
+        this.purchasedAmount = getAmountForType(transactions, TransactionType.PURCHASE);
+        this.creditAmount = getAmountForType(transactions, TransactionType.CREDIT);
+        this.refundAmount = getAmountForType(transactions, TransactionType.REFUND);
+        this.isVoided = Iterables.filter(transactions, new Predicate<PaymentTransaction>() {
+            @Override
+            public boolean apply(final PaymentTransaction input) {
+                return input.getTransactionType() == TransactionType.VOID && TransactionStatus.SUCCESS.equals(input.getTransactionStatus());
+            }
+        }).iterator().hasNext();
+        this.currency = (transactions != null && !transactions.isEmpty()) ? transactions.get(0).getCurrency() : null;
+    }
+
+    private static BigDecimal getAmountForType(final Iterable<PaymentTransaction> transactions, final TransactionType transactiontype) {
+        BigDecimal result = BigDecimal.ZERO;
+        final Iterable<PaymentTransaction> filtered = Iterables.filter(transactions, new Predicate<PaymentTransaction>() {
+            @Override
+            public boolean apply(final PaymentTransaction input) {
+                return input.getTransactionType() == transactiontype && TransactionStatus.SUCCESS.equals(input.getTransactionStatus());
+            }
+        });
+        if (TransactionType.AUTHORIZE.equals(transactiontype) && filtered.iterator().hasNext()) {
+            // HACK - For multi-step AUTH, don't sum the individual transactions
+            result = filtered.iterator().next().getAmount();
+        } else {
+            for (final PaymentTransaction dpt : filtered) {
+                result = result.add(dpt.getAmount());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public UUID getPaymentMethodId() {
+        return paymentMethodId;
     }
 
     @Override
@@ -94,114 +105,128 @@ public class DefaultPayment extends EntityBase implements Payment {
     }
 
     @Override
-    public UUID getAccountId() {
-        return accountId;
+    public String getExternalKey() {
+        return externalKey;
     }
 
     @Override
-    public UUID getInvoiceId() {
-        return invoiceId;
+    public BigDecimal getAuthAmount() {
+        return authAmount;
     }
 
     @Override
-    public UUID getPaymentMethodId() {
-        return paymentMethodId;
+    public BigDecimal getCapturedAmount() {
+        return captureAmount;
     }
 
     @Override
-    public BigDecimal getAmount() {
-        return amount;
+    public BigDecimal getPurchasedAmount() {
+        return purchasedAmount;
     }
 
     @Override
-    public BigDecimal getPaidAmount() {
-        return paidAmount;
+    public BigDecimal getCreditedAmount() {
+        return creditAmount;
     }
 
     @Override
-    public DateTime getEffectiveDate() {
-        return effectiveDate;
+    public BigDecimal getRefundedAmount() {
+        return refundAmount;
     }
 
     @Override
-    public Currency getCurrency() {
-        return currency;
+    public Boolean isAuthVoided() {
+        return isVoided;
     }
 
     @Override
-    public PaymentStatus getPaymentStatus() {
-        return paymentStatus;
+    public Currency getCurrency() {
+        return currency;
     }
 
     @Override
-    public PaymentInfoPlugin getPaymentInfoPlugin() {
-        return paymentPluginInfo;
+    public List<PaymentTransaction> getTransactions() {
+        return transactions;
     }
 
     @Override
-    public List<PaymentAttempt> getAttempts() {
-        return attempts;
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("DefaultPayment{");
+        sb.append("accountId=").append(accountId);
+        sb.append(", paymentMethodId=").append(paymentMethodId);
+        sb.append(", paymentNumber=").append(paymentNumber);
+        sb.append(", externalKey='").append(externalKey).append('\'');
+        sb.append(", authAmount=").append(authAmount);
+        sb.append(", captureAmount=").append(captureAmount);
+        sb.append(", purchasedAmount=").append(purchasedAmount);
+        sb.append(", refundAmount=").append(refundAmount);
+        sb.append(", currency=").append(currency);
+        sb.append(", transactions=").append(transactions);
+        sb.append('}');
+        return sb.toString();
     }
 
-    private static BigDecimal toPaidAmount(final PaymentStatus paymentStatus, final BigDecimal amount, final Iterable<RefundModelDao> refunds) {
-        if (paymentStatus != PaymentStatus.SUCCESS) {
-            return BigDecimal.ZERO;
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
         }
-
-        BigDecimal result = amount;
-        for (final RefundModelDao cur : refunds) {
-            if (cur.getRefundStatus() == RefundStatus.COMPLETED) {
-                result = result.subtract(cur.getAmount());
-            }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
         }
-        return result;
-    }
 
-    private static List<PaymentAttempt> toPaymentAttempts(final Collection<PaymentAttemptModelDao> attempts) {
-        if (attempts == null || attempts.isEmpty()) {
-            return Collections.emptyList();
+        final DefaultPayment that = (DefaultPayment) o;
+
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (authAmount != null ? authAmount.compareTo(that.authAmount) != 0 : that.authAmount != null) {
+            return false;
+        }
+        if (captureAmount != null ? captureAmount.compareTo(that.captureAmount) != 0 : that.captureAmount != null) {
+            return false;
+        }
+        if (purchasedAmount != null ? purchasedAmount.compareTo(that.purchasedAmount) != 0 : that.purchasedAmount != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
+            return false;
+        }
+        if (paymentMethodId != null ? !paymentMethodId.equals(that.paymentMethodId) : that.paymentMethodId != null) {
+            return false;
+        }
+        if (paymentNumber != null ? !paymentNumber.equals(that.paymentNumber) : that.paymentNumber != null) {
+            return false;
+        }
+        if (refundAmount != null ? refundAmount.compareTo(that.refundAmount) != 0 : that.refundAmount != null) {
+            return false;
+        }
+        if (transactions != null ? !transactions.equals(that.transactions) : that.transactions != null) {
+            return false;
         }
 
-        return new ArrayList<PaymentAttempt>(Collections2.transform(attempts, new Function<PaymentAttemptModelDao, PaymentAttempt>() {
-            @Override
-            public PaymentAttempt apply(final PaymentAttemptModelDao input) {
-                return new PaymentAttempt() {
-                    @Override
-                    public PaymentStatus getPaymentStatus() {
-                        return input.getProcessingStatus();
-                    }
-
-                    @Override
-                    public DateTime getEffectiveDate() {
-                        return input.getEffectiveDate();
-                    }
-
-                    @Override
-                    public UUID getId() {
-                        return input.getId();
-                    }
-
-                    @Override
-                    public DateTime getCreatedDate() {
-                        return input.getCreatedDate();
-                    }
-
-                    @Override
-                    public DateTime getUpdatedDate() {
-                        return input.getUpdatedDate();
-                    }
-
-                    @Override
-                    public String getGatewayErrorCode() {
-                        return input.getGatewayErrorCode();
-                    }
-
-                    @Override
-                    public String getGatewayErrorMsg() {
-                        return input.getGatewayErrorMsg();
-                    }
-                };
-            }
-        }));
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
+        result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
+        result = 31 * result + (paymentNumber != null ? paymentNumber.hashCode() : 0);
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (authAmount != null ? authAmount.hashCode() : 0);
+        result = 31 * result + (captureAmount != null ? captureAmount.hashCode() : 0);
+        result = 31 * result + (purchasedAmount != null ? purchasedAmount.hashCode() : 0);
+        result = 31 * result + (refundAmount != null ? refundAmount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (transactions != null ? transactions.hashCode() : 0);
+        return result;
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
index ef74dbf..3c0b151 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentApi.java
@@ -1,7 +1,8 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,266 +18,420 @@
 package org.killbill.billing.payment.api;
 
 import java.math.BigDecimal;
-import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.clock.Clock;
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.core.PaymentMethodProcessor;
 import org.killbill.billing.payment.core.PaymentProcessor;
-import org.killbill.billing.payment.core.RefundProcessor;
+import org.killbill.billing.payment.core.PluginControlledPaymentProcessor;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.Pagination;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class DefaultPaymentApi implements PaymentApi {
 
-    private final PaymentMethodProcessor methodProcessor;
+    private static final boolean SHOULD_LOCK_ACCOUNT = true;
+    private static final boolean IS_API_PAYMENT = true;
+    private static final UUID NULL_ATTEMPT_ID = null;
+
+    private static final Logger log = LoggerFactory.getLogger(DefaultPaymentApi.class);
+
     private final PaymentProcessor paymentProcessor;
-    private final RefundProcessor refundProcessor;
+    private final PaymentMethodProcessor paymentMethodProcessor;
+    private final PluginControlledPaymentProcessor pluginControlledPaymentProcessor;
     private final InternalCallContextFactory internalCallContextFactory;
-    private final Clock clock;
 
     @Inject
-    public DefaultPaymentApi(final PaymentMethodProcessor methodProcessor,
-                             final PaymentProcessor paymentProcessor,
-                             final RefundProcessor refundProcessor,
-                             final Clock clock,
-                             final InternalCallContextFactory internalCallContextFactory) {
-        this.methodProcessor = methodProcessor;
+    public DefaultPaymentApi(final PaymentProcessor paymentProcessor, final PaymentMethodProcessor paymentMethodProcessor, final PluginControlledPaymentProcessor pluginControlledPaymentProcessor, final InternalCallContextFactory internalCallContextFactory) {
         this.paymentProcessor = paymentProcessor;
-        this.refundProcessor = refundProcessor;
-        this.clock = clock;
+        this.paymentMethodProcessor = paymentMethodProcessor;
+        this.pluginControlledPaymentProcessor = pluginControlledPaymentProcessor;
         this.internalCallContextFactory = internalCallContextFactory;
     }
 
     @Override
-    public Payment createPayment(final Account account, final UUID invoiceId,
-                                 final BigDecimal amount, final CallContext context) throws PaymentApiException {
-        return paymentProcessor.createPayment(account, invoiceId, amount,
-                                              internalCallContextFactory.createInternalCallContext(account.getId(), context), true, false);
-    }
+    public Payment createAuthorization(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
+                                       final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
 
-    @Override
-    public Payment createExternalPayment(final Account account, final UUID invoiceId, final BigDecimal amount, final CallContext context) throws PaymentApiException {
-        return paymentProcessor.createPayment(account, invoiceId, amount,
-                                              internalCallContextFactory.createInternalCallContext(account.getId(), context), true, true);
-    }
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentMethodId, "paymentMethodId");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
 
-    @Override
-    public void notifyPendingPaymentOfStateChanged(final Account account, final UUID paymentId, final boolean isSuccess, final CallContext context) throws PaymentApiException {
-        paymentProcessor.notifyPendingPaymentOfStateChanged(account, paymentId, isSuccess,
-                                                            internalCallContextFactory.createInternalCallContext(account.getId(), context));
-    }
+        logAPICall(TransactionType.AUTHORIZE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
 
-    @Override
-    public Payment retryPayment(final Account account, final UUID paymentId, final CallContext context) throws PaymentApiException {
-        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), context);
-        paymentProcessor.retryPaymentFromApi(paymentId, internalCallContext);
-        return getPayment(paymentId, false, context);
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return paymentProcessor.createAuthorization(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                    SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
     }
 
     @Override
-    public Pagination<Payment> getPayments(final Long offset, final Long limit, final TenantContext context) {
-        return paymentProcessor.getPayments(offset, limit, context, internalCallContextFactory.createInternalTenantContext(context));
+    public Payment createCapture(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentTransactionExternalKey,
+                                 final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentId, "paymentId");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
+
+        logAPICall(TransactionType.CAPTURE.name(), account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return paymentProcessor.createCapture(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                              SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
     }
 
     @Override
-    public Pagination<Payment> getPayments(final Long offset, final Long limit, final String pluginName, final TenantContext tenantContext) throws PaymentApiException {
-        return paymentProcessor.getPayments(offset, limit, pluginName, tenantContext, internalCallContextFactory.createInternalTenantContext(tenantContext));
+    public Payment createPurchase(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
+                                  final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentMethodId, "paymentMethodId");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
+
+        logAPICall(TransactionType.PURCHASE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return paymentProcessor.createPurchase(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                               SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
     }
 
     @Override
-    public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final TenantContext context) throws PaymentApiException {
-        final Payment payment = paymentProcessor.getPayment(paymentId, withPluginInfo, internalCallContextFactory.createInternalTenantContext(context));
-        if (payment == null) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
+    public Payment createPurchaseWithPaymentControl(final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey, final String paymentTransactionExternalKey,
+                                                    final Iterable<PluginProperty> properties, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(paymentExternalKey, "paymentExternalKey");
+        checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
+
+        logAPICall(TransactionType.PURCHASE.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+
+        if (paymentMethodId == null && !paymentOptions.isExternalPayment()) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "paymentMethodId", "should not be null");
         }
-        return payment;
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+
+        final UUID nonNulPaymentMethodId = (paymentMethodId != null) ?
+                                           paymentMethodId :
+                                           paymentMethodProcessor.createOrGetExternalPaymentMethod(UUID.randomUUID().toString(), account, properties, callContext, internalCallContext);
+        return pluginControlledPaymentProcessor.createPurchase(IS_API_PAYMENT, account, nonNulPaymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                                               properties, paymentOptions.getPaymentControlPluginName(), callContext, internalCallContext);
+
     }
 
     @Override
-    public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
-        return paymentProcessor.searchPayments(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+    public Payment createVoid(final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
+                              final CallContext callContext) throws PaymentApiException {
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentId, "paymentId");
+        checkNotNullParameter(properties, "plugin properties");
+
+        logAPICall(TransactionType.VOID.name(), account, null, paymentId, null, null, null, null, paymentTransactionExternalKey);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return paymentProcessor.createVoid(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey,
+                                           SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
     }
 
     @Override
-    public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final String pluginName, final TenantContext context) throws PaymentApiException {
-        return paymentProcessor.searchPayments(searchKey, offset, limit, pluginName, internalCallContextFactory.createInternalTenantContext(context));
+    public Payment createRefund(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, @Nullable final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
+                                final CallContext callContext) throws PaymentApiException {
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(paymentId, "paymentId");
+        checkNotNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
+
+        logAPICall(TransactionType.REFUND.name(), account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return paymentProcessor.createRefund(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                             SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
     }
 
     @Override
-    public Pagination<Refund> getRefunds(final Long offset, final Long limit, final TenantContext context) {
-        return refundProcessor.getRefunds(offset, limit, context, internalCallContextFactory.createInternalTenantContext(context));
+    public Payment createRefundWithPaymentControl(final Account account, final UUID paymentId, @Nullable final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final Iterable<PluginProperty> properties,
+                                                  final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(paymentId, "paymentId");
+        checkNotNullParameter(paymentTransactionExternalKey, "paymentTransactionExternalKey");
+        checkNotNullParameter(properties, "plugin properties");
+        if (amount != null) {
+            checkPositiveAmount(amount);
+        }
+
+        logAPICall(TransactionType.REFUND.name(), account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return pluginControlledPaymentProcessor.createRefund(IS_API_PAYMENT, account, paymentId, amount, currency, paymentTransactionExternalKey,
+                                                             properties, paymentOptions.getPaymentControlPluginName(), callContext, internalCallContext);
+
     }
 
     @Override
-    public Pagination<Refund> getRefunds(final Long offset, final Long limit, final String pluginName, final TenantContext tenantContext) throws PaymentApiException {
-        return refundProcessor.getRefunds(offset, limit, pluginName, tenantContext, internalCallContextFactory.createInternalTenantContext(tenantContext));
+    public Payment createCredit(final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
+                                final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentMethodId, "paymentMethodId");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(properties, "plugin properties");
+        checkPositiveAmount(amount);
+
+        logAPICall(TransactionType.CREDIT.name(), account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return paymentProcessor.createCredit(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentMethodId, paymentId, amount, currency, paymentExternalKey, paymentTransactionExternalKey,
+                                             SHOULD_LOCK_ACCOUNT, properties, callContext, internalCallContext);
+
     }
 
     @Override
-    public Pagination<Refund> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
-        return refundProcessor.searchRefunds(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+    public Payment notifyPendingTransactionOfStateChanged(final Account account, final UUID paymentTransactionId, final boolean isSuccess, final CallContext callContext) throws PaymentApiException {
+
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(paymentTransactionId, "paymentTransactionId");
+
+        logAPICall("NOTIFY_STATE_CHANGE", account, null, null, paymentTransactionId, null, null, null, null);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return paymentProcessor.notifyPendingPaymentOfStateChanged(account, paymentTransactionId, isSuccess, callContext, internalCallContext);
     }
 
     @Override
-    public Pagination<Refund> searchRefunds(final String searchKey, final Long offset, final Long limit, final String pluginName, final TenantContext context) throws PaymentApiException {
-        return refundProcessor.searchRefunds(searchKey, offset, limit, pluginName, internalCallContextFactory.createInternalTenantContext(context));
+    public Payment notifyPendingTransactionOfStateChangedWithPaymentControl(final Account account, final UUID paymentTransactionId, final boolean isSuccess, final PaymentOptions paymentOptions, final CallContext context) throws PaymentApiException {
+        throw new IllegalStateException("Not implemented");
     }
 
     @Override
-    public List<Payment> getInvoicePayments(final UUID invoiceId, final TenantContext context) {
-        return paymentProcessor.getInvoicePayments(invoiceId, internalCallContextFactory.createInternalTenantContext(context));
+    public Payment createChargeback(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final CallContext callContext) throws PaymentApiException {
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(paymentId, "paymentId");
+        checkPositiveAmount(amount);
+
+        logAPICall(TransactionType.CHARGEBACK.name(), account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return paymentProcessor.createChargeback(IS_API_PAYMENT, NULL_ATTEMPT_ID, account, paymentId, paymentTransactionExternalKey, amount, currency, true,
+                                                 callContext, internalCallContext);
+
     }
 
     @Override
-    public List<Payment> getAccountPayments(final UUID accountId, final TenantContext context)
-            throws PaymentApiException {
-        return paymentProcessor.getAccountPayments(accountId, internalCallContextFactory.createInternalTenantContext(context));
+    public Payment createChargebackWithPaymentControl(final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentTransactionExternalKey, final PaymentOptions paymentOptions, final CallContext callContext) throws PaymentApiException {
+        checkNotNullParameter(account, "account");
+        checkNotNullParameter(amount, "amount");
+        checkNotNullParameter(currency, "currency");
+        checkNotNullParameter(paymentId, "paymentId");
+        checkPositiveAmount(amount);
+
+        final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(account.getId(), callContext);
+        return pluginControlledPaymentProcessor.createChargeback(account, paymentId, paymentTransactionExternalKey, amount, currency,
+                                                                 paymentOptions.getPaymentControlPluginName(), callContext, internalCallContext);
     }
 
     @Override
-    public Refund getRefund(final UUID refundId, final boolean withPluginInfo, final TenantContext context) throws PaymentApiException {
-        return refundProcessor.getRefund(refundId, withPluginInfo, internalCallContextFactory.createInternalTenantContext(context));
+    public List<Payment> getAccountPayments(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentApiException {
+        return paymentProcessor.getAccountPayments(accountId, withPluginInfo, tenantContext, internalCallContextFactory.createInternalTenantContext(accountId, tenantContext));
     }
 
     @Override
-    public Refund createRefund(final Account account, final UUID paymentId, final BigDecimal refundAmount, final CallContext context) throws PaymentApiException {
-        if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL);
-        }
-        return refundProcessor.createRefund(account, paymentId, refundAmount, false, ImmutableMap.<UUID, BigDecimal>of(),
-                                            internalCallContextFactory.createInternalCallContext(account.getId(), context));
+    public Pagination<Payment> getPayments(final Long offset, final Long limit, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context) {
+        return paymentProcessor.getPayments(offset, limit, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public void notifyPendingRefundOfStateChanged(final Account account, final UUID refundId, final boolean isSuccess, final CallContext context) throws PaymentApiException {
-        refundProcessor.notifyPendingRefundOfStateChanged(account, refundId, isSuccess,
-                                                          internalCallContextFactory.createInternalCallContext(account.getId(), context));
+    public Pagination<Payment> getPayments(final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentApiException {
+        return paymentProcessor.getPayments(offset, limit, pluginName, withPluginInfo, properties, tenantContext, internalCallContextFactory.createInternalTenantContext(tenantContext));
     }
 
     @Override
-    public Refund createRefundWithAdjustment(final Account account, final UUID paymentId, final BigDecimal refundAmount, final CallContext context) throws PaymentApiException {
-        if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_REFUND_AMOUNT_NEGATIVE_OR_NULL);
+    public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentApiException {
+        final Payment payment = paymentProcessor.getPayment(paymentId, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
+        if (payment == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
         }
-        return refundProcessor.createRefund(account, paymentId, refundAmount, true, ImmutableMap.<UUID, BigDecimal>of(),
-                                            internalCallContextFactory.createInternalCallContext(account.getId(), context));
+        return payment;
     }
 
     @Override
-    public Refund createRefundWithItemsAdjustments(final Account account, final UUID paymentId, final Set<UUID> invoiceItemIds, final CallContext context) throws PaymentApiException {
-        final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts = new HashMap<UUID, BigDecimal>();
-        for (final UUID invoiceItemId : invoiceItemIds) {
-            invoiceItemIdsWithAmounts.put(invoiceItemId, null);
+    public Payment getPaymentByExternalKey(final String paymentExternalKey, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext)
+            throws PaymentApiException {
+        final Payment payment = paymentProcessor.getPaymentByExternalKey(paymentExternalKey, withPluginInfo, properties, tenantContext, internalCallContextFactory.createInternalTenantContext(tenantContext));
+        if (payment == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentExternalKey);
         }
-
-        return refundProcessor.createRefund(account, paymentId, null, true, invoiceItemIdsWithAmounts,
-                                            internalCallContextFactory.createInternalCallContext(account.getId(), context));
+        return payment;
     }
 
     @Override
-    public Refund createRefundWithItemsAdjustments(final Account account, final UUID paymentId, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final CallContext context) throws PaymentApiException {
-        return refundProcessor.createRefund(account, paymentId, null, true, invoiceItemIdsWithAmounts,
-                                            internalCallContextFactory.createInternalCallContext(account.getId(), context));
+    public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context) {
+        return paymentProcessor.searchPayments(searchKey, offset, limit, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public List<Refund> getAccountRefunds(final Account account, final TenantContext context)
-            throws PaymentApiException {
-        return refundProcessor.getAccountRefunds(account, internalCallContextFactory.createInternalTenantContext(context));
+    public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentApiException {
+        return paymentProcessor.searchPayments(searchKey, offset, limit, pluginName, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public List<Refund> getPaymentRefunds(final UUID paymentId, final TenantContext context)
+    public UUID addPaymentMethod(final Account account, final String paymentMethodExternalKey, final String pluginName,
+                                 final boolean setDefault, final PaymentMethodPlugin paymentMethodInfo,
+                                 final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentApiException {
-        return refundProcessor.getPaymentRefunds(paymentId, internalCallContextFactory.createInternalTenantContext(context));
+        return paymentMethodProcessor.addPaymentMethod(paymentMethodExternalKey, pluginName, account, setDefault, paymentMethodInfo, properties,
+                                                       context, internalCallContextFactory.createInternalCallContext(account.getId(), context));
     }
 
     @Override
-    public Set<String> getAvailablePlugins() {
-        return methodProcessor.getAvailablePlugins();
-    }
-
-    @Override
-    public UUID addPaymentMethod(final String pluginName, final Account account,
-                                 final boolean setDefault, final PaymentMethodPlugin paymentMethodInfo, final CallContext context)
+    public List<PaymentMethod> getAccountPaymentMethods(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context)
             throws PaymentApiException {
-        return methodProcessor.addPaymentMethod(pluginName, account, setDefault, paymentMethodInfo,
-                                                internalCallContextFactory.createInternalCallContext(account.getId(), context));
+        return paymentMethodProcessor.getPaymentMethods(accountId, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public List<PaymentMethod> getPaymentMethods(final Account account, final boolean withPluginInfo, final TenantContext context)
+    public PaymentMethod getPaymentMethodById(final UUID paymentMethodId, final boolean includedDeleted, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context)
             throws PaymentApiException {
-        return methodProcessor.getPaymentMethods(account, withPluginInfo, internalCallContextFactory.createInternalTenantContext(context));
+        return paymentMethodProcessor.getPaymentMethodById(paymentMethodId, includedDeleted, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public PaymentMethod getPaymentMethodById(final UUID paymentMethodId, final boolean includedDeleted, final boolean withPluginInfo, final TenantContext context)
+    public PaymentMethod getPaymentMethodByExternalKey(final String paymentMethodExternalKey, final boolean includedInactive, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context)
             throws PaymentApiException {
-        return methodProcessor.getPaymentMethodById(paymentMethodId, includedDeleted, withPluginInfo, internalCallContextFactory.createInternalTenantContext(context));
+        return paymentMethodProcessor.getPaymentMethodByExternalKey(paymentMethodExternalKey, includedInactive, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final TenantContext context) {
-        return methodProcessor.getPaymentMethods(offset, limit, context, internalCallContextFactory.createInternalTenantContext(context));
+    public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context) {
+        return paymentMethodProcessor.getPaymentMethods(offset, limit, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final String pluginName, final TenantContext context) throws PaymentApiException {
-        return methodProcessor.getPaymentMethods(offset, limit, pluginName, context, internalCallContextFactory.createInternalTenantContext(context));
+    public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentApiException {
+        return paymentMethodProcessor.getPaymentMethods(offset, limit, pluginName, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext context) {
-        return methodProcessor.searchPaymentMethods(searchKey, offset, limit, internalCallContextFactory.createInternalTenantContext(context));
+    public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context) {
+        return paymentMethodProcessor.searchPaymentMethods(searchKey, offset, limit, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final String pluginName, final TenantContext context) throws PaymentApiException {
-        return methodProcessor.searchPaymentMethods(searchKey, offset, limit, pluginName, internalCallContextFactory.createInternalTenantContext(context));
+    public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentApiException {
+        return paymentMethodProcessor.searchPaymentMethods(searchKey, offset, limit, pluginName, withPluginInfo, properties, context, internalCallContextFactory.createInternalTenantContext(context));
     }
 
     @Override
-    public void deletedPaymentMethod(final Account account, final UUID paymentMethodId, final boolean deleteDefaultPaymentMethodWithAutoPayOff, final CallContext context)
+    public void deletePaymentMethod(final Account account, final UUID paymentMethodId, final boolean deleteDefaultPaymentMethodWithAutoPayOff, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentApiException {
-        methodProcessor.deletedPaymentMethod(account, paymentMethodId, deleteDefaultPaymentMethodWithAutoPayOff, internalCallContextFactory.createInternalCallContext(account.getId(), context));
+        paymentMethodProcessor.deletedPaymentMethod(account, paymentMethodId, deleteDefaultPaymentMethodWithAutoPayOff, properties, context, internalCallContextFactory.createInternalCallContext(account.getId(), context));
     }
 
     @Override
-    public void setDefaultPaymentMethod(final Account account, final UUID paymentMethodId, final CallContext context)
+    public void setDefaultPaymentMethod(final Account account, final UUID paymentMethodId, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentApiException {
-        methodProcessor.setDefaultPaymentMethod(account, paymentMethodId, internalCallContextFactory.createInternalCallContext(account.getId(), context));
+        paymentMethodProcessor.setDefaultPaymentMethod(account, paymentMethodId, properties, context, internalCallContextFactory.createInternalCallContext(account.getId(), context));
     }
 
     @Override
-    public List<PaymentMethod> refreshPaymentMethods(final String pluginName, final Account account, final CallContext context)
+    public List<PaymentMethod> refreshPaymentMethods(final Account account, final String pluginName, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentApiException {
-        return methodProcessor.refreshPaymentMethods(pluginName, account, internalCallContextFactory.createInternalCallContext(account.getId(), context));
+        return paymentMethodProcessor.refreshPaymentMethods(pluginName, account, properties, context, internalCallContextFactory.createInternalCallContext(account.getId(), context));
     }
 
     @Override
-    public List<PaymentMethod> refreshPaymentMethods(final Account account, final CallContext context)
+    public List<PaymentMethod> refreshPaymentMethods(final Account account, final Iterable<PluginProperty> properties, final CallContext context)
             throws PaymentApiException {
         final InternalCallContext callContext = internalCallContextFactory.createInternalCallContext(account.getId(), context);
 
         final List<PaymentMethod> paymentMethods = new LinkedList<PaymentMethod>();
-        for (final String pluginName : methodProcessor.getAvailablePlugins()) {
-            paymentMethods.addAll(methodProcessor.refreshPaymentMethods(pluginName, account, callContext));
+        for (final String pluginName : paymentMethodProcessor.getAvailablePlugins()) {
+            paymentMethods.addAll(paymentMethodProcessor.refreshPaymentMethods(pluginName, account, properties, context, callContext));
         }
 
         return paymentMethods;
     }
+
+    private void logAPICall(final String transactionType, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final UUID transactionId, @Nullable final BigDecimal amount, @Nullable final Currency currency, @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey) {
+        if (log.isInfoEnabled()) {
+            final StringBuilder logLine = new StringBuilder();
+            logLine.append("PaymentApi : ")
+                   .append(transactionType)
+                   .append(", account = ")
+                   .append(account.getId());
+            if (paymentMethodId != null) {
+                logLine.append(", paymentMethodId = ")
+                       .append(paymentMethodId);
+            }
+            if (paymentExternalKey != null) {
+                logLine.append(", paymentExternalKey = ")
+                       .append(paymentExternalKey);
+            }
+            if (paymentTransactionExternalKey != null) {
+                logLine.append(", paymentTransactionExternalKey = ")
+                       .append(paymentTransactionExternalKey);
+            }
+            if (paymentId != null) {
+                logLine.append(", paymentId = ")
+                       .append(paymentId);
+            }
+            if (transactionId != null) {
+                logLine.append(", transactionId = ")
+                       .append(transactionId);
+            }
+            if (amount != null) {
+                logLine.append(", amount = ")
+                       .append(amount);
+            }
+            if (currency != null) {
+                logLine.append(", currency = ")
+                       .append(currency);
+            }
+            log.info(logLine.toString());
+        }
+    }
+
+    private void checkNotNullParameter(final Object parameter, final String parameterName) throws PaymentApiException {
+        if (parameter == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, parameterName, "should not be null");
+        }
+    }
+
+    private void checkPositiveAmount(final BigDecimal amount) throws PaymentApiException {
+        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "amount", "should be greater than 0");
+        }
+    }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentErrorEvent.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentErrorEvent.java
index fc5c995..d81ebad 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentErrorEvent.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentErrorEvent.java
@@ -29,13 +29,13 @@ public class DefaultPaymentErrorEvent extends BusEventBase implements PaymentErr
 
     private final String message;
     private final UUID accountId;
-    private final UUID invoiceId;
     private final UUID paymentId;
+    private final TransactionType transactionType;
 
     @JsonCreator
     public DefaultPaymentErrorEvent(@JsonProperty("accountId") final UUID accountId,
-                                    @JsonProperty("invoiceId") final UUID invoiceId,
                                     @JsonProperty("paymentId") final UUID paymentId,
+                                    @JsonProperty("transactionType")  final TransactionType transactionType,
                                     @JsonProperty("message") final String message,
                                     @JsonProperty("searchKey1") final Long searchKey1,
                                     @JsonProperty("searchKey2") final Long searchKey2,
@@ -43,26 +43,30 @@ public class DefaultPaymentErrorEvent extends BusEventBase implements PaymentErr
         super(searchKey1, searchKey2, userToken);
         this.message = message;
         this.accountId = accountId;
-        this.invoiceId = invoiceId;
         this.paymentId = paymentId;
+        this.transactionType = transactionType;
     }
 
+    @Override
     public String getMessage() {
         return message;
     }
 
+    @Override
     public UUID getAccountId() {
         return accountId;
     }
 
-    public UUID getInvoiceId() {
-        return invoiceId;
-    }
-
+    @Override
     public UUID getPaymentId() {
         return paymentId;
     }
 
+    @Override
+    public TransactionType getTransactionType() {
+        return transactionType;
+    }
+
     @JsonIgnore
     @Override
     public BusInternalEventType getBusEventType() {
@@ -70,6 +74,17 @@ public class DefaultPaymentErrorEvent extends BusEventBase implements PaymentErr
     }
 
     @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("DefaultPaymentErrorEvent{");
+        sb.append("message='").append(message).append('\'');
+        sb.append(", accountId=").append(accountId);
+        sb.append(", paymentId=").append(paymentId);
+        sb.append(", transactionType=").append(transactionType);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
     public boolean equals(final Object o) {
         if (this == o) {
             return true;
@@ -83,7 +98,7 @@ public class DefaultPaymentErrorEvent extends BusEventBase implements PaymentErr
         if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
         }
-        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+        if (transactionType != null ? !transactionType.equals(that.transactionType) : that.transactionType != null) {
             return false;
         }
         if (message != null ? !message.equals(that.message) : that.message != null) {
@@ -100,7 +115,7 @@ public class DefaultPaymentErrorEvent extends BusEventBase implements PaymentErr
     public int hashCode() {
         int result = message != null ? message.hashCode() : 0;
         result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
-        result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
+        result = 31 * result + (transactionType != null ? transactionType.hashCode() : 0);
         result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
         return result;
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
new file mode 100644
index 0000000..a7ed94c
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentGatewayApi.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.api;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PaymentGatewayApi;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.core.PaymentGatewayProcessor;
+import org.killbill.billing.payment.plugin.api.GatewayNotification;
+import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+
+public class DefaultPaymentGatewayApi implements PaymentGatewayApi {
+
+    private final PaymentGatewayProcessor paymentGatewayProcessor;
+    private final InternalCallContextFactory internalCallContextFactory;
+
+    @Inject
+    public DefaultPaymentGatewayApi(final PaymentGatewayProcessor paymentGatewayProcessor, final InternalCallContextFactory internalCallContextFactory) {
+        this.paymentGatewayProcessor = paymentGatewayProcessor;
+        this.internalCallContextFactory = internalCallContextFactory;
+    }
+
+    @Override
+    public HostedPaymentPageFormDescriptor buildFormDescriptor(final Account account, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
+        return paymentGatewayProcessor.buildFormDescriptor(account, customFields, properties, callContext, internalCallContextFactory.createInternalCallContext(account.getId(), callContext));
+    }
+
+    @Override
+    public GatewayNotification processNotification(final String notification, final String pluginName, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
+        return paymentGatewayProcessor.processNotification(notification, pluginName, properties, callContext);
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentInfoEvent.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentInfoEvent.java
index 561d7b9..d3d73b2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentInfoEvent.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentInfoEvent.java
@@ -20,7 +20,7 @@ import java.math.BigDecimal;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.events.BusEventBase;
 import org.killbill.billing.events.PaymentInfoInternalEvent;
 
@@ -31,20 +31,20 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfoInternalEvent {
 
     private final UUID accountId;
-    private final UUID invoiceId;
     private final UUID paymentId;
     private final BigDecimal amount;
-    private final Integer paymentNumber;
-    private final PaymentStatus status;
+    private final Currency currency;
+    private final TransactionStatus status;
+    private final TransactionType transactionType;
     private final DateTime effectiveDate;
 
     @JsonCreator
     public DefaultPaymentInfoEvent(@JsonProperty("accountId") final UUID accountId,
-                                   @JsonProperty("invoiceId") final UUID invoiceId,
                                    @JsonProperty("paymentId") final UUID paymentId,
                                    @JsonProperty("amount") final BigDecimal amount,
-                                   @JsonProperty("paymentNumber") final Integer paymentNumber,
-                                   @JsonProperty("status") final PaymentStatus status,
+                                   @JsonProperty("currency") final Currency currency,
+                                   @JsonProperty("status") final TransactionStatus status,
+                                   @JsonProperty("transactionType")  final TransactionType transactionType,
                                    @JsonProperty("extFirstPaymentRefId") final String extFirstPaymentRefId /* TODO for backward compatibility only */,
                                    @JsonProperty("extSecondPaymentRefId") final String extSecondPaymentRefId /* TODO for backward compatibility only */,
                                    @JsonProperty("effectiveDate") final DateTime effectiveDate,
@@ -53,22 +53,25 @@ public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfo
                                    @JsonProperty("userToken") final UUID userToken) {
         super(searchKey1, searchKey2, userToken);
         this.accountId = accountId;
-        this.invoiceId = invoiceId;
         this.paymentId = paymentId;
         this.amount = amount;
-        this.paymentNumber = paymentNumber;
+        this.currency = currency;
         this.status = status;
+        this.transactionType = transactionType;
         this.effectiveDate = effectiveDate;
     }
 
-    public DefaultPaymentInfoEvent(final UUID accountId, final UUID invoiceId,
-                                   final UUID paymentId, final BigDecimal amount, final Integer paymentNumber,
-                                   final PaymentStatus status,
+    public DefaultPaymentInfoEvent(final UUID accountId,
+                                   final UUID paymentId,
+                                   final BigDecimal amount,
+                                   final Currency currency,
+                                   final TransactionStatus status,
+                                   final TransactionType transactionType,
                                    final DateTime effectiveDate,
                                    final Long searchKey1,
                                    final Long searchKey2,
                                    final UUID userToken) {
-        this(accountId, invoiceId, paymentId, amount, paymentNumber, status, null, null,
+        this(accountId, paymentId, amount, currency, status, transactionType, null, null,
              effectiveDate, searchKey1, searchKey2, userToken);
     }
 
@@ -78,16 +81,11 @@ public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfo
         return BusInternalEventType.PAYMENT_INFO;
     }
 
-
     @Override
     public UUID getAccountId() {
         return accountId;
     }
 
-    @Override
-    public UUID getInvoiceId() {
-        return invoiceId;
-    }
 
     @Override
     public BigDecimal getAmount() {
@@ -95,6 +93,11 @@ public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfo
     }
 
     @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
     public DateTime getEffectiveDate() {
         return effectiveDate;
     }
@@ -105,12 +108,12 @@ public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfo
     }
 
     @Override
-    public Integer getPaymentNumber() {
-        return paymentNumber;
+    public TransactionType getTransactionType() {
+        return transactionType;
     }
 
     @Override
-    public PaymentStatus getStatus() {
+    public TransactionStatus getStatus() {
         return status;
     }
 
@@ -119,11 +122,11 @@ public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfo
         final StringBuilder sb = new StringBuilder();
         sb.append("DefaultPaymentInfoEvent");
         sb.append("{accountId=").append(accountId);
-        sb.append(", invoiceId=").append(invoiceId);
         sb.append(", paymentId=").append(paymentId);
         sb.append(", amount=").append(amount);
-        sb.append(", paymentNumber=").append(paymentNumber);
+        sb.append(", currency=").append(currency);
         sb.append(", status=").append(status);
+        sb.append(", transactionType=").append(transactionType);
         sb.append(", effectiveDate=").append(effectiveDate);
         sb.append('}');
         return sb.toString();
@@ -139,11 +142,9 @@ public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfo
         result = prime * result
                  + ((effectiveDate == null) ? 0 : effectiveDate.hashCode());
         result = prime * result
-                 + ((invoiceId == null) ? 0 : invoiceId.hashCode());
-        result = prime * result
                  + ((paymentId == null) ? 0 : paymentId.hashCode());
         result = prime * result
-                 + ((paymentNumber == null) ? 0 : paymentNumber.hashCode());
+                 + ((currency == null) ? 0 : currency.hashCode());
         result = prime * result + ((status == null) ? 0 : status.hashCode());
         return result;
     }
@@ -167,6 +168,13 @@ public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfo
         } else if (!accountId.equals(other.accountId)) {
             return false;
         }
+        if (transactionType == null) {
+            if (other.transactionType != null) {
+                return false;
+            }
+        } else if (!transactionType.equals(other.transactionType)) {
+            return false;
+        }
         if (amount == null) {
             if (other.amount != null) {
                 return false;
@@ -181,13 +189,6 @@ public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfo
         } else if (effectiveDate.compareTo(other.effectiveDate) != 0) {
             return false;
         }
-        if (invoiceId == null) {
-            if (other.invoiceId != null) {
-                return false;
-            }
-        } else if (!invoiceId.equals(other.invoiceId)) {
-            return false;
-        }
         if (paymentId == null) {
             if (other.paymentId != null) {
                 return false;
@@ -195,11 +196,11 @@ public class DefaultPaymentInfoEvent extends BusEventBase implements PaymentInfo
         } else if (!paymentId.equals(other.paymentId)) {
             return false;
         }
-        if (paymentNumber == null) {
-            if (other.paymentNumber != null) {
+        if (currency == null) {
+            if (other.currency != null) {
                 return false;
             }
-        } else if (!paymentNumber.equals(other.paymentNumber)) {
+        } else if (!currency.equals(other.currency)) {
             return false;
         }
         if (status != other.status) {
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentMethod.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentMethod.java
index 2cd8ce7..e414f61 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentMethod.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentMethod.java
@@ -27,30 +27,37 @@ import org.killbill.billing.entity.EntityBase;
 
 public class DefaultPaymentMethod extends EntityBase implements PaymentMethod {
 
+    private final String externalKey;
     private final UUID accountId;
     private final Boolean isActive;
     private final String pluginName;
     private final PaymentMethodPlugin pluginDetail;
 
-    public DefaultPaymentMethod(final UUID paymentMethodId, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
+    public DefaultPaymentMethod(final UUID paymentMethodId, final String externalKey, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
                                 final UUID accountId, final Boolean isActive, final String pluginName, @Nullable final PaymentMethodPlugin pluginDetail) {
         super(paymentMethodId, createdDate, updatedDate);
+        this.externalKey = externalKey;
         this.accountId = accountId;
         this.isActive = isActive;
         this.pluginName = pluginName;
         this.pluginDetail = pluginDetail;
     }
 
-    public DefaultPaymentMethod(final UUID accountId, final String pluginName, final PaymentMethodPlugin pluginDetail) {
-        this(UUID.randomUUID(), null, null, accountId, true, pluginName, pluginDetail);
+    public DefaultPaymentMethod(final String externalKey, final UUID accountId, final String pluginName, final PaymentMethodPlugin pluginDetail) {
+        this(UUID.randomUUID(), externalKey, null, null, accountId, true, pluginName, pluginDetail);
     }
 
-    public DefaultPaymentMethod(final UUID paymentMethodId, final UUID accountId, final String pluginName) {
-        this(paymentMethodId, null, null, accountId, true, pluginName, null);
+    public DefaultPaymentMethod(final UUID paymentMethodId, final String externalKey, final UUID accountId, final String pluginName) {
+        this(paymentMethodId, externalKey, null, null, accountId, true, pluginName, null);
     }
 
     public DefaultPaymentMethod(final PaymentMethodModelDao input, @Nullable final PaymentMethodPlugin pluginDetail) {
-        this(input.getId(), input.getCreatedDate(), input.getUpdatedDate(), input.getAccountId(), input.isActive(), input.getPluginName(), pluginDetail);
+        this(input.getId(), input.getExternalKey(), input.getCreatedDate(), input.getUpdatedDate(), input.getAccountId(), input.isActive(), input.getPluginName(), pluginDetail);
+    }
+
+    @Override
+    public String getExternalKey() {
+        return externalKey;
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentPluginErrorEvent.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentPluginErrorEvent.java
index 8bc0220..5ef1ac2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentPluginErrorEvent.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultPaymentPluginErrorEvent.java
@@ -28,13 +28,13 @@ public class DefaultPaymentPluginErrorEvent extends BusEventBase implements Paym
 
     private final String message;
     private final UUID accountId;
-    private final UUID invoiceId;
     private final UUID paymentId;
+    private final TransactionType transactionType;
 
     @JsonCreator
     public DefaultPaymentPluginErrorEvent(@JsonProperty("accountId") final UUID accountId,
-                                          @JsonProperty("invoiceId") final UUID invoiceId,
                                           @JsonProperty("paymentId") final UUID paymentId,
+                                          @JsonProperty("transactionType")  final TransactionType transactionType,
                                           @JsonProperty("message") final String message,
                                           @JsonProperty("searchKey1") final Long searchKey1,
                                           @JsonProperty("searchKey2") final Long searchKey2,
@@ -42,26 +42,30 @@ public class DefaultPaymentPluginErrorEvent extends BusEventBase implements Paym
         super(searchKey1, searchKey2, userToken);
         this.message = message;
         this.accountId = accountId;
-        this.invoiceId = invoiceId;
         this.paymentId = paymentId;
+        this.transactionType = transactionType;
     }
 
+    @Override
     public String getMessage() {
         return message;
     }
 
+    @Override
     public UUID getAccountId() {
         return accountId;
     }
 
-    public UUID getInvoiceId() {
-        return invoiceId;
-    }
-
+    @Override
     public UUID getPaymentId() {
         return paymentId;
     }
 
+    @Override
+    public TransactionType getTransactionType() {
+        return transactionType;
+    }
+
     @JsonIgnore
     @Override
     public BusInternalEventType getBusEventType() {
@@ -82,7 +86,7 @@ public class DefaultPaymentPluginErrorEvent extends BusEventBase implements Paym
         if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
         }
-        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+        if (transactionType != null ? !transactionType.equals(that.transactionType) : that.transactionType != null) {
             return false;
         }
         if (message != null ? !message.equals(that.message) : that.message != null) {
@@ -99,7 +103,7 @@ public class DefaultPaymentPluginErrorEvent extends BusEventBase implements Paym
     public int hashCode() {
         int result = message != null ? message.hashCode() : 0;
         result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
-        result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
+        result = 31 * result + (transactionType != null ? transactionType.hashCode() : 0);
         result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
         return result;
     }
diff --git a/payment/src/main/java/org/killbill/billing/payment/bus/InvoiceHandler.java b/payment/src/main/java/org/killbill/billing/payment/bus/InvoiceHandler.java
index 545dd5a..a5fcee7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/bus/InvoiceHandler.java
+++ b/payment/src/main/java/org/killbill/billing/payment/bus/InvoiceHandler.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,39 +18,56 @@
 
 package org.killbill.billing.payment.bus;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
 
 import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.events.InvoiceCreationInternalEvent;
 import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
+import org.killbill.billing.payment.core.PluginControlledPaymentProcessor;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.CallOrigin;
-import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.UserType;
-import org.killbill.billing.events.InvoiceCreationInternalEvent;
-import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
 
 public class InvoiceHandler {
 
-    private final PaymentProcessor paymentProcessor;
     private final AccountInternalApi accountApi;
     private final InternalCallContextFactory internalCallContextFactory;
+    private final PluginControlledPaymentProcessor pluginControlledPaymentProcessor;
+    private final NonEntityDao nonEntityDao;
+    private final CacheControllerDispatcher controllerDispatcher;
 
     private static final Logger log = LoggerFactory.getLogger(InvoiceHandler.class);
 
     @Inject
     public InvoiceHandler(final AccountInternalApi accountApi,
-                          final PaymentProcessor paymentProcessor,
-                          final InternalCallContextFactory internalCallContextFactory) {
+                          final PluginControlledPaymentProcessor pluginControlledPaymentProcessor,
+                          final NonEntityDao nonEntityDao,
+                          final InternalCallContextFactory internalCallContextFactory,
+                          final CacheControllerDispatcher controllerDispatcher) {
         this.accountApi = accountApi;
-        this.paymentProcessor = paymentProcessor;
         this.internalCallContextFactory = internalCallContextFactory;
+        this.pluginControlledPaymentProcessor = pluginControlledPaymentProcessor;
+        this.nonEntityDao = nonEntityDao;
+        this.controllerDispatcher = controllerDispatcher;
     }
 
     @Subscribe
@@ -61,13 +80,22 @@ public class InvoiceHandler {
         try {
             final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "PaymentRequestProcessor", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
             account = accountApi.getAccountById(event.getAccountId(), internalContext);
-            paymentProcessor.createPayment(account, event.getInvoiceId(), null, internalContext, false, false);
-        } catch (AccountApiException e) {
+
+            final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+            final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, event.getInvoiceId().toString(), false);
+            properties.add(prop1);
+
+            final CallContext callContext = internalContext.toCallContext(nonEntityDao.retrieveIdFromObject(internalContext.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID)));
+
+            final BigDecimal amountToBePaid = null; // We let the plugin compute how much should be paid
+            pluginControlledPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amountToBePaid, account.getCurrency(), UUID.randomUUID().toString(), UUID.randomUUID().toString(),
+                                                            properties, InvoicePaymentControlPluginApi.PLUGIN_NAME, callContext, internalContext);
+        } catch (final AccountApiException e) {
             log.error("Failed to process invoice payment", e);
-        } catch (PaymentApiException e) {
+        } catch (final PaymentApiException e) {
             // Log as error unless:
             if (e.getCode() != ErrorCode.PAYMENT_NULL_INVOICE.getCode() /* Nothing left to be paid */ &&
-                    e.getCode() != ErrorCode.PAYMENT_CREATE_PAYMENT.getCode() /* User payment error */) {
+                e.getCode() != ErrorCode.PAYMENT_CREATE_PAYMENT.getCode() /* User payment error */) {
                 log.error("Failed to process invoice payment {}", e.toString());
             }
         }
diff --git a/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java b/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java
new file mode 100644
index 0000000..0d59ea4
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.control.dao;
+
+import java.math.BigDecimal;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.killbill.billing.catalog.api.Currency;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.tweak.HandleCallback;
+
+public class InvoicePaymentControlDao {
+
+    private final IDBI dbi;
+
+    @Inject
+    public InvoicePaymentControlDao(final IDBI dbi) {
+        this.dbi = dbi;
+    }
+
+    public void insertAutoPayOff(final PluginAutoPayOffModelDao data) {
+        dbi.withHandle(new HandleCallback<Void>() {
+            @Override
+            public Void withHandle(final Handle handle) throws Exception {
+                final String paymentId = data.getPaymentId() != null ? data.getPaymentId().toString() : null;
+                final String paymentMethodId = data.getPaymentMethodId() != null ? data.getPaymentMethodId().toString() : null;
+                handle.execute("insert into _invoice_payment_control_plugin_auto_pay_off " +
+                               "(attempt_id, payment_external_key, transaction_external_key, account_id, plugin_name, payment_id, payment_method_id, amount, currency, created_by, created_date) values " +
+                               "(?,?,?,?,?,?,?,?,?,?,?)",
+                               data.getAttemptId().toString(), data.getPaymentExternalKey(), data.getTransactionExternalKey(), data.getAccountId(), data.getPluginName(), paymentId, paymentMethodId,
+                               data.getAmount(), data.getCurrency(), data.getCreatedBy(), data.getCreatedDate()
+                              );
+                return null;
+            }
+        });
+    }
+
+    public List<PluginAutoPayOffModelDao> getAutoPayOffEntry(final UUID accountId) {
+        return dbi.withHandle(new HandleCallback<List<PluginAutoPayOffModelDao>>() {
+            @Override
+            public List<PluginAutoPayOffModelDao> withHandle(final Handle handle) throws Exception {
+                final List<Map<String, Object>> queryResult = handle.select("select * from _invoice_payment_control_plugin_auto_pay_off where account_id = ? and is_active", accountId.toString());
+                final List<PluginAutoPayOffModelDao> result = new ArrayList<PluginAutoPayOffModelDao>(queryResult.size());
+                for (final Map<String, Object> row : queryResult) {
+
+                    final PluginAutoPayOffModelDao entry = new PluginAutoPayOffModelDao(Long.valueOf(row.get("record_id").toString()),
+                                                                                        UUID.fromString((String) row.get("attempt_id")),
+                                                                                        (String) row.get("payment_external_key"),
+                                                                                        (String) row.get("transaction_external_key"),
+                                                                                        UUID.fromString((String) row.get("account_id")),
+                                                                                        (String) row.get("plugin_name"),
+                                                                                        row.get("payment_id") != null ? UUID.fromString((String) row.get("payment_id")) : null,
+                                                                                        UUID.fromString((String) row.get("payment_method_id")),
+                                                                                        (BigDecimal) row.get("amount"),
+                                                                                        Currency.valueOf((String) row.get("currency")),
+                                                                                        (String) row.get("created_by"),
+                                                                                        getDateTime(row.get("created_date")));
+                    result.add(entry);
+
+                }
+                return result;
+            }
+        });
+    }
+
+    public void removeAutoPayOffEntry(final UUID accountId) {
+        dbi.withHandle(new HandleCallback<Void>() {
+            @Override
+            public Void withHandle(final Handle handle) throws Exception {
+                handle.execute("update _invoice_payment_control_plugin_auto_pay_off set is_active = 0 where account_id = ?", accountId.toString());
+                return null;
+            }
+        });
+    }
+
+    protected DateTime getDateTime(final Object timestamp) throws SQLException {
+        final Timestamp resultStamp = (Timestamp) timestamp;
+        return new DateTime(resultStamp).toDateTime(DateTimeZone.UTC);
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/control/dao/PluginAutoPayOffModelDao.java b/payment/src/main/java/org/killbill/billing/payment/control/dao/PluginAutoPayOffModelDao.java
new file mode 100644
index 0000000..f82b295
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/control/dao/PluginAutoPayOffModelDao.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.control.dao;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.Currency;
+
+public class PluginAutoPayOffModelDao {
+
+    private Long recordId;
+    private UUID attemptId;
+    private String paymentExternalKey;
+    private String transactionExternalKey;
+    private UUID accountId;
+    private String pluginName;
+    private UUID paymentId;
+    private UUID paymentMethodId;
+    private BigDecimal amount;
+    private Currency currency;
+    private String createdBy;
+    private DateTime createdDate;
+
+    public PluginAutoPayOffModelDao() { /* For the DAO mapper */
+    }
+
+    public PluginAutoPayOffModelDao(final UUID attemptId, final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName,
+                                    final UUID paymentId, final UUID paymentMethodId, final BigDecimal amount, final Currency currency, final String createdBy, final DateTime createdDate) {
+        this(-1L, attemptId, paymentExternalKey, transactionExternalKey, accountId, pluginName, paymentId, paymentMethodId, amount, currency, createdBy, createdDate);
+    }
+
+    public PluginAutoPayOffModelDao(final Long recordId, UUID attemptId, final String paymentExternalKey, final String transactionExternalKey, final UUID accountId, final String pluginName,
+                                    final UUID paymentId, final UUID paymentMethodId, final BigDecimal amount, final Currency currency, final String createdBy, final DateTime createdDate) {
+        this.recordId = recordId;
+        this.attemptId = attemptId;
+        this.paymentExternalKey = paymentExternalKey;
+        this.transactionExternalKey = transactionExternalKey;
+        this.accountId = accountId;
+        this.pluginName = pluginName;
+        this.paymentId = paymentId;
+        this.paymentMethodId = paymentMethodId;
+        this.amount = amount;
+        this.currency = currency;
+        this.createdBy = createdBy;
+        this.createdDate = createdDate;
+    }
+
+    public UUID getAttemptId() {
+        return attemptId;
+    }
+
+    public void setAttemptId(final UUID attemptId) {
+        this.attemptId = attemptId;
+    }
+
+    public Long getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(final Long recordId) {
+        this.recordId = recordId;
+    }
+
+    public String getPaymentExternalKey() {
+        return paymentExternalKey;
+    }
+
+    public void setPaymentExternalKey(final String paymentExternalKey) {
+        this.paymentExternalKey = paymentExternalKey;
+    }
+
+    public String getTransactionExternalKey() {
+        return transactionExternalKey;
+    }
+
+    public void setTransactionExternalKey(final String transactionExternalKey) {
+        this.transactionExternalKey = transactionExternalKey;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(final UUID accountId) {
+        this.accountId = accountId;
+    }
+
+    public String getPluginName() {
+        return pluginName;
+    }
+
+    public void setPluginName(final String pluginName) {
+        this.pluginName = pluginName;
+    }
+
+    public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    public void setPaymentId(final UUID paymentId) {
+        this.paymentId = paymentId;
+    }
+
+    public UUID getPaymentMethodId() {
+        return paymentMethodId;
+    }
+
+    public void setPaymentMethodId(final UUID paymentMethodId) {
+        this.paymentMethodId = paymentMethodId;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(final BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    public void setCurrency(final Currency currency) {
+        this.currency = currency;
+    }
+
+    public String getCreatedBy() {
+        return createdBy;
+    }
+
+    public void setCreatedBy(final String createdBy) {
+        this.createdBy = createdBy;
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    public void setCreatedDate(final DateTime createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PluginAutoPayOffModelDao)) {
+            return false;
+        }
+
+        final PluginAutoPayOffModelDao that = (PluginAutoPayOffModelDao) o;
+
+        if (attemptId != null ? !attemptId.equals(that.attemptId) : that.attemptId != null) {
+            return false;
+        }
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
+            return false;
+        }
+        if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
+            return false;
+        }
+        if (createdBy != null ? !createdBy.equals(that.createdBy) : that.createdBy != null) {
+            return false;
+        }
+        if (createdDate != null ? createdDate.compareTo(that.createdDate) == 0 : that.createdDate != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (paymentExternalKey != null ? !paymentExternalKey.equals(that.paymentExternalKey) : that.paymentExternalKey != null) {
+            return false;
+        }
+        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+            return false;
+        }
+        if (paymentMethodId != null ? !paymentMethodId.equals(that.paymentMethodId) : that.paymentMethodId != null) {
+            return false;
+        }
+        if (pluginName != null ? !pluginName.equals(that.pluginName) : that.pluginName != null) {
+            return false;
+        }
+        if (recordId != null ? !recordId.equals(that.recordId) : that.recordId != null) {
+            return false;
+        }
+        if (transactionExternalKey != null ? !transactionExternalKey.equals(that.transactionExternalKey) : that.transactionExternalKey != null) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = recordId != null ? recordId.hashCode() : 0;
+        result = 31 * result + (attemptId != null ? attemptId.hashCode() : 0);
+        result = 31 * result + (paymentExternalKey != null ? paymentExternalKey.hashCode() : 0);
+        result = 31 * result + (transactionExternalKey != null ? transactionExternalKey.hashCode() : 0);
+        result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
+        result = 31 * result + (pluginName != null ? pluginName.hashCode() : 0);
+        result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
+        result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (createdBy != null ? createdBy.hashCode() : 0);
+        result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/control/InvoicePaymentControlPluginApi.java b/payment/src/main/java/org/killbill/billing/payment/control/InvoicePaymentControlPluginApi.java
new file mode 100644
index 0000000..dbfe997
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/control/InvoicePaymentControlPluginApi.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.control;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.control.dao.InvoicePaymentControlDao;
+import org.killbill.billing.payment.control.dao.PluginAutoPayOffModelDao;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
+import org.killbill.billing.payment.retry.DefaultFailureCallResult;
+import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult;
+import org.killbill.billing.retry.plugin.api.FailureCallResult;
+import org.killbill.billing.retry.plugin.api.PaymentControlApiException;
+import org.killbill.billing.retry.plugin.api.PaymentControlContext;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.retry.plugin.api.PriorPaymentControlResult;
+import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.tag.ControlTagType;
+import org.killbill.billing.util.tag.Tag;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public final class InvoicePaymentControlPluginApi implements PaymentControlPluginApi {
+
+    public final static String CREATED_BY = "InvoicePaymentControlPluginApi";
+
+    /* Don't change value String for properties as they are referenced from jaxrs without the constants which are not accessible */
+    public final static String PLUGIN_NAME = "__INVOICE_PAYMENT_CONTROL_PLUGIN__";
+    public static final String PROP_IPCD_INVOICE_ID = "IPCD_INVOICE_ID";
+    public static final String PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY = "IPCD_REFUND_IDS_AMOUNTS";
+    public static final String PROP_IPCD_REFUND_WITH_ADJUSTMENTS = "IPCD_REFUND_WITH_ADJUSTMENTS";
+
+    private final PaymentConfig paymentConfig;
+    private final InvoiceInternalApi invoiceApi;
+    private final TagUserApi tagApi;
+    private final PaymentDao paymentDao;
+    private final InvoicePaymentControlDao controlDao;
+    private final RetryServiceScheduler retryServiceScheduler;
+    private final InternalCallContextFactory internalCallContextFactory;
+    private final Clock clock;
+
+    private final Logger logger = LoggerFactory.getLogger(InvoicePaymentControlPluginApi.class);
+
+    @Inject
+    public InvoicePaymentControlPluginApi(final PaymentConfig paymentConfig, final InvoiceInternalApi invoiceApi, final TagUserApi tagApi, final PaymentDao paymentDao,
+                                          final InvoicePaymentControlDao invoicePaymentControlDao,
+                                          @Named(PaymentModule.RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler,
+                                          final InternalCallContextFactory internalCallContextFactory, final Clock clock) {
+        this.paymentConfig = paymentConfig;
+        this.invoiceApi = invoiceApi;
+        this.tagApi = tagApi;
+        this.paymentDao = paymentDao;
+        this.controlDao = invoicePaymentControlDao;
+        this.retryServiceScheduler = retryServiceScheduler;
+        this.internalCallContextFactory = internalCallContextFactory;
+        this.clock = clock;
+    }
+
+    @Override
+    public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
+
+        final TransactionType transactionType = paymentControlContext.getTransactionType();
+        Preconditions.checkArgument(transactionType == TransactionType.PURCHASE ||
+                                    transactionType == TransactionType.REFUND ||
+                                    transactionType == TransactionType.CHARGEBACK);
+
+        final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
+        switch (transactionType) {
+            case PURCHASE:
+                return getPluginPurchaseResult(paymentControlContext, internalContext);
+            case REFUND:
+                return getPluginRefundResult(paymentControlContext, internalContext);
+            case CHARGEBACK:
+                return new DefaultPriorPaymentControlResult(false, paymentControlContext.getAmount());
+            default:
+                throw new IllegalStateException("Unexpected transactionType " + transactionType);
+        }
+    }
+
+    @Override
+    public void onSuccessCall(final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
+
+        final TransactionType transactionType = paymentControlContext.getTransactionType();
+        Preconditions.checkArgument(transactionType == TransactionType.PURCHASE ||
+                                    transactionType == TransactionType.REFUND ||
+                                    transactionType == TransactionType.CHARGEBACK);
+
+        final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
+        try {
+            final InvoicePayment existingInvoicePayment;
+            switch (transactionType) {
+                case PURCHASE:
+                    final UUID invoiceId = getInvoiceId(paymentControlContext);
+                    existingInvoicePayment = invoiceApi.getInvoicePaymentForAttempt(paymentControlContext.getPaymentId(), internalContext);
+                    if (existingInvoicePayment != null) {
+                        logger.info("onSuccessCall was already completed for payment purchase :" + paymentControlContext.getPaymentId());
+                    } else {
+                        invoiceApi.notifyOfPayment(invoiceId,
+                                                   paymentControlContext.getAmount(),
+                                                   paymentControlContext.getCurrency(),
+                                                   paymentControlContext.getProcessedCurrency(),
+                                                   paymentControlContext.getPaymentId(),
+                                                   paymentControlContext.getCreatedDate(),
+                                                   internalContext);
+                    }
+                    break;
+
+                case REFUND:
+                    existingInvoicePayment = invoiceApi.getInvoicePaymentForRefund(paymentControlContext.getPaymentId(), internalContext);
+                    if (existingInvoicePayment != null) {
+                        logger.info("onSuccessCall was already completed for payment refund :" + paymentControlContext.getPaymentId());
+                    } else {
+                        final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(paymentControlContext.getPluginProperties());
+                        final PluginProperty prop = getPluginProperty(paymentControlContext.getPluginProperties(), PROP_IPCD_REFUND_WITH_ADJUSTMENTS);
+                        final boolean isAdjusted = prop != null ? Boolean.valueOf((String) prop.getValue()) : false;
+                        invoiceApi.createRefund(paymentControlContext.getPaymentId(), paymentControlContext.getAmount(), isAdjusted, idWithAmount, paymentControlContext.getTransactionExternalKey(), internalContext);
+                    }
+                    break;
+
+                case CHARGEBACK:
+                    existingInvoicePayment = invoiceApi.getInvoicePaymentForChargeback(paymentControlContext.getPaymentId(), internalContext);
+                    if (existingInvoicePayment != null) {
+                        logger.info("onSuccessCall was already completed for payment chargeback :" + paymentControlContext.getPaymentId());
+                    } else {
+                        invoiceApi.createChargeback(paymentControlContext.getPaymentId(), paymentControlContext.getProcessedAmount(), paymentControlContext.getProcessedCurrency(), internalContext);
+                    }
+                    break;
+
+                default:
+                    throw new IllegalStateException("Unexpected transactionType " + transactionType);
+            }
+        } catch (InvoiceApiException e) {
+            logger.error("InvoicePaymentControlPluginApi onSuccessCall failed for attemptId = " + paymentControlContext.getAttemptPaymentId() + ", transactionType  = " + transactionType , e);
+        }
+    }
+
+    @Override
+    public FailureCallResult onFailureCall(final PaymentControlContext paymentControlContext) throws
+                                                                                              PaymentControlApiException {
+
+        final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(paymentControlContext.getAccountId(), paymentControlContext);
+        final TransactionType transactionType = paymentControlContext.getTransactionType();
+        switch (transactionType) {
+            case PURCHASE:
+                final DateTime nextRetryDate = computeNextRetryDate(paymentControlContext.getPaymentExternalKey(), paymentControlContext.isApiPayment(), internalContext);
+                return new DefaultFailureCallResult(nextRetryDate);
+            case REFUND:
+            case CHARGEBACK:
+                // We don't retry  REFUND, CHARGEBACK
+                return new DefaultFailureCallResult(null);
+            default:
+                throw new IllegalStateException("Unexpected transactionType " + transactionType);
+        }
+    }
+
+    public void process_AUTO_PAY_OFF_removal(final Account account, final InternalCallContext internalCallContext) {
+        final List<PluginAutoPayOffModelDao> entries = controlDao.getAutoPayOffEntry(account.getId());
+        for (PluginAutoPayOffModelDao cur : entries) {
+            retryServiceScheduler.scheduleRetry(ObjectType.ACCOUNT, account.getId(), cur.getAttemptId(), PLUGIN_NAME, clock.getUTCNow());
+        }
+        controlDao.removeAutoPayOffEntry(account.getId());
+    }
+
+    private UUID getInvoiceId(final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
+        final PluginProperty invoiceProp = getPluginProperty(paymentControlContext.getPluginProperties(), PROP_IPCD_INVOICE_ID);
+        if (invoiceProp == null ||
+            !(invoiceProp.getValue() instanceof String)) {
+            throw new PaymentControlApiException("Need to specify a valid invoiceId in property " + PROP_IPCD_INVOICE_ID);
+        }
+        return UUID.fromString((String) invoiceProp.getValue());
+    }
+
+    private PriorPaymentControlResult getPluginPurchaseResult(final PaymentControlContext paymentControlPluginContext, final InternalCallContext internalContext) throws PaymentControlApiException {
+
+        try {
+            final UUID invoiceId = getInvoiceId(paymentControlPluginContext);
+            final Invoice invoice = rebalanceAndGetInvoice(invoiceId, internalContext);
+            final BigDecimal requestedAmount = validateAndComputePaymentAmount(invoice, paymentControlPluginContext.getAmount(), paymentControlPluginContext.isApiPayment());
+
+            final boolean isAborted = requestedAmount.compareTo(BigDecimal.ZERO) == 0;
+            if (!isAborted && insert_AUTO_PAY_OFF_ifRequired(paymentControlPluginContext, requestedAmount)) {
+                return new DefaultPriorPaymentControlResult(true);
+            }
+
+            if (paymentControlPluginContext.isApiPayment() && isAborted) {
+                throw new PaymentControlApiException("Payment for invoice " + invoice.getId() +
+                                                     " aborted : invoice balance is = " + invoice.getBalance() +
+                                                     ", requested payment amount is = " + paymentControlPluginContext.getAmount());
+            } else {
+                return new DefaultPriorPaymentControlResult(isAborted, requestedAmount);
+            }
+        } catch (InvoiceApiException e) {
+            throw new PaymentControlApiException(e);
+        } catch (IllegalArgumentException e) {
+            throw new PaymentControlApiException(e);
+        }
+    }
+
+    private PriorPaymentControlResult getPluginRefundResult(final PaymentControlContext paymentControlPluginContext, final InternalCallContext internalContext) throws PaymentControlApiException {
+
+        final Map<UUID, BigDecimal> idWithAmount = extractIdsWithAmountFromProperties(paymentControlPluginContext.getPluginProperties());
+        if ((paymentControlPluginContext.getAmount() == null || paymentControlPluginContext.getAmount().compareTo(BigDecimal.ZERO) == 0) &&
+            idWithAmount.size() == 0) {
+            throw new PaymentControlApiException("Refund for payment, key = " + paymentControlPluginContext.getPaymentExternalKey() +
+                                                 " aborted: requested refund amount is = " + paymentControlPluginContext.getAmount());
+        }
+
+        final PaymentModelDao payment = paymentDao.getPayment(paymentControlPluginContext.getPaymentId(), internalContext);
+        if (payment == null) {
+            throw new PaymentControlApiException();
+        }
+        // This will calculate the upper bound on the refund amount based on the invoice items associated with that payment.
+        // Note that we are not checking that other (partial) refund occurred, but if the refund ends up being greater than waht is allowed
+        // the call to the gateway would fail; it would need noce to validate on our side though...
+        final BigDecimal amountToBeRefunded = computeRefundAmount(payment.getId(), paymentControlPluginContext.getAmount(), idWithAmount, internalContext);
+        final boolean isAborted = amountToBeRefunded.compareTo(BigDecimal.ZERO) == 0;
+
+        if (paymentControlPluginContext.isApiPayment() && isAborted) {
+            throw new PaymentControlApiException("Refund for payment " + payment.getId() +
+                                                 " aborted : invoice item sum amount is " + amountToBeRefunded +
+                                                 ", requested refund amount is = " + paymentControlPluginContext.getAmount());
+        } else {
+            return new DefaultPriorPaymentControlResult(isAborted, amountToBeRefunded);
+        }
+    }
+
+    private Map<UUID, BigDecimal> extractIdsWithAmountFromProperties(final Iterable<PluginProperty> properties) {
+        final PluginProperty prop = getPluginProperty(properties, PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY);
+        if (prop == null) {
+            return ImmutableMap.<UUID, BigDecimal>of();
+        }
+        return (Map<UUID, BigDecimal>) prop.getValue();
+    }
+
+    private PluginProperty getPluginProperty(final Iterable<PluginProperty> properties, final String propertyName) {
+        return Iterables.tryFind(properties, new Predicate<PluginProperty>() {
+            @Override
+            public boolean apply(final PluginProperty input) {
+                return input.getKey().equals(propertyName);
+            }
+        }).orNull();
+    }
+
+    private BigDecimal computeRefundAmount(final UUID paymentId, @Nullable final BigDecimal specifiedRefundAmount,
+                                           final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts, final InternalTenantContext context)
+            throws PaymentControlApiException {
+
+        if (invoiceItemIdsWithAmounts.size() == 0) {
+            if (specifiedRefundAmount == null || specifiedRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
+                throw new PaymentControlApiException("You need to specify positive a refund amount");
+            }
+            return specifiedRefundAmount;
+        }
+
+        final List<InvoiceItem> items;
+        try {
+            items = invoiceApi.getInvoiceForPaymentId(paymentId, context).getInvoiceItems();
+
+            BigDecimal amountFromItems = BigDecimal.ZERO;
+            for (final UUID itemId : invoiceItemIdsWithAmounts.keySet()) {
+                final BigDecimal specifiedItemAmount = invoiceItemIdsWithAmounts.get(itemId);
+                final BigDecimal itemAmount = getAmountFromItem(items, itemId);
+                if (specifiedItemAmount != null &&
+                    (specifiedItemAmount.compareTo(BigDecimal.ZERO) <= 0 || specifiedItemAmount.compareTo(itemAmount) > 0)) {
+                    throw new PaymentControlApiException("You need to specify valid invoice item amount ");
+                }
+                amountFromItems = amountFromItems.add(Objects.firstNonNull(specifiedItemAmount, itemAmount));
+            }
+            return amountFromItems;
+        } catch (InvoiceApiException e) {
+            throw new PaymentControlApiException(e);
+        }
+    }
+
+    private BigDecimal getAmountFromItem(final List<InvoiceItem> items, final UUID itemId) throws PaymentControlApiException {
+        for (final InvoiceItem item : items) {
+            if (item.getId().equals(itemId)) {
+                return item.getAmount();
+            }
+        }
+        throw new PaymentControlApiException("Unable to find invoice item for id " + itemId);
+    }
+
+    private DateTime computeNextRetryDate(final String paymentExternalKey, final boolean isApiAPayment, final InternalCallContext internalContext) {
+
+        // Don't retry call that come from API.
+        if (isApiAPayment) {
+            return null;
+        }
+
+        final List<PaymentTransactionModelDao> purchasedTransactions = getPurchasedTransactions(paymentExternalKey, internalContext);
+        if (purchasedTransactions.size() == 0) {
+            return null;
+        }
+        final PaymentTransactionModelDao lastTransaction = purchasedTransactions.get(purchasedTransactions.size() - 1);
+        switch (lastTransaction.getTransactionStatus()) {
+            case PAYMENT_FAILURE:
+                return getNextRetryDateForPaymentFailure(purchasedTransactions);
+
+            case UNKNOWN:
+            case PLUGIN_FAILURE:
+                return getNextRetryDateForPluginFailure(purchasedTransactions);
+
+            default:
+                return null;
+        }
+    }
+
+    private DateTime getNextRetryDateForPaymentFailure(final List<PaymentTransactionModelDao> purchasedTransactions) {
+
+        DateTime result = null;
+        final List<Integer> retryDays = paymentConfig.getPaymentRetryDays();
+        final int attemptsInState = getNumberAttemptsInState(purchasedTransactions, TransactionStatus.PAYMENT_FAILURE);
+        final int retryCount = (attemptsInState - 1) >= 0 ? (attemptsInState - 1) : 0;
+        if (retryCount < retryDays.size()) {
+            int retryInDays;
+            final DateTime nextRetryDate = clock.getUTCNow();
+            try {
+                retryInDays = retryDays.get(retryCount);
+                result = nextRetryDate.plusDays(retryInDays);
+            } catch (NumberFormatException ex) {
+                logger.error("Could not get retry day for retry count {}", retryCount);
+            }
+        }
+        return result;
+    }
+
+    private DateTime getNextRetryDateForPluginFailure(final List<PaymentTransactionModelDao> purchasedTransactions) {
+
+        DateTime result = null;
+        final int attemptsInState = getNumberAttemptsInState(purchasedTransactions, TransactionStatus.PLUGIN_FAILURE);
+        final int retryAttempt = (attemptsInState - 1) >= 0 ? (attemptsInState - 1) : 0;
+
+        if (retryAttempt < paymentConfig.getPluginFailureRetryMaxAttempts()) {
+            int nbSec = paymentConfig.getPluginFailureRetryStart();
+            int remainingAttempts = retryAttempt;
+            while (--remainingAttempts > 0) {
+                nbSec = nbSec * paymentConfig.getPluginFailureRetryMultiplier();
+            }
+            result = clock.getUTCNow().plusSeconds(nbSec);
+        }
+        return result;
+    }
+
+    private int getNumberAttemptsInState(final Collection<PaymentTransactionModelDao> allTransactions, final TransactionStatus... statuses) {
+        if (allTransactions == null || allTransactions.size() == 0) {
+            return 0;
+        }
+        return Collections2.filter(allTransactions, new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao input) {
+                for (final TransactionStatus cur : statuses) {
+                    if (input.getTransactionStatus() == cur) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }).size();
+    }
+
+    private List<PaymentTransactionModelDao> getPurchasedTransactions(final String paymentExternalKey, final InternalCallContext internalContext) {
+        final PaymentModelDao payment = paymentDao.getPaymentByExternalKey(paymentExternalKey, internalContext);
+        if (payment == null) {
+            return Collections.emptyList();
+        }
+        final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(payment.getId(), internalContext);
+        if (transactions == null || transactions.size() == 0) {
+            return Collections.emptyList();
+        }
+        return ImmutableList.copyOf(Iterables.filter(transactions, new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao input) {
+                return input.getTransactionType() == TransactionType.PURCHASE;
+            }
+        }));
+    }
+
+    private Invoice rebalanceAndGetInvoice(final UUID invoiceId, final InternalCallContext context) throws InvoiceApiException {
+        final Invoice invoicePriorRebalancing = invoiceApi.getInvoiceById(invoiceId, context);
+        invoiceApi.consumeExistingCBAOnAccountWithUnpaidInvoices(invoicePriorRebalancing.getAccountId(), context);
+        final Invoice invoice = invoiceApi.getInvoiceById(invoiceId, context);
+        return invoice;
+    }
+
+    private BigDecimal validateAndComputePaymentAmount(final Invoice invoice, @Nullable final BigDecimal inputAmount, final boolean isApiPayment) {
+
+        if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
+            logger.info("Invoice " + invoice.getId() + " has already been paid");
+            return BigDecimal.ZERO;
+        }
+        if (isApiPayment &&
+            inputAmount != null &&
+            invoice.getBalance().compareTo(inputAmount) < 0) {
+            logger.info("Invoice " + invoice.getId() +
+                        " has a balance of " + invoice.getBalance().floatValue() +
+                        " less than retry payment amount of " + inputAmount.floatValue());
+            return BigDecimal.ZERO;
+        }
+        if (inputAmount == null) {
+            return invoice.getBalance();
+        } else {
+            return invoice.getBalance().compareTo(inputAmount) < 0 ? invoice.getBalance() : inputAmount;
+        }
+    }
+
+    private boolean insert_AUTO_PAY_OFF_ifRequired(final PaymentControlContext paymentControlContext, final BigDecimal computedAmount) {
+
+        if (paymentControlContext.isApiPayment() || !isAccountAutoPayOff(paymentControlContext.getAccountId(), paymentControlContext)) {
+            return false;
+        }
+        final PluginAutoPayOffModelDao data = new PluginAutoPayOffModelDao(paymentControlContext.getAttemptPaymentId(), paymentControlContext.getPaymentExternalKey(), paymentControlContext.getTransactionExternalKey(),
+                                                                           paymentControlContext.getAccountId(), PLUGIN_NAME,
+                                                                           paymentControlContext.getPaymentId(), paymentControlContext.getPaymentMethodId(),
+                                                                           computedAmount, paymentControlContext.getCurrency(), CREATED_BY, clock.getUTCNow());
+        controlDao.insertAutoPayOff(data);
+        return true;
+    }
+
+    private boolean isAccountAutoPayOff(final UUID accountId, final CallContext callContext) {
+        final List<Tag> accountTags = tagApi.getTagsForAccount(accountId, false, callContext);
+        return ControlTagType.isAutoPayOff(Collections2.transform(accountTags, new Function<Tag, UUID>() {
+            @Override
+            public UUID apply(final Tag tag) {
+                return tag.getTagDefinitionId();
+            }
+        }));
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/Janitor.java b/payment/src/main/java/org/killbill/billing/payment/core/Janitor.java
new file mode 100644
index 0000000..4a1e516
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/Janitor.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.DefaultCallContext;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.RetryStateMachineHelper;
+import org.killbill.billing.payment.core.sm.RetryablePaymentStateContext;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.CallOrigin;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.clock.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+/**
+ * Takes care of incomplete payment/transactions.
+ */
+public class Janitor {
+
+    private final static Logger log = LoggerFactory.getLogger(Janitor.class);
+
+    private final static int TERMINATION_TIMEOUT_SEC = 5;
+
+    private final ScheduledExecutorService janitorExecutor;
+    private final AccountInternalApi accountInternalApi;
+    private final PaymentDao paymentDao;
+    private final Clock clock;
+    private final PaymentConfig paymentConfig;
+    private final InternalCallContextFactory internalCallContextFactory;
+    private final NonEntityDao nonEntityDao;
+    private final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner;
+    private final RetryStateMachineHelper retrySMHelper;
+    private final CacheControllerDispatcher controllerDispatcher;
+
+    private volatile boolean isStopped;
+
+    @Inject
+    public Janitor(final AccountInternalApi accountInternalApi,
+                   final PaymentDao paymentDao,
+                   final PaymentConfig paymentConfig,
+                   final Clock clock,
+                   final NonEntityDao nonEntityDao,
+                   final InternalCallContextFactory internalCallContextFactory,
+                   final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner,
+                   @Named(PaymentModule.JANITOR_EXECUTOR_NAMED) final ScheduledExecutorService janitorExecutor,
+                   final RetryStateMachineHelper retrySMHelper,
+                   final CacheControllerDispatcher controllerDispatcher) {
+        this.accountInternalApi = accountInternalApi;
+        this.paymentDao = paymentDao;
+        this.clock = clock;
+        this.paymentConfig = paymentConfig;
+        this.janitorExecutor = janitorExecutor;
+        this.nonEntityDao = nonEntityDao;
+        this.internalCallContextFactory = internalCallContextFactory;
+        this.pluginControlledPaymentAutomatonRunner = pluginControlledPaymentAutomatonRunner;
+        this.retrySMHelper = retrySMHelper;
+        this.controllerDispatcher = controllerDispatcher;
+    }
+
+    public void start() {
+        isStopped = false;
+        // Start task for removing old pending payments.
+        final TimeUnit pendingRateUnit = paymentConfig.getJanitorRunningRate().getUnit();
+        final long pendingPeriod = paymentConfig.getJanitorRunningRate().getPeriod();
+        janitorExecutor.scheduleAtFixedRate(new PendingTransactionTask(), pendingPeriod, pendingPeriod, pendingRateUnit);
+
+        // Start task for completing incomplete payment attempts
+        final TimeUnit attemptCompletionRateUnit = paymentConfig.getJanitorRunningRate().getUnit();
+        final long attemptCompletionPeriod = paymentConfig.getJanitorRunningRate().getPeriod();
+        janitorExecutor.scheduleAtFixedRate(new AttemptCompletionTask(), attemptCompletionPeriod, attemptCompletionPeriod, attemptCompletionRateUnit);
+    }
+
+    public void stop() {
+        isStopped = true;
+        try {
+            /* Previously submitted tasks will be executed with shutdown(); when task executes as a result of shutdown being called
+             * or because it was already in its execution loop, it will check for the volatile boolean isStopped flag and
+             * return immediately.
+             * Then, awaitTermination with a timeout is required to ensure tasks completed.
+             */
+            janitorExecutor.shutdown();
+            boolean success = janitorExecutor.awaitTermination(TERMINATION_TIMEOUT_SEC, TimeUnit.SECONDS);
+            if (!success) {
+                log.warn("Janitor failed to complete termination within " + TERMINATION_TIMEOUT_SEC + "sec");
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.warn("Janitor stop sequence got interrupted");
+        }
+    }
+
+    protected abstract class CompletionTaskBase<T> implements Runnable {
+
+        private final String taskName;
+        protected final InternalCallContext fakeCallContext;
+
+        protected CompletionTaskBase() {
+            this.taskName = this.getClass().getName();
+            this.fakeCallContext = internalCallContextFactory.createInternalCallContext((Long) null, (Long) null, taskName, CallOrigin.INTERNAL, UserType.SYSTEM, UUID.randomUUID());
+        }
+
+        @Override
+        public void run() {
+
+            if (isStopped) {
+                log.info("Janitor Task " + taskName + " was requested to stop");
+                return;
+            }
+            final List<T> items = getItemsForIteration();
+            for (T item : items) {
+                if (isStopped) {
+                    log.info("Janitor Task " + taskName + " was requested to stop");
+                    return;
+                }
+                doIteration(item);
+            }
+        }
+
+        public abstract List<T> getItemsForIteration();
+
+        public abstract void doIteration(final T item);
+
+        protected DateTime getCreatedDateBefore() {
+            final long delayBeforeNowMs = paymentConfig.getJanitorPendingCleanupTime().getMillis();
+            return clock.getUTCNow().minusMillis((int) delayBeforeNowMs);
+        }
+    }
+
+    /**
+     * Task to find old PENDING transactions and move them into
+     */
+    private final class PendingTransactionTask extends CompletionTaskBase<Integer> {
+
+        private final List<Integer> itemsForIterations;
+
+        private PendingTransactionTask() {
+            super();
+            this.itemsForIterations = ImmutableList.of(new Integer(1));
+        }
+
+        @Override
+        public List<Integer> getItemsForIteration() {
+            return itemsForIterations;
+        }
+
+        @Override
+        public void doIteration(final Integer item) {
+            int result = paymentDao.failOldPendingTransactions(TransactionStatus.PLUGIN_FAILURE, getCreatedDateBefore(), fakeCallContext);
+            if (result > 0) {
+                log.info("Janitor PendingTransactionTask moved " + result + " PENDING payments ->  PLUGIN_FAILURE");
+            }
+        }
+    }
+
+    /**
+     * Task to complete 'partially' incomplete attempts
+     * <p/>
+     * If the state of the transaction associated with the attempt completed, but the attempt state machine did not,
+     * we rerun the retry state machine to complete the call and transition the attempt into a terminal state.
+     */
+    private final class AttemptCompletionTask extends CompletionTaskBase<PaymentAttemptModelDao> {
+
+        private AttemptCompletionTask() {
+            super();
+        }
+
+        @Override
+        public List<PaymentAttemptModelDao> getItemsForIteration() {
+            final List<PaymentAttemptModelDao> incompleteAttempts = paymentDao.getPaymentAttemptsByState(retrySMHelper.getInitialState().getName(), getCreatedDateBefore(), fakeCallContext);
+            log.info("Janitor AttemptCompletionTask start run : found " + incompleteAttempts.size() + " incomplete attempts");
+            return incompleteAttempts;
+        }
+
+        @Override
+        public void doIteration(final PaymentAttemptModelDao attempt) {
+            // STEPH seems a bit insane??
+            final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(attempt.getAccountId(), attempt.getId(), ObjectType.PAYMENT_ATTEMPT);
+            final UUID tenantId = nonEntityDao.retrieveIdFromObject(tenantContext.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+            final CallContext callContext = new DefaultCallContext(tenantId, "AttemptCompletionJanitorTask", CallOrigin.INTERNAL, UserType.SYSTEM, UUID.randomUUID(), clock);
+            final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(attempt.getAccountId(), callContext);
+
+            final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(attempt.getTransactionExternalKey(), tenantContext);
+            final PaymentTransactionModelDao transaction = Iterables.tryFind(transactions, new Predicate<PaymentTransactionModelDao>() {
+                @Override
+                public boolean apply(final PaymentTransactionModelDao input) {
+                    return input.getAttemptId().equals(attempt.getId()) &&
+                           input.getTransactionStatus() == TransactionStatus.SUCCESS;
+                }
+            }).orNull();
+
+            if (transaction == null) {
+                log.info("Janitor AttemptCompletionTask moving attempt " + attempt.getId() + " -> ABORTED");
+                paymentDao.updatePaymentAttempt(attempt.getId(), attempt.getTransactionId(), "ABORTED", internalCallContext);
+                return;
+            }
+
+            try {
+                log.info("Janitor AttemptCompletionTask completing attempt " + attempt.getId() + " -> SUCCESS");
+
+                final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
+                final boolean isApiPayment = true; // unclear
+                final RetryablePaymentStateContext paymentStateContext = new RetryablePaymentStateContext(attempt.getPluginName(),
+                                                                                                          isApiPayment,
+                                                                                                          transaction.getPaymentId(),
+                                                                                                          attempt.getPaymentExternalKey(),
+                                                                                                          transaction.getTransactionExternalKey(),
+                                                                                                          transaction.getTransactionType(),
+                                                                                                          account,
+                                                                                                          attempt.getPaymentMethodId(),
+                                                                                                          transaction.getAmount(),
+                                                                                                          transaction.getCurrency(),
+                                                                                                          PluginPropertySerializer.deserialize(attempt.getPluginProperties()),
+                                                                                                          internalCallContext,
+                                                                                                          callContext);
+
+                paymentStateContext.setAttemptId(attempt.getId()); // Normally set by leavingState Callback
+                paymentStateContext.setPaymentTransactionModelDao(transaction); // Normally set by raw state machine
+                //
+                // Will rerun the state machine with special callbacks to only make the onCompletion call
+                // to the PaymentControlPluginApi plugin and transition the state.
+                //
+                pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
+            } catch (AccountApiException e) {
+                log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+            } catch (PluginPropertySerializerException e) {
+                log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+            } catch (PaymentApiException e) {
+                log.warn("Janitor AttemptCompletionTask failed to complete payment attempt " + attempt.getId(), e);
+            }
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
new file mode 100644
index 0000000..37ad942
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentGatewayProcessor.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core;
+
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.billing.payment.plugin.api.GatewayNotification;
+import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.LockFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+import com.google.inject.name.Named;
+
+import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+
+public class PaymentGatewayProcessor extends ProcessorBase {
+
+    private final PluginDispatcher<HostedPaymentPageFormDescriptor> paymentPluginFormDispatcher;
+    private final PluginDispatcher<GatewayNotification> paymentPluginNotificationDispatcher;
+
+    private static final Logger log = LoggerFactory.getLogger(PaymentGatewayProcessor.class);
+
+    @Inject
+    public PaymentGatewayProcessor(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
+                                   final AccountInternalApi accountUserApi,
+                                   final InvoiceInternalApi invoiceApi,
+                                   final TagInternalApi tagUserApi,
+                                   final PaymentDao paymentDao,
+                                   final NonEntityDao nonEntityDao,
+                                   final GlobalLocker locker,
+                                   final PaymentConfig paymentConfig,
+                                   @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                                   final Clock clock,
+                                   final CacheControllerDispatcher controllerDispatcher) {
+        super(pluginRegistry, accountUserApi, paymentDao, nonEntityDao, tagUserApi, locker, executor, invoiceApi, clock, controllerDispatcher);
+        final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
+        this.paymentPluginFormDispatcher = new PluginDispatcher<HostedPaymentPageFormDescriptor>(paymentPluginTimeoutSec, executor);
+        this.paymentPluginNotificationDispatcher = new PluginDispatcher<GatewayNotification>(paymentPluginTimeoutSec, executor);
+    }
+
+    public GatewayNotification processNotification(final String notification, final String pluginName, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentApiException {
+        return dispatchWithExceptionHandling(null,
+                                             new Callable<PluginDispatcherReturnType<GatewayNotification>>() {
+                                                 @Override
+                                                 public PluginDispatcherReturnType<GatewayNotification> call() throws PaymentApiException {
+                                                     final PaymentPluginApi plugin = getPaymentPluginApi(pluginName);
+                                                     try {
+                                                         final GatewayNotification result = plugin.processNotification(notification, properties, callContext);
+                                                         return PluginDispatcher.createPluginDispatcherReturnType(result);
+                                                     } catch (PaymentPluginApiException e) {
+                                                         throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
+                                                     }
+                                                 }
+                                             }, paymentPluginNotificationDispatcher);
+    }
+
+
+
+    public HostedPaymentPageFormDescriptor buildFormDescriptor(final Account account, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return dispatchWithExceptionHandling(account,
+                                             new CallableWithAccountLock<HostedPaymentPageFormDescriptor, PaymentApiException>(locker,
+                                                                                                                               account.getExternalKey(),
+                                                                                                                               new WithAccountLockCallback<PluginDispatcherReturnType<HostedPaymentPageFormDescriptor>, PaymentApiException>() {
+                                                                                                                                   @Override
+                                                                                                                                   public PluginDispatcherReturnType<HostedPaymentPageFormDescriptor> doOperation() throws PaymentApiException {
+                                                                                                                                       final PaymentPluginApi plugin = getPaymentProviderPlugin(account, internalCallContext);
+
+                                                                                                                                       try {
+                                                                                                                                           final HostedPaymentPageFormDescriptor result = plugin.buildFormDescriptor(account.getId(), customFields, properties, callContext);
+                                                                                                                                           return PluginDispatcher.createPluginDispatcherReturnType(result);
+                                                                                                                                       } catch (final RuntimeException e) {
+                                                                                                                                           throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+                                                                                                                                       } catch (final PaymentPluginApiException e) {
+                                                                                                                                           throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
+                                                                                                                                       }
+                                                                                                                                   }
+                                                                                                                               }),
+                                             paymentPluginFormDispatcher);
+    }
+
+    private static <ReturnType> ReturnType dispatchWithExceptionHandling(@Nullable final Account account, final Callable<PluginDispatcherReturnType<ReturnType>> callable, PluginDispatcher<ReturnType> pluginFormDispatcher) throws PaymentApiException {
+        final UUID accountId = account != null ? account.getId() : null;
+        final String accountExternalKey = account != null ? account.getExternalKey() : "";
+        try {
+            return pluginFormDispatcher.dispatchWithTimeout(callable);
+        } catch (final TimeoutException e) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_TIMEOUT, accountId, null);
+        } catch (final InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+        } catch (final ExecutionException e) {
+            if (e.getCause() instanceof PaymentApiException) {
+                throw (PaymentApiException) e.getCause();
+            } else if (e.getCause() instanceof LockFailedException) {
+                final String format = String.format("Failed to lock account %s", accountExternalKey);
+                log.error(String.format(format), e);
+                throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, format);
+            } else {
+                throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+            }
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
index 81dba8a..5dc84f1 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentMethodProcessor.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,6 +19,7 @@
 package org.killbill.billing.payment.core;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
@@ -24,27 +27,23 @@ import java.util.concurrent.ExecutorService;
 
 import javax.annotation.Nullable;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ErrorCode;
-import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.bus.api.PersistentBus;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.DefaultPaymentMethod;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PaymentMethod;
-import org.killbill.billing.payment.api.PaymentMethodKVInfo;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentMethodModelDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.payment.plugin.api.PaymentMethodInfoPlugin;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
@@ -52,13 +51,20 @@ import org.killbill.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
 import org.killbill.billing.payment.provider.DefaultPaymentMethodInfoPlugin;
 import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
 import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.EntityPaginationBuilder;
 import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
+import com.google.common.base.Objects;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
@@ -76,75 +82,91 @@ public class PaymentMethodProcessor extends ProcessorBase {
     public PaymentMethodProcessor(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
                                   final AccountInternalApi accountInternalApi,
                                   final InvoiceInternalApi invoiceApi,
-                                  final PersistentBus eventBus,
                                   final PaymentDao paymentDao,
                                   final NonEntityDao nonEntityDao,
                                   final TagInternalApi tagUserApi,
                                   final GlobalLocker locker,
-                                  @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor) {
-        super(pluginRegistry, accountInternalApi, eventBus, paymentDao, nonEntityDao, tagUserApi, locker, executor, invoiceApi);
+                                  @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                                  final Clock clock,
+                                  final CacheControllerDispatcher controllerDispatcher) {
+        super(pluginRegistry, accountInternalApi, paymentDao, nonEntityDao, tagUserApi, locker, executor, invoiceApi, clock, controllerDispatcher);
     }
 
-    public UUID addPaymentMethod(final String paymentPluginServiceName, final Account account,
-                                 final boolean setDefault, final PaymentMethodPlugin paymentMethodProps, final InternalCallContext context)
+    public UUID addPaymentMethod(final String paymentMethodExternalKey, final String paymentPluginServiceName, final Account account,
+                                 final boolean setDefault, final PaymentMethodPlugin paymentMethodProps,
+                                 final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context)
             throws PaymentApiException {
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
-
-        return new WithAccountLock<UUID>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<UUID>() {
-
-            @Override
-            public UUID doOperation() throws PaymentApiException {
-                PaymentMethod pm = null;
-                PaymentPluginApi pluginApi = null;
-                try {
-                    pluginApi = getPaymentPluginApi(paymentPluginServiceName);
-                    pm = new DefaultPaymentMethod(account.getId(), paymentPluginServiceName, paymentMethodProps);
-                    pluginApi.addPaymentMethod(account.getId(), pm.getId(), paymentMethodProps, setDefault, context.toCallContext(tenantId));
-                    final PaymentMethodModelDao pmModel = new PaymentMethodModelDao(pm.getId(), pm.getCreatedDate(), pm.getUpdatedDate(),
-                                                                                    pm.getAccountId(), pm.getPluginName(), pm.isActive());
-                    paymentDao.insertPaymentMethod(pmModel, context);
-
-                    if (setDefault) {
-                        accountInternalApi.updatePaymentMethod(account.getId(), pm.getId(), context);
+        try {
+            final PluginDispatcherReturnType<UUID> result = new WithAccountLock<UUID, PaymentApiException>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<PluginDispatcherReturnType<UUID>, PaymentApiException>() {
+
+                @Override
+                public PluginDispatcherReturnType<UUID> doOperation() throws PaymentApiException {
+                    PaymentMethod pm = null;
+                    final PaymentPluginApi pluginApi;
+                    try {
+                        pluginApi = getPaymentPluginApi(paymentPluginServiceName);
+                        pm = new DefaultPaymentMethod(paymentMethodExternalKey, account.getId(), paymentPluginServiceName, paymentMethodProps);
+                        pluginApi.addPaymentMethod(account.getId(), pm.getId(), paymentMethodProps, setDefault, properties, callContext);
+                        final PaymentMethodModelDao pmModel = new PaymentMethodModelDao(pm.getId(), pm.getExternalKey(), pm.getCreatedDate(), pm.getUpdatedDate(),
+                                                                                        pm.getAccountId(), pm.getPluginName(), pm.isActive());
+                        paymentDao.insertPaymentMethod(pmModel, context);
+
+                        if (setDefault) {
+                            accountInternalApi.updatePaymentMethod(account.getId(), pm.getId(), context);
+                        }
+                    } catch (final PaymentPluginApiException e) {
+                        log.warn("Error adding payment method " + pm.getId() + " for plugin " + paymentPluginServiceName, e);
+                        throw new PaymentApiException(ErrorCode.PAYMENT_ADD_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
+                    } catch (final AccountApiException e) {
+                        throw new PaymentApiException(e);
                     }
-                } catch (PaymentPluginApiException e) {
-                    log.warn("Error adding payment method " + pm.getId() + " for plugin " + paymentPluginServiceName, e);
-                    // STEPH all errors should also take a pluginName
-                    throw new PaymentApiException(ErrorCode.PAYMENT_ADD_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
-                } catch (AccountApiException e) {
-                    throw new PaymentApiException(e);
+                    return PluginDispatcher.createPluginDispatcherReturnType(pm.getId());
                 }
-                return pm.getId();
-            }
-        });
+            });
+            return result.getReturnType();
+        } catch (final Exception e) {
+            throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+        }
     }
 
-    public List<PaymentMethod> getPaymentMethods(final Account account, final boolean withPluginInfo, final InternalTenantContext context) throws PaymentApiException {
+    public List<PaymentMethod> getPaymentMethods(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final InternalTenantContext context) throws PaymentApiException {
+        return getPaymentMethods(accountId, withPluginInfo, properties, buildTenantContext(context), context);
+    }
 
-        final List<PaymentMethodModelDao> paymentMethodModels = paymentDao.getPaymentMethods(account.getId(), context);
-        if (paymentMethodModels.size() == 0) {
+    public List<PaymentMethod> getPaymentMethods(final UUID accountId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
+        final List<PaymentMethodModelDao> paymentMethodModels = paymentDao.getPaymentMethods(accountId, context);
+        if (paymentMethodModels.isEmpty()) {
             return Collections.emptyList();
         }
-        return getPaymentMethodInternal(paymentMethodModels, withPluginInfo, context);
+        return getPaymentMethodInternal(paymentMethodModels, withPluginInfo, properties, tenantContext, context);
     }
 
-    public PaymentMethod getPaymentMethodById(final UUID paymentMethodId, final boolean includedDeleted, final boolean withPluginInfo, final InternalTenantContext context)
+    public PaymentMethod getPaymentMethodById(final UUID paymentMethodId, final boolean includedDeleted, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context)
             throws PaymentApiException {
         final PaymentMethodModelDao paymentMethodModel = includedDeleted ? paymentDao.getPaymentMethodIncludedDeleted(paymentMethodId, context) : paymentDao.getPaymentMethod(paymentMethodId, context);
         if (paymentMethodModel == null) {
             throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
         }
 
-        return buildDefaultPaymentMethod(paymentMethodModel, withPluginInfo, context);
+        return buildDefaultPaymentMethod(paymentMethodModel, withPluginInfo, properties, tenantContext, context);
+    }
+
+    public PaymentMethod getPaymentMethodByExternalKey(final String paymentMethodExternalKey, final boolean includedDeleted, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context)
+            throws PaymentApiException {
+        final PaymentMethodModelDao paymentMethodModel = includedDeleted ? paymentDao.getPaymentMethodByExternalKeyIncludedDeleted(paymentMethodExternalKey, context) : paymentDao.getPaymentMethodByExternalKey(paymentMethodExternalKey, context);
+        if (paymentMethodModel == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodExternalKey);
+        }
+        return buildDefaultPaymentMethod(paymentMethodModel, withPluginInfo, properties, tenantContext, context);
     }
 
-    private PaymentMethod buildDefaultPaymentMethod(final PaymentMethodModelDao paymentMethodModelDao, final boolean withPluginInfo, final InternalTenantContext context) throws PaymentApiException {
+    private PaymentMethod buildDefaultPaymentMethod(final PaymentMethodModelDao paymentMethodModelDao, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
         final PaymentMethodPlugin paymentMethodPlugin;
         if (withPluginInfo) {
             try {
                 final PaymentPluginApi pluginApi = getPaymentPluginApi(paymentMethodModelDao.getPluginName());
-                paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), buildTenantContext(context));
-            } catch (PaymentPluginApiException e) {
+                paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), properties, tenantContext);
+            } catch (final PaymentPluginApiException e) {
                 log.warn("Error retrieving payment method " + paymentMethodModelDao.getId() + " from plugin " + paymentMethodModelDao.getPluginName(), e);
                 throw new PaymentApiException(ErrorCode.PAYMENT_GET_PAYMENT_METHODS, paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId());
             }
@@ -155,20 +177,21 @@ public class PaymentMethodProcessor extends ProcessorBase {
         return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
     }
 
-    public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) {
+    public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) {
         return getEntityPaginationFromPlugins(getAvailablePlugins(),
                                               offset,
                                               limit,
                                               new EntityPaginationBuilder<PaymentMethod, PaymentApiException>() {
                                                   @Override
                                                   public Pagination<PaymentMethod> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
-                                                      return getPaymentMethods(offset, limit, pluginName, tenantContext, internalTenantContext);
+                                                      return getPaymentMethods(offset, limit, pluginName, withPluginInfo, properties, tenantContext, internalTenantContext);
                                                   }
-                                              });
+                                              }
+                                             );
     }
 
-    public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final String pluginName, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
-        final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
+    public Pagination<PaymentMethod> getPaymentMethods(final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+        final PaymentPluginApi pluginApi = withPluginInfo ? getPaymentPluginApi(pluginName) : null;
 
         return getEntityPagination(limit,
                                    new SourcePaginationBuilder<PaymentMethodModelDao, PaymentApiException>() {
@@ -182,11 +205,13 @@ public class PaymentMethodProcessor extends ProcessorBase {
                                        @Override
                                        public PaymentMethod apply(final PaymentMethodModelDao paymentMethodModelDao) {
                                            PaymentMethodPlugin paymentMethodPlugin = null;
-                                           try {
-                                               paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), tenantContext);
-                                           } catch (final PaymentPluginApiException e) {
-                                               log.warn("Unable to find payment method id " + paymentMethodModelDao.getId() + " in plugin " + pluginName);
-                                               // We still want to return a payment method object, even though the plugin details are missing
+                                           if (pluginApi != null) {
+                                               try {
+                                                   paymentMethodPlugin = pluginApi.getPaymentMethodDetail(paymentMethodModelDao.getAccountId(), paymentMethodModelDao.getId(), properties, tenantContext);
+                                               } catch (final PaymentPluginApiException e) {
+                                                   log.warn("Unable to find payment method id " + paymentMethodModelDao.getId() + " in plugin " + pluginName);
+                                                   // We still want to return a payment method object, even though the plugin details are missing
+                                               }
                                            }
 
                                            return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
@@ -195,154 +220,183 @@ public class PaymentMethodProcessor extends ProcessorBase {
                                   );
     }
 
-    public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final InternalTenantContext internalTenantContext) {
+    public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) {
         return getEntityPaginationFromPlugins(getAvailablePlugins(),
                                               offset,
                                               limit,
                                               new EntityPaginationBuilder<PaymentMethod, PaymentApiException>() {
                                                   @Override
                                                   public Pagination<PaymentMethod> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
-                                                      return searchPaymentMethods(searchKey, offset, limit, pluginName, internalTenantContext);
+                                                      return searchPaymentMethods(searchKey, offset, limit, pluginName, withPluginInfo, properties, tenantContext, internalTenantContext);
                                                   }
-                                              });
+                                              }
+                                             );
     }
 
-    public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final String pluginName, final InternalTenantContext internalTenantContext) throws PaymentApiException {
-        final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
-
-        return getEntityPagination(limit,
-                                   new SourcePaginationBuilder<PaymentMethodPlugin, PaymentApiException>() {
-                                       @Override
-                                       public Pagination<PaymentMethodPlugin> build() throws PaymentApiException {
-                                           try {
-                                               return pluginApi.searchPaymentMethods(searchKey, offset, limit, buildTenantContext(internalTenantContext));
-                                           } catch (final PaymentPluginApiException e) {
-                                               throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENT_METHODS, pluginName, searchKey);
+    public Pagination<PaymentMethod> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final String pluginName,
+                                                          final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+        if (withPluginInfo) {
+            final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
+
+            return getEntityPagination(limit,
+                                       new SourcePaginationBuilder<PaymentMethodPlugin, PaymentApiException>() {
+                                           @Override
+                                           public Pagination<PaymentMethodPlugin> build() throws PaymentApiException {
+                                               try {
+                                                   return pluginApi.searchPaymentMethods(searchKey, offset, limit, properties, tenantContext);
+                                               } catch (final PaymentPluginApiException e) {
+                                                   throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENT_METHODS, pluginName, searchKey);
+                                               }
+                                           }
+                                       },
+                                       new Function<PaymentMethodPlugin, PaymentMethod>() {
+                                           @Override
+                                           public PaymentMethod apply(final PaymentMethodPlugin paymentMethodPlugin) {
+                                               if (paymentMethodPlugin.getKbPaymentMethodId() == null) {
+                                                   // Garbage from the plugin?
+                                                   log.debug("Plugin {} returned a payment method without a kbPaymentMethodId for searchKey {}", pluginName, searchKey);
+                                                   return null;
+                                               }
+
+                                               final PaymentMethodModelDao paymentMethodModelDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodPlugin.getKbPaymentMethodId(), internalTenantContext);
+                                               if (paymentMethodModelDao == null) {
+                                                   log.warn("Unable to find payment method id " + paymentMethodPlugin.getKbPaymentMethodId() + " present in plugin " + pluginName);
+                                                   return null;
+                                               }
+
+                                               return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
                                            }
                                        }
-                                   },
-                                   new Function<PaymentMethodPlugin, PaymentMethod>() {
-                                       @Override
-                                       public PaymentMethod apply(final PaymentMethodPlugin paymentMethodPlugin) {
-                                           if (paymentMethodPlugin.getKbPaymentMethodId() == null) {
-                                               // Garbage from the plugin?
-                                               log.debug("Plugin {} returned a payment method without a kbPaymentMethodId for searchKey {}", pluginName, searchKey);
-                                               return null;
+                                      );
+        } else {
+            return getEntityPagination(limit,
+                                       new SourcePaginationBuilder<PaymentMethodModelDao, PaymentApiException>() {
+                                           @Override
+                                           public Pagination<PaymentMethodModelDao> build() {
+                                               return paymentDao.searchPaymentMethods(searchKey, offset, limit, internalTenantContext);
                                            }
-
-                                           final PaymentMethodModelDao paymentMethodModelDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodPlugin.getKbPaymentMethodId(), internalTenantContext);
-                                           if (paymentMethodModelDao == null) {
-                                               log.warn("Unable to find payment method id " + paymentMethodPlugin.getKbPaymentMethodId() + " present in plugin " + pluginName);
-                                               return null;
+                                       },
+                                       new Function<PaymentMethodModelDao, PaymentMethod>() {
+                                           @Override
+                                           public PaymentMethod apply(final PaymentMethodModelDao paymentMethodModelDao) {
+                                               return new DefaultPaymentMethod(paymentMethodModelDao, null);
                                            }
-
-                                           return new DefaultPaymentMethod(paymentMethodModelDao, paymentMethodPlugin);
                                        }
-                                   }
-                                  );
+                                      );
+        }
     }
 
-    public PaymentMethod getExternalPaymentMethod(final Account account, final InternalTenantContext context) throws PaymentApiException {
-        final List<PaymentMethod> paymentMethods = getPaymentMethods(account, false, context);
+    public PaymentMethod getExternalPaymentMethod(final UUID accountId, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context) throws PaymentApiException {
+        final List<PaymentMethod> paymentMethods = getPaymentMethods(accountId, false, properties, tenantContext, context);
         for (final PaymentMethod paymentMethod : paymentMethods) {
             if (ExternalPaymentProviderPlugin.PLUGIN_NAME.equals(paymentMethod.getPluginName())) {
                 return paymentMethod;
             }
         }
-
         return null;
     }
 
-    public ExternalPaymentProviderPlugin getExternalPaymentProviderPlugin(final Account account, final InternalCallContext context) throws PaymentApiException {
+    public UUID createOrGetExternalPaymentMethod(final String paymentMethodExternalKey, final Account account, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context) throws PaymentApiException {
         // Check if this account has already used the external payment plugin
         // If not, it's the first time - add a payment method for it
-        if (getExternalPaymentMethod(account, context) == null) {
-            final DefaultNoOpPaymentMethodPlugin props = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), false, ImmutableList.<PaymentMethodKVInfo>of());
-            addPaymentMethod(ExternalPaymentProviderPlugin.PLUGIN_NAME, account, false, props, context);
+        final PaymentMethod externalPaymentMethod = getExternalPaymentMethod(account.getId(), properties, callContext, context);
+        if (externalPaymentMethod != null) {
+            return externalPaymentMethod.getId();
         }
+        final DefaultNoOpPaymentMethodPlugin props = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), false, properties);
+        return addPaymentMethod(paymentMethodExternalKey, ExternalPaymentProviderPlugin.PLUGIN_NAME, account, false, props, properties, callContext, context);
+    }
 
+    public ExternalPaymentProviderPlugin createPaymentMethodAndGetExternalPaymentProviderPlugin(final String paymentMethodExternalKey, final Account account, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalContext) throws PaymentApiException {
+        // Check if this account has already used the external payment plugin
+        // If not, it's the first time - add a payment method for it
+        createOrGetExternalPaymentMethod(paymentMethodExternalKey, account, properties, callContext, internalContext);
         return (ExternalPaymentProviderPlugin) getPaymentPluginApi(ExternalPaymentProviderPlugin.PLUGIN_NAME);
     }
 
-    private List<PaymentMethod> getPaymentMethodInternal(final List<PaymentMethodModelDao> paymentMethodModels, final boolean withPluginInfo, final InternalTenantContext context)
+    private List<PaymentMethod> getPaymentMethodInternal(final Collection<PaymentMethodModelDao> paymentMethodModels, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext context)
             throws PaymentApiException {
-
         final List<PaymentMethod> result = new ArrayList<PaymentMethod>(paymentMethodModels.size());
         for (final PaymentMethodModelDao paymentMethodModel : paymentMethodModels) {
-            final PaymentMethod pm = buildDefaultPaymentMethod(paymentMethodModel, withPluginInfo, context);
+            final PaymentMethod pm = buildDefaultPaymentMethod(paymentMethodModel, withPluginInfo, properties, tenantContext, context);
             result.add(pm);
         }
         return result;
     }
 
     public void deletedPaymentMethod(final Account account, final UUID paymentMethodId,
-                                     final boolean deleteDefaultPaymentMethodWithAutoPayOff, final InternalCallContext context)
+                                     final boolean deleteDefaultPaymentMethodWithAutoPayOff,
+                                     final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context)
             throws PaymentApiException {
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
-
-        new WithAccountLock<Void>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<Void>() {
+        try {
+            new WithAccountLock<Void, PaymentApiException>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<PluginDispatcherReturnType<Void>, PaymentApiException>() {
 
-            @Override
-            public Void doOperation() throws PaymentApiException {
-                final PaymentMethodModelDao paymentMethodModel = paymentDao.getPaymentMethod(paymentMethodId, context);
-                if (paymentMethodModel == null) {
-                    throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
-                }
+                @Override
+                public PluginDispatcherReturnType<Void> doOperation() throws PaymentApiException {
+                    final PaymentMethodModelDao paymentMethodModel = paymentDao.getPaymentMethod(paymentMethodId, context);
+                    if (paymentMethodModel == null) {
+                        throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
+                    }
 
-                try {
-                    // Note: account.getPaymentMethodId() may be null
-                    if (paymentMethodId.equals(account.getPaymentMethodId())) {
-                        if (!deleteDefaultPaymentMethodWithAutoPayOff) {
-                            throw new PaymentApiException(ErrorCode.PAYMENT_DEL_DEFAULT_PAYMENT_METHOD, account.getId());
-                        } else {
-                            final boolean isAccountAutoPayOff = isAccountAutoPayOff(account.getId(), context);
-                            if (!isAccountAutoPayOff) {
-                                log.info("Setting account {} to AUTO_PAY_OFF because of default payment method deletion", account.getId());
-                                setAccountAutoPayOff(account.getId(), context);
+                    try {
+                        // Note: account.getPaymentMethodId() may be null
+                        if (paymentMethodId.equals(account.getPaymentMethodId())) {
+                            if (!deleteDefaultPaymentMethodWithAutoPayOff) {
+                                throw new PaymentApiException(ErrorCode.PAYMENT_DEL_DEFAULT_PAYMENT_METHOD, account.getId());
+                            } else {
+                                final boolean isAccountAutoPayOff = isAccountAutoPayOff(account.getId(), context);
+                                if (!isAccountAutoPayOff) {
+                                    log.info("Setting account {} to AUTO_PAY_OFF because of default payment method deletion", account.getId());
+                                    setAccountAutoPayOff(account.getId(), context);
+                                }
+                                accountInternalApi.removePaymentMethod(account.getId(), context);
                             }
-                            accountInternalApi.removePaymentMethod(account.getId(), context);
                         }
+                        final PaymentPluginApi pluginApi = getPluginApi(paymentMethodId, context);
+                        pluginApi.deletePaymentMethod(account.getId(), paymentMethodId, properties, callContext);
+                        paymentDao.deletedPaymentMethod(paymentMethodId, context);
+                        return PluginDispatcher.createPluginDispatcherReturnType(null);
+                    } catch (final PaymentPluginApiException e) {
+                        log.warn("Error deleting payment method " + paymentMethodId, e);
+                        throw new PaymentApiException(ErrorCode.PAYMENT_DEL_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
+                    } catch (final AccountApiException e) {
+                        throw new PaymentApiException(e);
                     }
-                    final PaymentPluginApi pluginApi = getPluginApi(paymentMethodId, context);
-                    pluginApi.deletePaymentMethod(account.getId(), paymentMethodId, context.toCallContext(tenantId));
-                    paymentDao.deletedPaymentMethod(paymentMethodId, context);
-                    return null;
-                } catch (PaymentPluginApiException e) {
-                    log.warn("Error deleting payment method " + paymentMethodId, e);
-                    throw new PaymentApiException(ErrorCode.PAYMENT_DEL_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
-                } catch (AccountApiException e) {
-                    throw new PaymentApiException(e);
                 }
-            }
-        });
+            });
+        } catch (final Exception e) {
+            throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+        }
     }
 
-    public void setDefaultPaymentMethod(final Account account, final UUID paymentMethodId, final InternalCallContext context)
+    public void setDefaultPaymentMethod(final Account account, final UUID paymentMethodId, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context)
             throws PaymentApiException {
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
-
-        new WithAccountLock<Void>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<Void>() {
+        try {
+            new WithAccountLock<Void, PaymentApiException>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<PluginDispatcherReturnType<Void>, PaymentApiException>() {
 
-            @Override
-            public Void doOperation() throws PaymentApiException {
-                final PaymentMethodModelDao paymentMethodModel = paymentDao.getPaymentMethod(paymentMethodId, context);
-                if (paymentMethodModel == null) {
-                    throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
-                }
+                @Override
+                public PluginDispatcherReturnType<Void> doOperation() throws PaymentApiException {
+                    final PaymentMethodModelDao paymentMethodModel = paymentDao.getPaymentMethod(paymentMethodId, context);
+                    if (paymentMethodModel == null) {
+                        throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
+                    }
 
-                try {
-                    final PaymentPluginApi pluginApi = getPluginApi(paymentMethodId, context);
+                    try {
+                        final PaymentPluginApi pluginApi = getPluginApi(paymentMethodId, context);
 
-                    pluginApi.setDefaultPaymentMethod(account.getId(), paymentMethodId, context.toCallContext(tenantId));
-                    accountInternalApi.updatePaymentMethod(account.getId(), paymentMethodId, context);
-                    return null;
-                } catch (PaymentPluginApiException e) {
-                    throw new PaymentApiException(ErrorCode.PAYMENT_UPD_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
-                } catch (AccountApiException e) {
-                    throw new PaymentApiException(e);
+                        pluginApi.setDefaultPaymentMethod(account.getId(), paymentMethodId, properties, callContext);
+                        accountInternalApi.updatePaymentMethod(account.getId(), paymentMethodId, context);
+                        return PluginDispatcher.createPluginDispatcherReturnType(null);
+                    } catch (final PaymentPluginApiException e) {
+                        throw new PaymentApiException(ErrorCode.PAYMENT_UPD_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
+                    } catch (final AccountApiException e) {
+                        throw new PaymentApiException(e);
+                    }
                 }
-            }
-        });
+            });
+        } catch (final Exception e) {
+            throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+        }
     }
 
     private PaymentPluginApi getPluginApi(final UUID paymentMethodId, final InternalTenantContext context)
@@ -365,76 +419,79 @@ public class PaymentMethodProcessor extends ProcessorBase {
      * @return the list of payment methods -- should be identical between KB, the plugin view-- if it keeps a state-- and the gateway.
      * @throws PaymentApiException
      */
-    public List<PaymentMethod> refreshPaymentMethods(final String pluginName, final Account account, final InternalCallContext context) throws PaymentApiException {
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
-
+    public List<PaymentMethod> refreshPaymentMethods(final String pluginName, final Account account, final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext context) throws PaymentApiException {
         // Don't hold the account lock while fetching the payment methods from the gateway as those could change anyway
         final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
         final List<PaymentMethodInfoPlugin> pluginPms;
         try {
-            pluginPms = pluginApi.getPaymentMethods(account.getId(), true, context.toCallContext(tenantId));
+            pluginPms = pluginApi.getPaymentMethods(account.getId(), true, properties, callContext);
             // The method should never return null by convention, but let's not trust the plugin...
             if (pluginPms == null) {
                 log.debug("No payment methods defined on the account {} for plugin {}", account.getId(), pluginName);
                 return ImmutableList.<PaymentMethod>of();
             }
-        } catch (PaymentPluginApiException e) {
+        } catch (final PaymentPluginApiException e) {
             log.warn("Error refreshing payment methods for account " + account.getId() + " and plugin " + pluginName, e);
             throw new PaymentApiException(ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
         }
 
-        return new WithAccountLock<List<PaymentMethod>>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<List<PaymentMethod>>() {
-
-            @Override
-            public List<PaymentMethod> doOperation() throws PaymentApiException {
-
-                UUID defaultPaymentMethodId = null;
-
-                final List<PaymentMethodInfoPlugin> pluginPmsWithId = new ArrayList<PaymentMethodInfoPlugin>();
-                final List<PaymentMethodModelDao> finalPaymentMethods = new ArrayList<PaymentMethodModelDao>();
-                for (final PaymentMethodInfoPlugin cur : pluginPms) {
-                    // If the kbPaymentId is NULL, the plugin does not know about it, so we create a new UUID
-                    final UUID paymentMethodId = cur.getPaymentMethodId() != null ? cur.getPaymentMethodId() : UUID.randomUUID();
-                    final PaymentMethod input = new DefaultPaymentMethod(paymentMethodId, account.getId(), pluginName);
-                    final PaymentMethodModelDao pmModel = new PaymentMethodModelDao(input.getId(), input.getCreatedDate(), input.getUpdatedDate(),
-                                                                                    input.getAccountId(), input.getPluginName(), input.isActive());
-                    finalPaymentMethods.add(pmModel);
-
-                    pluginPmsWithId.add(new DefaultPaymentMethodInfoPlugin(cur, paymentMethodId));
-
-                    // Note: we do not unset the default payment method in Kill Bill even if isDefault is false here.
-                    // Some gateways don't support the concept of "default" payment methods, in that case the plugin
-                    // will always return false - it's Kill Bill in that case which is responsible to manage default payment methods
-                    if (cur.isDefault()) {
-                        defaultPaymentMethodId = paymentMethodId;
+        try {
+            final PluginDispatcherReturnType<List<PaymentMethod>> result = new WithAccountLock<List<PaymentMethod>, PaymentApiException>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<PluginDispatcherReturnType<List<PaymentMethod>>, PaymentApiException>() {
+                @Override
+                public PluginDispatcherReturnType<List<PaymentMethod>> doOperation() throws PaymentApiException {
+
+                    UUID defaultPaymentMethodId = null;
+
+                    final List<PaymentMethodInfoPlugin> pluginPmsWithId = new ArrayList<PaymentMethodInfoPlugin>();
+                    final List<PaymentMethodModelDao> finalPaymentMethods = new ArrayList<PaymentMethodModelDao>();
+                    for (final PaymentMethodInfoPlugin cur : pluginPms) {
+                        // If the kbPaymentId is NULL, the plugin does not know about it, so we create a new UUID
+                        final UUID paymentMethodId = cur.getPaymentMethodId() != null ? cur.getPaymentMethodId() : UUID.randomUUID();
+                        // TODO paymentMethod externalKey seems broken here.
+                        final PaymentMethod input = new DefaultPaymentMethod(paymentMethodId, paymentMethodId.toString(), account.getId(), pluginName);
+                        final PaymentMethodModelDao pmModel = new PaymentMethodModelDao(input.getId(), input.getExternalKey(), input.getCreatedDate(), input.getUpdatedDate(),
+                                                                                        input.getAccountId(), input.getPluginName(), input.isActive());
+                        finalPaymentMethods.add(pmModel);
+
+                        pluginPmsWithId.add(new DefaultPaymentMethodInfoPlugin(cur, paymentMethodId));
+
+                        // Note: we do not unset the default payment method in Kill Bill even if isDefault is false here.
+                        // Some gateways don't support the concept of "default" payment methods, in that case the plugin
+                        // will always return false - it's Kill Bill in that case which is responsible to manage default payment methods
+                        if (cur.isDefault()) {
+                            defaultPaymentMethodId = paymentMethodId;
+                        }
                     }
-                }
 
-                final List<PaymentMethodModelDao> refreshedPaymentMethods = paymentDao.refreshPaymentMethods(account.getId(),
-                                                                                                             pluginName,
-                                                                                                             finalPaymentMethods,
-                                                                                                             context);
-                try {
-                    pluginApi.resetPaymentMethods(account.getId(), pluginPmsWithId);
-                } catch (PaymentPluginApiException e) {
-                    log.warn("Error resetting payment methods for account " + account.getId() + " and plugin " + pluginName, e);
-                    throw new PaymentApiException(ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
-                }
-
-                try {
-                    updateDefaultPaymentMethodIfNeeded(pluginName, account, defaultPaymentMethodId, context);
-                } catch (AccountApiException e) {
-                    throw new PaymentApiException(e);
-                }
+                    final List<PaymentMethodModelDao> refreshedPaymentMethods = paymentDao.refreshPaymentMethods(account.getId(),
+                                                                                                                 pluginName,
+                                                                                                                 finalPaymentMethods,
+                                                                                                                 context);
 
-                return ImmutableList.<PaymentMethod>copyOf(Collections2.transform(refreshedPaymentMethods, new Function<PaymentMethodModelDao, PaymentMethod>() {
-                    @Override
-                    public PaymentMethod apply(final PaymentMethodModelDao input) {
-                        return new DefaultPaymentMethod(input, null);
+                    try {
+                        pluginApi.resetPaymentMethods(account.getId(), pluginPmsWithId, properties, callContext);
+                    } catch (final PaymentPluginApiException e) {
+                        log.warn("Error resetting payment methods for account " + account.getId() + " and plugin " + pluginName, e);
+                        throw new PaymentApiException(ErrorCode.PAYMENT_REFRESH_PAYMENT_METHOD, account.getId(), e.getErrorMessage());
                     }
-                }));
-            }
-        });
+                    try {
+                        updateDefaultPaymentMethodIfNeeded(pluginName, account, defaultPaymentMethodId, context);
+                    } catch (final AccountApiException e) {
+                        throw new PaymentApiException(e);
+                    }
+                    final List<PaymentMethod> result = ImmutableList.<PaymentMethod>copyOf(Collections2.transform(refreshedPaymentMethods, new Function<PaymentMethodModelDao, PaymentMethod>() {
+                        @Override
+                        public PaymentMethod apply(final PaymentMethodModelDao input) {
+                            return new DefaultPaymentMethod(input, null);
+                        }
+                    }));
+                    return PluginDispatcher.createPluginDispatcherReturnType(result);
+                }
+            });
+            return result.getReturnType();
+        } catch (final Exception e) {
+            throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+        }
     }
 
     private void updateDefaultPaymentMethodIfNeeded(final String pluginName, final Account account, @Nullable final UUID defaultPluginPaymentMethodId, final InternalCallContext context) throws PaymentApiException, AccountApiException {
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index b2fe6ab..f78753d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,68 +19,63 @@
 package org.killbill.billing.payment.core;
 
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import org.killbill.automaton.OperationResult;
 import org.killbill.billing.ErrorCode;
-import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
-import org.killbill.billing.account.api.AccountApiException;
 import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.bus.api.PersistentBus;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.clock.Clock;
-import org.killbill.commons.locker.GlobalLocker;
-import org.killbill.billing.events.BusInternalEvent;
-import org.killbill.billing.events.PaymentErrorInternalEvent;
-import org.killbill.billing.invoice.api.Invoice;
-import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.DefaultPayment;
-import org.killbill.billing.payment.api.DefaultPaymentErrorEvent;
-import org.killbill.billing.payment.api.DefaultPaymentInfoEvent;
-import org.killbill.billing.payment.api.DefaultPaymentPluginErrorEvent;
+import org.killbill.billing.payment.api.DefaultPaymentTransaction;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.PaymentStatus;
-import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.sm.PaymentAutomatonRunner;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
-import org.killbill.billing.payment.dao.RefundModelDao;
-import org.killbill.billing.payment.dispatcher.PluginDispatcher;
-import org.killbill.billing.payment.plugin.api.PaymentInfoPlugin;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
-import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
-import org.killbill.billing.payment.retry.AutoPayRetryService.AutoPayRetryServiceScheduler;
-import org.killbill.billing.payment.retry.FailedPaymentRetryService.FailedPaymentRetryServiceScheduler;
-import org.killbill.billing.payment.retry.PluginFailureRetryService.PluginFailureRetryServiceScheduler;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
-import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.EntityPaginationBuilder;
 import org.killbill.billing.util.entity.dao.DefaultPaginationHelper.SourcePaginationBuilder;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
-import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
 import com.google.inject.name.Named;
 
 import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
@@ -87,81 +84,148 @@ import static org.killbill.billing.util.entity.dao.DefaultPaginationHelper.getEn
 
 public class PaymentProcessor extends ProcessorBase {
 
-    private static final UUID MISSING_PAYMENT_METHOD_ID = UUID.fromString("99999999-dead-beef-babe-999999999999");
+    private static final ImmutableList<PluginProperty> PLUGIN_PROPERTIES = ImmutableList.<PluginProperty>of();
 
-    private final PaymentMethodProcessor paymentMethodProcessor;
-    private final FailedPaymentRetryServiceScheduler failedPaymentRetryService;
-    private final PluginFailureRetryServiceScheduler pluginFailureRetryService;
-    private final AutoPayRetryServiceScheduler autoPayoffRetryService;
-
-    private final Clock clock;
-
-    private final PaymentConfig paymentConfig;
-
-    private final PluginDispatcher<Payment> paymentPluginDispatcher;
-    private final PluginDispatcher<Void> voidPluginDispatcher;
+    private final PaymentAutomatonRunner paymentAutomatonRunner;
+    private final InternalCallContextFactory internalCallContextFactory;
 
     private static final Logger log = LoggerFactory.getLogger(PaymentProcessor.class);
 
     @Inject
     public PaymentProcessor(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
-                            final PaymentMethodProcessor paymentMethodProcessor,
                             final AccountInternalApi accountUserApi,
                             final InvoiceInternalApi invoiceApi,
                             final TagInternalApi tagUserApi,
-                            final FailedPaymentRetryServiceScheduler failedPaymentRetryService,
-                            final PluginFailureRetryServiceScheduler pluginFailureRetryService,
-                            final AutoPayRetryServiceScheduler autoPayoffRetryService,
                             final PaymentDao paymentDao,
                             final NonEntityDao nonEntityDao,
-                            final PersistentBus eventBus,
-                            final Clock clock,
+                            final InternalCallContextFactory internalCallContextFactory,
                             final GlobalLocker locker,
-                            final PaymentConfig paymentConfig,
-                            @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor) {
-        super(pluginRegistry, accountUserApi, eventBus, paymentDao, nonEntityDao, tagUserApi, locker, executor, invoiceApi);
-        this.paymentMethodProcessor = paymentMethodProcessor;
-        this.failedPaymentRetryService = failedPaymentRetryService;
-        this.pluginFailureRetryService = pluginFailureRetryService;
-        this.autoPayoffRetryService = autoPayoffRetryService;
-        this.clock = clock;
-        this.paymentConfig = paymentConfig;
-        final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
-        this.paymentPluginDispatcher = new PluginDispatcher<Payment>(paymentPluginTimeoutSec, executor);
-        this.voidPluginDispatcher = new PluginDispatcher<Void>(paymentPluginTimeoutSec, executor);
+                            @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                            final PaymentAutomatonRunner paymentAutomatonRunner,
+                            final Clock clock,
+                            final CacheControllerDispatcher controllerDispatcher) {
+        super(pluginRegistry, accountUserApi, paymentDao, nonEntityDao, tagUserApi, locker, executor, invoiceApi, clock, controllerDispatcher);
+        this.internalCallContextFactory = internalCallContextFactory;
+        this.paymentAutomatonRunner = paymentAutomatonRunner;
+    }
+
+    public Payment createAuthorization(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                       @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
+                                       final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return performOperation(isApiPayment, attemptId, TransactionType.AUTHORIZE, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+    }
+
+    public Payment createCapture(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                 @Nullable final String paymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
+                                 final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return performOperation(isApiPayment, attemptId, TransactionType.CAPTURE, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+    }
+
+    public Payment createPurchase(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                  @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
+                                  final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return performOperation(isApiPayment, attemptId, TransactionType.PURCHASE, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+    }
+
+    public Payment createVoid(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
+                              final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return performOperation(isApiPayment, attemptId, TransactionType.VOID, account, null, paymentId, null, null, null, null, paymentTransactionExternalKey, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+    }
+
+    public Payment createRefund(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                final String paymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
+                                final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return performOperation(isApiPayment, attemptId, TransactionType.REFUND, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
+    }
+
+    public Payment createCredit(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey, final boolean shouldLockAccountAndDispatch,
+                                final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return performOperation(isApiPayment, attemptId, TransactionType.CREDIT, account, paymentMethodId, paymentId, null, amount, currency, paymentExternalKey, paymentTransactionExternalKey, shouldLockAccountAndDispatch, null, properties, callContext, internalCallContext);
     }
 
-    public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final InternalTenantContext context) throws PaymentApiException {
-        final PaymentModelDao model = paymentDao.getPayment(paymentId, context);
-        if (model == null) {
+    public Payment createChargeback(final boolean isApiPayment, @Nullable final UUID attemptId, final Account account, final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final BigDecimal amount, final Currency currency, final boolean shouldLockAccountAndDispatch,
+                                    final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return performOperation(isApiPayment, attemptId, TransactionType.CHARGEBACK, account, null, paymentId, null, amount, currency, null, paymentTransactionExternalKey, shouldLockAccountAndDispatch, null, PLUGIN_PROPERTIES, callContext, internalCallContext);
+    }
+
+    public Payment notifyPendingPaymentOfStateChanged(final Account account, final UUID transactionId, final boolean isSuccess, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        final PaymentTransactionModelDao transactionModelDao = paymentDao.getPaymentTransaction(transactionId, internalCallContext);
+        if (transactionModelDao.getTransactionStatus() != TransactionStatus.PENDING) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_SUCCESS_PAYMENT, transactionModelDao.getPaymentId());
+        }
+
+        final OperationResult overridePluginResult = isSuccess ? OperationResult.SUCCESS : OperationResult.FAILURE;
+
+        return performOperation(true, null, transactionModelDao.getTransactionType(), account, null, transactionModelDao.getPaymentId(),
+                                transactionModelDao.getId(), transactionModelDao.getAmount(), transactionModelDao.getCurrency(), null, transactionModelDao.getTransactionExternalKey(), true,
+                                overridePluginResult, PLUGIN_PROPERTIES, callContext, internalCallContext);
+    }
+
+    public List<Payment> getAccountPayments(final UUID accountId, final boolean withPluginInfo, final TenantContext context, final InternalTenantContext tenantContext) throws PaymentApiException {
+        final List<PaymentModelDao> paymentsModelDao = paymentDao.getPaymentsForAccount(accountId, tenantContext);
+        final List<PaymentTransactionModelDao> transactionsModelDao = paymentDao.getTransactionsForAccount(accountId, tenantContext);
+
+        final Map<UUID, PaymentPluginApi> paymentPluginByPaymentMethodId = new HashMap<UUID, PaymentPluginApi>();
+        final Collection<UUID> absentPlugins = new HashSet<UUID>();
+        return Lists.<PaymentModelDao, Payment>transform(paymentsModelDao,
+                                                         new Function<PaymentModelDao, Payment>() {
+                                                             @Override
+                                                             public Payment apply(final PaymentModelDao paymentModelDao) {
+                                                                 List<PaymentTransactionInfoPlugin> pluginInfo = null;
+
+                                                                 if (withPluginInfo) {
+                                                                     PaymentPluginApi pluginApi = paymentPluginByPaymentMethodId.get(paymentModelDao.getPaymentMethodId());
+                                                                     if (pluginApi == null && !absentPlugins.contains(paymentModelDao.getPaymentMethodId())) {
+                                                                         try {
+                                                                             pluginApi = getPaymentProviderPlugin(paymentModelDao.getPaymentMethodId(), tenantContext);
+                                                                             paymentPluginByPaymentMethodId.put(paymentModelDao.getPaymentMethodId(), pluginApi);
+                                                                         } catch (final PaymentApiException e) {
+                                                                             log.warn("Unable to retrieve pluginApi for payment method " + paymentModelDao.getPaymentMethodId());
+                                                                             absentPlugins.add(paymentModelDao.getPaymentMethodId());
+                                                                         }
+                                                                     }
+
+                                                                     pluginInfo = getPaymentTransactionInfoPluginsIfNeeded(pluginApi, paymentModelDao, context);
+                                                                 }
+
+                                                                 return toPayment(paymentModelDao, transactionsModelDao, pluginInfo);
+                                                             }
+                                                         });
+    }
+
+    public Payment getPayment(final UUID paymentId, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+        final PaymentModelDao paymentModelDao = paymentDao.getPayment(paymentId, internalTenantContext);
+        if (paymentModelDao == null) {
             return null;
         }
-        final PaymentPluginApi plugin = withPluginInfo ? getPaymentProviderPlugin(model.getPaymentMethodId(), context) : null;
-        PaymentInfoPlugin pluginInfo = null;
-        if (plugin != null) {
-            try {
-                pluginInfo = plugin.getPaymentInfo(model.getAccountId(), paymentId, buildTenantContext(context));
-            } catch (PaymentPluginApiException e) {
-                throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_GET_PAYMENT_INFO, paymentId, e.toString());
-            }
+        return toPayment(paymentModelDao, withPluginInfo, properties, tenantContext, internalTenantContext);
+    }
+
+    public Payment getPaymentByExternalKey(final String paymentExternalKey, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+        final PaymentModelDao paymentModelDao = paymentDao.getPaymentByExternalKey(paymentExternalKey, internalTenantContext);
+        if (paymentModelDao == null) {
+            return null;
         }
-        return fromPaymentModelDao(model, pluginInfo, context);
+        return toPayment(paymentModelDao, withPluginInfo, properties, tenantContext, internalTenantContext);
     }
 
-    public Pagination<Payment> getPayments(final Long offset, final Long limit, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) {
+    public Pagination<Payment> getPayments(final Long offset, final Long limit, final boolean withPluginInfo, final Iterable<PluginProperty> properties,
+                                           final TenantContext tenantContext, final InternalTenantContext internalTenantContext) {
         return getEntityPaginationFromPlugins(getAvailablePlugins(),
                                               offset,
                                               limit,
                                               new EntityPaginationBuilder<Payment, PaymentApiException>() {
                                                   @Override
                                                   public Pagination<Payment> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
-                                                      return getPayments(offset, limit, pluginName, tenantContext, internalTenantContext);
+                                                      return getPayments(offset, limit, pluginName, withPluginInfo, properties, tenantContext, internalTenantContext);
                                                   }
-                                              });
+                                              }
+                                             );
     }
 
-    public Pagination<Payment> getPayments(final Long offset, final Long limit, final String pluginName, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
-        final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
+    public Pagination<Payment> getPayments(final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+        final PaymentPluginApi pluginApi = withPluginInfo ? getPaymentPluginApi(pluginName) : null;
 
         return getEntityPagination(limit,
                                    new SourcePaginationBuilder<PaymentModelDao, PaymentApiException>() {
@@ -174,601 +238,207 @@ public class PaymentProcessor extends ProcessorBase {
                                    new Function<PaymentModelDao, Payment>() {
                                        @Override
                                        public Payment apply(final PaymentModelDao paymentModelDao) {
-                                           PaymentInfoPlugin pluginInfo = null;
-                                           try {
-                                               pluginInfo = pluginApi.getPaymentInfo(paymentModelDao.getAccountId(), paymentModelDao.getId(), tenantContext);
-                                           } catch (final PaymentPluginApiException e) {
-                                               log.warn("Unable to find payment id " + paymentModelDao.getId() + " in plugin " + pluginName);
-                                               // We still want to return a payment object, even though the plugin details are missing
-                                           }
-
-                                           return fromPaymentModelDao(paymentModelDao, pluginInfo, internalTenantContext);
+                                           final List<PaymentTransactionInfoPlugin> pluginInfo = getPaymentTransactionInfoPluginsIfNeeded(pluginApi, paymentModelDao, tenantContext);
+                                           return toPayment(paymentModelDao.getId(), pluginInfo, internalTenantContext);
                                        }
                                    }
                                   );
     }
 
-    public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final InternalTenantContext internalTenantContext) {
+    public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) {
         return getEntityPaginationFromPlugins(getAvailablePlugins(),
                                               offset,
                                               limit,
                                               new EntityPaginationBuilder<Payment, PaymentApiException>() {
                                                   @Override
                                                   public Pagination<Payment> build(final Long offset, final Long limit, final String pluginName) throws PaymentApiException {
-                                                      return searchPayments(searchKey, offset, limit, pluginName, internalTenantContext);
+                                                      return searchPayments(searchKey, offset, limit, pluginName, withPluginInfo, properties, tenantContext, internalTenantContext);
                                                   }
-                                              });
+                                              }
+                                             );
     }
 
-    public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final String pluginName, final InternalTenantContext internalTenantContext) throws PaymentApiException {
-        final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
+    public Pagination<Payment> searchPayments(final String searchKey, final Long offset, final Long limit, final String pluginName, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext tenantContext, final InternalTenantContext internalTenantContext) throws PaymentApiException {
+        if (withPluginInfo) {
+            final PaymentPluginApi pluginApi = getPaymentPluginApi(pluginName);
+
+            return getEntityPagination(limit,
+                                       new SourcePaginationBuilder<PaymentTransactionInfoPlugin, PaymentApiException>() {
+                                           @Override
+                                           public Pagination<PaymentTransactionInfoPlugin> build() throws PaymentApiException {
+                                               try {
+                                                   return pluginApi.searchPayments(searchKey, offset, limit, properties, tenantContext);
+                                               } catch (final PaymentPluginApiException e) {
+                                                   throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENTS, pluginName, searchKey);
+                                               }
+                                           }
 
-        return getEntityPagination(limit,
-                                   new SourcePaginationBuilder<PaymentInfoPlugin, PaymentApiException>() {
-                                       @Override
-                                       public Pagination<PaymentInfoPlugin> build() throws PaymentApiException {
-                                           try {
-                                               return pluginApi.searchPayments(searchKey, offset, limit, buildTenantContext(internalTenantContext));
-                                           } catch (final PaymentPluginApiException e) {
-                                               throw new PaymentApiException(e, ErrorCode.PAYMENT_PLUGIN_SEARCH_PAYMENTS, pluginName, searchKey);
+                                       },
+                                       new Function<PaymentTransactionInfoPlugin, Payment>() {
+                                           final List<PaymentTransactionInfoPlugin> cachedPaymentTransactions = new LinkedList<PaymentTransactionInfoPlugin>();
+
+                                           @Override
+                                           public Payment apply(final PaymentTransactionInfoPlugin pluginTransaction) {
+                                               if (pluginTransaction.getKbPaymentId() == null) {
+                                                   // Garbage from the plugin?
+                                                   log.debug("Plugin {} returned a payment without a kbPaymentId for searchKey {}", pluginName, searchKey);
+                                                   return null;
+                                               }
+
+                                               if (cachedPaymentTransactions.isEmpty() ||
+                                                   (cachedPaymentTransactions.get(0).getKbPaymentId().equals(pluginTransaction.getKbPaymentId()))) {
+                                                   cachedPaymentTransactions.add(pluginTransaction);
+                                                   return null;
+                                               } else {
+                                                   final Payment result = toPayment(pluginTransaction.getKbPaymentId(), ImmutableList.<PaymentTransactionInfoPlugin>copyOf(cachedPaymentTransactions), internalTenantContext);
+                                                   cachedPaymentTransactions.clear();
+                                                   cachedPaymentTransactions.add(pluginTransaction);
+                                                   return result;
+                                               }
                                            }
                                        }
-                                   },
-                                   new Function<PaymentInfoPlugin, Payment>() {
-                                       @Override
-                                       public Payment apply(final PaymentInfoPlugin paymentInfoPlugin) {
-                                           if (paymentInfoPlugin.getKbPaymentId() == null) {
-                                               // Garbage from the plugin?
-                                               log.debug("Plugin {} returned a payment without a kbPaymentId for searchKey {}", pluginName, searchKey);
-                                               return null;
+                                      );
+        } else {
+            return getEntityPagination(limit,
+                                       new SourcePaginationBuilder<PaymentModelDao, PaymentApiException>() {
+                                           @Override
+                                           public Pagination<PaymentModelDao> build() {
+                                               return paymentDao.searchPayments(searchKey, offset, limit, internalTenantContext);
                                            }
-
-                                           final PaymentModelDao model = paymentDao.getPayment(paymentInfoPlugin.getKbPaymentId(), internalTenantContext);
-                                           if (model == null) {
-                                               log.warn("Unable to find payment id " + paymentInfoPlugin.getKbPaymentId() + " present in plugin " + pluginName);
-                                               return null;
+                                       },
+                                       new Function<PaymentModelDao, Payment>() {
+                                           @Override
+                                           public Payment apply(final PaymentModelDao paymentModelDao) {
+                                               return toPayment(paymentModelDao.getId(), null, internalTenantContext);
                                            }
-
-                                           return fromPaymentModelDao(model, paymentInfoPlugin, internalTenantContext);
                                        }
-                                   }
-                                  );
-    }
-
-    public List<Payment> getInvoicePayments(final UUID invoiceId, final InternalTenantContext context) {
-        return getPayments(paymentDao.getPaymentsForInvoice(invoiceId, context), context);
-    }
-
-    public List<Payment> getAccountPayments(final UUID accountId, final InternalTenantContext context) {
-        return getPayments(paymentDao.getPaymentsForAccount(accountId, context), context);
-    }
-
-    private List<Payment> getPayments(final List<PaymentModelDao> payments, final InternalTenantContext context) {
-        if (payments == null) {
-            return Collections.emptyList();
+                                      );
         }
-        final List<Payment> result = new LinkedList<Payment>();
-        for (final PaymentModelDao cur : payments) {
-            final Payment entry = fromPaymentModelDao(cur, null, context);
-            result.add(entry);
-        }
-        return result;
-    }
-
-    private Payment fromPaymentModelDao(final PaymentModelDao input, @Nullable final PaymentInfoPlugin pluginInfo, final InternalTenantContext context) {
-        final List<PaymentAttemptModelDao> attempts = paymentDao.getAttemptsForPayment(input.getId(), context);
-        final List<RefundModelDao> refunds = paymentDao.getRefundsForPayment(input.getId(), context);
-        final Payment payment = new DefaultPayment(input, pluginInfo, attempts, refunds);
-        return payment;
     }
 
-    public void process_AUTO_PAY_OFF_removal(final Account account, final InternalCallContext context) throws PaymentApiException {
-
-        try {
-            voidPluginDispatcher.dispatchWithAccountLock(new CallableWithAccountLock<Void>(locker,
-                                                                                           account.getExternalKey(),
-                                                                                           new WithAccountLockCallback<Void>() {
-
-                                                                                               @Override
-                                                                                               public Void doOperation() throws PaymentApiException {
-
-                                                                                                   final List<PaymentModelDao> payments = paymentDao.getPaymentsForAccount(account.getId(), context);
-                                                                                                   final Collection<PaymentModelDao> paymentsToBeCompleted = Collections2.filter(payments, new Predicate<PaymentModelDao>() {
-                                                                                                       @Override
-                                                                                                       public boolean apply(final PaymentModelDao in) {
-                                                                                                           // Payments left in AUTO_PAY_OFF or for which we did not retry enough
-                                                                                                           return (in.getPaymentStatus() == PaymentStatus.AUTO_PAY_OFF ||
-                                                                                                                   in.getPaymentStatus() == PaymentStatus.PAYMENT_FAILURE ||
-                                                                                                                   in.getPaymentStatus() == PaymentStatus.PLUGIN_FAILURE ||
-                                                                                                                   in.getPaymentStatus() == PaymentStatus.UNKNOWN);
-                                                                                                       }
-                                                                                                   });
-                                                                                                   // Insert one retry event for each payment left in AUTO_PAY_OFF
-                                                                                                   for (PaymentModelDao cur : paymentsToBeCompleted) {
-                                                                                                       switch (cur.getPaymentStatus()) {
-                                                                                                           case AUTO_PAY_OFF:
-                                                                                                               autoPayoffRetryService.scheduleRetry(cur.getId(), clock.getUTCNow());
-                                                                                                               break;
-                                                                                                           case PAYMENT_FAILURE:
-                                                                                                               scheduleRetryOnPaymentFailure(cur.getId(), context);
-                                                                                                               break;
-                                                                                                           case PLUGIN_FAILURE:
-                                                                                                           case UNKNOWN:
-                                                                                                               scheduleRetryOnPluginFailure(cur.getId(), context);
-                                                                                                               break;
-                                                                                                           default:
-                                                                                                               // Impossible...
-                                                                                                               throw new RuntimeException("Unexpected case " + cur.getPaymentStatus());
-                                                                                                       }
-
-                                                                                                   }
-                                                                                                   return null;
-                                                                                               }
-                                                                                           }));
-        } catch (TimeoutException e) {
-            throw new PaymentApiException(ErrorCode.UNEXPECTED_ERROR, "Unexpected timeout for payment creation (AUTO_PAY_OFF)");
-        }
+    private Payment performOperation(final boolean isApiPayment, @Nullable final UUID attemptId,
+                                     final TransactionType transactionType, final Account account,
+                                     @Nullable final UUID paymentMethodId, @Nullable final UUID paymentId, @Nullable final UUID transactionId,
+                                     @Nullable final BigDecimal amount, @Nullable final Currency currency,
+                                     @Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey,
+                                     final boolean shouldLockAccountAndDispatch, @Nullable final OperationResult overridePluginOperationResult,
+                                     final Iterable<PluginProperty> properties,
+                                     final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        validateUniqueTransactionExternalKey(paymentTransactionExternalKey, internalCallContext);
+        final UUID nonNullPaymentId = paymentAutomatonRunner.run(isApiPayment,
+                                                                 transactionType,
+                                                                 account,
+                                                                 attemptId,
+                                                                 paymentMethodId,
+                                                                 paymentId,
+                                                                 transactionId,
+                                                                 paymentExternalKey,
+                                                                 paymentTransactionExternalKey,
+                                                                 amount,
+                                                                 currency,
+                                                                 shouldLockAccountAndDispatch,
+                                                                 overridePluginOperationResult,
+                                                                 properties,
+                                                                 callContext,
+                                                                 internalCallContext);
+        return getPayment(nonNullPaymentId, true, properties, callContext, internalCallContext);
     }
 
-    public Payment createPayment(final Account account, final UUID invoiceId, @Nullable final BigDecimal inputAmount,
-                                 final InternalCallContext context, final boolean isInstantPayment, final boolean isExternalPayment)
-            throws PaymentApiException {
-        // If this is an external payment, retrieve the external payment method first.
-        // We need to do this without the lock, because getExternalPaymentProviderPlugin will acquire the lock
-        // if it needs to create an external payment method (for the first external payment for that account).
-        // We don't want to retrieve any other payment method here, because we need to validate the invoice amount first
-        // (to avoid throwing an exception if there is nothing to pay).
-        final PaymentPluginApi externalPaymentPlugin;
-        if (isExternalPayment) {
-            externalPaymentPlugin = paymentMethodProcessor.getExternalPaymentProviderPlugin(account, context);
-        } else {
-            externalPaymentPlugin = null;
+    // Used in bulk get API (getAccountPayments / getPayments)
+    private List<PaymentTransactionInfoPlugin> getPaymentTransactionInfoPluginsIfNeeded(@Nullable final PaymentPluginApi pluginApi, final PaymentModelDao paymentModelDao, final TenantContext context) {
+        if (pluginApi == null) {
+            return null;
         }
 
         try {
-            return paymentPluginDispatcher.dispatchWithAccountLock(new CallableWithAccountLock<Payment>(locker,
-                                                                                                        account.getExternalKey(),
-                                                                                                        new WithAccountLockCallback<Payment>() {
-
-                                                                                                            @Override
-                                                                                                            public Payment doOperation() throws PaymentApiException {
-
-                                                                                                                try {
-                                                                                                                    // First, rebalance CBA and retrieve the latest version of the invoice
-                                                                                                                    final Invoice invoice = rebalanceAndGetInvoice(account.getId(), invoiceId, context);
-                                                                                                                    if (invoice == null || invoice.isMigrationInvoice()) {
-                                                                                                                        log.error("Received invoice for payment that is a migration invoice - don't know how to handle those yet: {}", invoice);
-                                                                                                                        return null;
-                                                                                                                    }
-
-                                                                                                                    // Second, validate the payment amount. We want to bail as early as possible if e.g. the balance is zero
-                                                                                                                    final BigDecimal requestedAmount = getAndValidatePaymentAmount(invoice, inputAmount, isInstantPayment);
-
-                                                                                                                    // Third, retrieve the payment method and associated plugin
-                                                                                                                    final PaymentPluginApi plugin;
-                                                                                                                    final UUID paymentMethodId;
-                                                                                                                    try {
-                                                                                                                        // Use the special external payment plugin to handle external payments
-                                                                                                                        if (isExternalPayment) {
-                                                                                                                            plugin = externalPaymentPlugin;
-                                                                                                                            paymentMethodId = paymentMethodProcessor.getExternalPaymentMethod(account, context).getId();
-                                                                                                                        } else {
-                                                                                                                            plugin = getPaymentProviderPlugin(account, context);
-                                                                                                                            paymentMethodId = account.getPaymentMethodId();
-                                                                                                                        }
-                                                                                                                    } catch (PaymentApiException e) {
-
-                                                                                                                        // Insert a payment entry with one attempt in a terminal state to keep a record of the failure
-                                                                                                                        processNewPaymentForMissingDefaultPaymentMethodWithAccountLocked(account, invoice, requestedAmount, context);
-
-                                                                                                                        // This event will be caught by overdue to refresh the overdue state, if needed.
-                                                                                                                        // Note that at this point, we don't know the exact invoice balance (see getAndValidatePaymentAmount() below).
-                                                                                                                        // This means that events will be posted for null and zero dollar invoices (e.g. trials).
-                                                                                                                        final PaymentErrorInternalEvent event = new DefaultPaymentErrorEvent(account.getId(), invoiceId, null,
-                                                                                                                                                                                             ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD.toString(),
-                                                                                                                                                                                             context.getAccountRecordId(), context.getTenantRecordId(),
-                                                                                                                                                                                             context.getUserToken());
-                                                                                                                        postPaymentEvent(event, account.getId(), context);
-                                                                                                                        throw e;
-                                                                                                                    }
-
-                                                                                                                    final boolean isAccountAutoPayOff = isAccountAutoPayOff(account.getId(), context);
-                                                                                                                    setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(account.getId(), paymentMethodId, isAccountAutoPayOff, context, isInstantPayment);
-
-                                                                                                                    if (!isInstantPayment && isAccountAutoPayOff) {
-                                                                                                                        return processNewPaymentForAutoPayOffWithAccountLocked(paymentMethodId, account, invoice, requestedAmount, context);
-                                                                                                                    } else {
-                                                                                                                        return processNewPaymentWithAccountLocked(paymentMethodId, plugin, account, invoice, requestedAmount, isInstantPayment, context);
-                                                                                                                    }
-                                                                                                                } catch (InvoiceApiException e) {
-                                                                                                                    throw new PaymentApiException(e);
-                                                                                                                }
-                                                                                                            }
-                                                                                                        }));
-        } catch (TimeoutException e) {
-            if (isInstantPayment) {
-                throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_TIMEOUT, account.getId(), invoiceId);
-            } else {
-                log.warn(String.format("Payment from Account %s, Invoice %s timedout", account.getId(), invoiceId));
-                // If we don't crash, plugin thread will complete (and set the correct status)
-                // If we crash before plugin thread completes, we may end up with a UNKNOWN Payment
-                // We would like to return an error so the Bus can retry but we are limited by Guava bug
-                // swallowing exception
-                return null;
-            }
-        } catch (RuntimeException e) {
-            log.error("Failure when dispatching payment for invoice " + invoiceId, e);
-            if (isInstantPayment) {
-                throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, invoiceId);
-            } else {
-                return null;
-            }
+            return getPaymentTransactionInfoPlugins(pluginApi, paymentModelDao, PLUGIN_PROPERTIES, context);
+        } catch (final PaymentApiException e) {
+            log.warn("Unable to retrieve plugin info for payment " + paymentModelDao.getId());
+            return null;
         }
     }
 
-    public void notifyPendingPaymentOfStateChanged(final Account account, final UUID paymentId, final boolean isSuccess, final InternalCallContext context)
-            throws PaymentApiException {
-
-        new WithAccountLock<Void>().processAccountWithLock(locker, account.getExternalKey(), new WithAccountLockCallback<Void>() {
-
-            @Override
-            public Void doOperation() throws PaymentApiException {
-                final PaymentModelDao payment = paymentDao.getPayment(paymentId, context);
-                if (payment == null) {
-                    throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
-                }
-                if (payment.getPaymentStatus() != PaymentStatus.PENDING) {
-                    throw new PaymentApiException(ErrorCode.PAYMENT_NOT_PENDING, paymentId);
-                }
-
-                final List<PaymentAttemptModelDao> attempts = paymentDao.getAttemptsForPayment(paymentId, context);
-                final PaymentAttemptModelDao lastAttempt = attempts.get(attempts.size() - 1);
-                final PaymentStatus newPaymentStatus = isSuccess ? PaymentStatus.SUCCESS : PaymentStatus.PAYMENT_FAILURE_ABORTED;
-                paymentDao.updatePaymentAndAttemptOnCompletion(paymentId, newPaymentStatus, payment.getProcessedAmount(), payment.getProcessedCurrency(), lastAttempt.getId(), null, null, context);
-                return null;
-            }
-        });
-    }
-
-    private void setUnsaneAccount_AUTO_PAY_OFFWithAccountLock(final UUID accountId, final UUID paymentMethodId, final boolean isAccountAutoPayOff,
-                                                              final InternalCallContext context, final boolean isInstantPayment)
-            throws PaymentApiException {
-
-        final PaymentModelDao lastPaymentForPaymentMethod = paymentDao.getLastPaymentForPaymentMethod(accountId, paymentMethodId, context);
-        final boolean isLastPaymentForPaymentMethodBad = lastPaymentForPaymentMethod != null &&
-                                                         (lastPaymentForPaymentMethod.getPaymentStatus() == PaymentStatus.PLUGIN_FAILURE_ABORTED ||
-                                                          lastPaymentForPaymentMethod.getPaymentStatus() == PaymentStatus.UNKNOWN);
-
-        if (isLastPaymentForPaymentMethodBad &&
-            !isInstantPayment &&
-            !isAccountAutoPayOff) {
-            log.warn(String.format("Setting account %s into AUTO_PAY_OFF because of bad payment %s", accountId, lastPaymentForPaymentMethod.getId()));
-            setAccountAutoPayOff(accountId, context);
+    private List<PaymentTransactionInfoPlugin> getPaymentTransactionInfoPlugins(final PaymentPluginApi plugin, final PaymentModelDao paymentModelDao, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentApiException {
+        try {
+            return plugin.getPaymentInfo(paymentModelDao.getAccountId(), paymentModelDao.getId(), properties, context);
+        } catch (final PaymentPluginApiException e) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_GET_PAYMENT_INFO, paymentModelDao.getId(), e.toString());
         }
     }
 
-    private BigDecimal getAndValidatePaymentAmount(final Invoice invoice, @Nullable final BigDecimal inputAmount, final boolean isInstantPayment)
-            throws PaymentApiException {
-
-        if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_NULL_INVOICE, invoice.getId());
-        }
-        if (isInstantPayment &&
-            inputAmount != null &&
-            invoice.getBalance().compareTo(inputAmount) < 0) {
-            throw new PaymentApiException(ErrorCode.PAYMENT_AMOUNT_DENIED,
-                                          invoice.getId(), inputAmount.floatValue(), invoice.getBalance().floatValue());
+    // Used in bulk get APIs (getPayments / searchPayments)
+    private Payment toPayment(final UUID paymentId, @Nullable final Iterable<PaymentTransactionInfoPlugin> pluginTransactions, final InternalTenantContext tenantContext) {
+        final PaymentModelDao paymentModelDao = paymentDao.getPayment(paymentId, tenantContext);
+        if (paymentModelDao == null) {
+            log.warn("Unable to find payment id " + paymentId);
+            return null;
         }
-        return inputAmount != null ? inputAmount : invoice.getBalance();
-    }
 
-    public void retryAutoPayOff(final UUID paymentId, final InternalCallContext context) {
-        retryFailedPaymentInternal(paymentId, context, PaymentStatus.AUTO_PAY_OFF);
+        return toPayment(paymentModelDao, pluginTransactions, tenantContext);
     }
 
-    public void retryPluginFailure(final UUID paymentId, final InternalCallContext context) {
+    // Used in single get APIs (getPayment / getPaymentByExternalKey)
+    private Payment toPayment(final PaymentModelDao paymentModelDao, final boolean withPluginInfo, final Iterable<PluginProperty> properties, final TenantContext context, final InternalTenantContext tenantContext) throws PaymentApiException {
+        final PaymentPluginApi plugin = getPaymentProviderPlugin(paymentModelDao.getPaymentMethodId(), tenantContext);
+        final List<PaymentTransactionInfoPlugin> pluginTransactions = withPluginInfo ? getPaymentTransactionInfoPlugins(plugin, paymentModelDao, properties, context) : null;
 
-        retryFailedPaymentInternal(paymentId, context, PaymentStatus.PLUGIN_FAILURE);
+        return toPayment(paymentModelDao, pluginTransactions, tenantContext);
     }
 
-    public void retryFailedPayment(final UUID paymentId, final InternalCallContext context) {
-        log.info("Retrying failed payment " + paymentId + " time = " + clock.getUTCNow());
-        retryFailedPaymentInternal(paymentId, context, PaymentStatus.PAYMENT_FAILURE);
-    }
+    private Payment toPayment(final PaymentModelDao paymentModelDao, @Nullable final Iterable<PaymentTransactionInfoPlugin> pluginTransactions, final InternalTenantContext tenantContext) {
+        final InternalTenantContext tenantContextWithAccountRecordId = getInternalTenantContextWithAccountRecordId(paymentModelDao.getAccountId(), tenantContext);
+        final List<PaymentTransactionModelDao> transactionsForPayment = paymentDao.getTransactionsForPayment(paymentModelDao.getId(), tenantContextWithAccountRecordId);
 
-    public void retryPaymentFromApi(final UUID paymentId, final InternalCallContext context) {
-        log.info("Retrying payment " + paymentId + " time = " + clock.getUTCNow());
-        retryFailedPaymentInternal(paymentId, context, PaymentStatus.UNKNOWN,
-                                   PaymentStatus.AUTO_PAY_OFF,
-                                   PaymentStatus.PAYMENT_FAILURE,
-                                   PaymentStatus.PLUGIN_FAILURE);
+        return toPayment(paymentModelDao, transactionsForPayment, pluginTransactions);
     }
 
-    private void retryFailedPaymentInternal(final UUID paymentId, final InternalCallContext context, final PaymentStatus... expectedPaymentStates) {
-
-        try {
-
-            final PaymentModelDao payment = paymentDao.getPayment(paymentId, context);
-            if (payment == null) {
-                log.error("Invalid retry for non existent paymentId {}", paymentId);
-                return;
-            }
-
-            if (isAccountAutoPayOff(payment.getAccountId(), context)) {
-                log.info(String.format("Skip retry payment %s in state %s because AUTO_PAY_OFF", payment.getId(), payment.getPaymentStatus()));
-                return;
+    // Used in bulk get API (getAccountPayments)
+    private Payment toPayment(final PaymentModelDao curPaymentModelDao, final Iterable<PaymentTransactionModelDao> transactionsModelDao, @Nullable final Iterable<PaymentTransactionInfoPlugin> pluginTransactions) {
+        final Ordering<PaymentTransaction> perPaymentTransactionOrdering = Ordering.<PaymentTransaction>from(new Comparator<PaymentTransaction>() {
+            @Override
+            public int compare(final PaymentTransaction o1, final PaymentTransaction o2) {
+                return o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
             }
+        });
 
-            final Account account = accountInternalApi.getAccountById(payment.getAccountId(), context);
-            final PaymentPluginApi plugin = getPaymentProviderPlugin(account, context);
-
-            voidPluginDispatcher.dispatchWithAccountLock(new CallableWithAccountLock<Void>(locker,
-                                                                                           account.getExternalKey(),
-                                                                                           new WithAccountLockCallback<Void>() {
-
-                                                                                               @Override
-                                                                                               public Void doOperation() throws PaymentApiException {
-                                                                                                   try {
-                                                                                                       // Fetch again with account lock this time
-                                                                                                       final PaymentModelDao payment = paymentDao.getPayment(paymentId, context);
-                                                                                                       boolean foundExpectedState = false;
-                                                                                                       for (final PaymentStatus cur : expectedPaymentStates) {
-                                                                                                           if (payment.getPaymentStatus() == cur) {
-                                                                                                               foundExpectedState = true;
-                                                                                                               break;
-                                                                                                           }
-                                                                                                       }
-                                                                                                       if (!foundExpectedState) {
-                                                                                                           log.info("Aborted retry for payment {} because it is {} state", paymentId, payment.getPaymentStatus());
-                                                                                                           return null;
-                                                                                                       }
-
-                                                                                                       final Invoice invoice = rebalanceAndGetInvoice(payment.getAccountId(), payment.getInvoiceId(), context);
-                                                                                                       if (invoice == null || invoice.isMigrationInvoice()) {
-                                                                                                           return null;
-                                                                                                       }
-                                                                                                       if (invoice.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
-                                                                                                           log.info("Aborted retry for payment {} because invoice has been paid", paymentId);
-                                                                                                           setTerminalStateOnRetryWithAccountLocked(account, invoice, payment, invoice.getBalance(), "Paid invoice", context);
-                                                                                                           return null;
-                                                                                                       }
-                                                                                                       processRetryPaymentWithAccountLocked(plugin, account, invoice, payment, invoice.getBalance(), context);
-                                                                                                       return null;
-                                                                                                   } catch (InvoiceApiException e) {
-                                                                                                       throw new PaymentApiException(e);
-                                                                                                   }
-                                                                                               }
-                                                                                           }));
-        } catch (AccountApiException e) {
-            log.error(String.format("Failed to retry payment for paymentId %s", paymentId), e);
-        } catch (PaymentApiException e) {
-            log.info(String.format("Failed to retry payment for paymentId %s", paymentId), e);
-        } catch (TimeoutException e) {
-            log.warn(String.format("Retry for payment %s timedout", paymentId));
-            // STEPH we should throw some exception so NotificationQ does not clear status and retries us
-        }
-    }
-
-    private Payment processNewPaymentForAutoPayOffWithAccountLocked(final UUID paymentMethodId, final Account account, final Invoice invoice,
-                                                                    final BigDecimal requestedAmount, final InternalCallContext context)
-            throws PaymentApiException {
-        final PaymentStatus paymentStatus = PaymentStatus.AUTO_PAY_OFF;
-
-        final PaymentModelDao paymentInfo = new PaymentModelDao(account.getId(), invoice.getId(), paymentMethodId, requestedAmount, invoice.getCurrency(), clock.getUTCNow(), paymentStatus);
-        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), paymentInfo.getId(), paymentMethodId, paymentStatus, clock.getUTCNow(),
-                                                                          requestedAmount, invoice.getCurrency());
-
-        paymentDao.insertPaymentWithFirstAttempt(paymentInfo, attempt, context);
-        return fromPaymentModelDao(paymentInfo, null, context);
-    }
-
-    private Payment processNewPaymentForMissingDefaultPaymentMethodWithAccountLocked(final Account account, final Invoice invoice,
-                                                                                     final BigDecimal requestedAmount, final InternalCallContext context)
-            throws PaymentApiException {
-        final PaymentStatus paymentStatus = PaymentStatus.PAYMENT_FAILURE_ABORTED;
-
-        final PaymentModelDao paymentInfo = new PaymentModelDao(account.getId(), invoice.getId(), MISSING_PAYMENT_METHOD_ID, requestedAmount, invoice.getCurrency(), clock.getUTCNow(), paymentStatus);
-        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), paymentInfo.getId(), MISSING_PAYMENT_METHOD_ID, paymentStatus, clock.getUTCNow(),
-                                                                          requestedAmount, invoice.getCurrency());
-
-        paymentDao.insertPaymentWithFirstAttempt(paymentInfo, attempt, context);
-        return fromPaymentModelDao(paymentInfo, null, context);
-    }
-
-    private Payment processNewPaymentWithAccountLocked(final UUID paymentMethodId, final PaymentPluginApi plugin, final Account account, final Invoice invoice,
-                                                       final BigDecimal requestedAmount, final boolean isInstantPayment, final InternalCallContext context) throws PaymentApiException {
-        final PaymentModelDao payment = new PaymentModelDao(account.getId(), invoice.getId(), paymentMethodId, requestedAmount, invoice.getCurrency(), clock.getUTCNow());
-        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), payment.getId(), paymentMethodId, clock.getUTCNow(),
-                                                                          requestedAmount, invoice.getCurrency());
-
-        final PaymentModelDao savedPayment = paymentDao.insertPaymentWithFirstAttempt(payment, attempt, context);
-        return processPaymentWithAccountLocked(plugin, account, invoice, savedPayment, attempt, isInstantPayment, context);
-    }
-
-    private Payment setTerminalStateOnRetryWithAccountLocked(final Account account, final Invoice invoice, final PaymentModelDao payment, final BigDecimal requestedAmount, final String terminalStateReason, final InternalCallContext context) {
-
-        final PaymentStatus paymentStatus;
-        switch (payment.getPaymentStatus()) {
-            case PAYMENT_FAILURE:
-                paymentStatus = PaymentStatus.PAYMENT_FAILURE_ABORTED;
-                break;
-
-            case PLUGIN_FAILURE:
-            case UNKNOWN:
-                paymentStatus = PaymentStatus.PLUGIN_FAILURE_ABORTED;
-                break;
-
-            case AUTO_PAY_OFF:
-            default:
-                throw new IllegalStateException("Unexpected payment status for retry " + payment.getPaymentStatus());
-        }
-        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), payment.getId(), account.getPaymentMethodId(), clock.getUTCNow(),
-                                                                          requestedAmount, invoice.getCurrency());
-        paymentDao.updatePaymentWithNewAttempt(payment.getId(), attempt, context);
-        paymentDao.updatePaymentAndAttemptOnCompletion(payment.getId(), paymentStatus, requestedAmount, account.getCurrency(), attempt.getId(), null, terminalStateReason, context);
-
-        final List<PaymentAttemptModelDao> allAttempts = paymentDao.getAttemptsForPayment(payment.getId(), context);
-        return new DefaultPayment(payment, null, allAttempts, Collections.<RefundModelDao>emptyList());
-
-    }
-
-    private Payment processRetryPaymentWithAccountLocked(final PaymentPluginApi plugin, final Account account, final Invoice invoice, final PaymentModelDao payment,
-                                                         final BigDecimal requestedAmount, final InternalCallContext context) throws PaymentApiException {
-        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), invoice.getId(), payment.getId(), account.getPaymentMethodId(), clock.getUTCNow(),
-                                                                          requestedAmount, invoice.getCurrency());
-        paymentDao.updatePaymentWithNewAttempt(payment.getId(), attempt, context);
-        return processPaymentWithAccountLocked(plugin, account, invoice, payment, attempt, false, context);
-    }
-
-    private Payment processPaymentWithAccountLocked(final PaymentPluginApi plugin, final Account account, final Invoice invoice,
-                                                    final PaymentModelDao paymentInput, final PaymentAttemptModelDao attemptInput, final boolean isInstantPayment, final InternalCallContext context)
-            throws PaymentApiException {
-        final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
-
-        List<PaymentAttemptModelDao> allAttempts = null;
-        if (paymentConfig.isPaymentOff()) {
-            paymentDao.updatePaymentAndAttemptOnCompletion(paymentInput.getId(), PaymentStatus.PAYMENT_SYSTEM_OFF,
-                                                           attemptInput.getRequestedAmount(), account.getCurrency(), attemptInput.getId(), null, null, context);
-            allAttempts = paymentDao.getAttemptsForPayment(paymentInput.getId(), context);
-            return new DefaultPayment(paymentInput, null, allAttempts, Collections.<RefundModelDao>emptyList());
-        }
-
-        PaymentModelDao payment = null;
-        BusInternalEvent event = null;
-        PaymentStatus paymentStatus;
-        final PaymentInfoPlugin paymentPluginInfo;
-        try {
-            try {
-                paymentPluginInfo = plugin.processPayment(account.getId(), paymentInput.getId(), attemptInput.getPaymentMethodId(),
-                                                          attemptInput.getRequestedAmount(), account.getCurrency(), context.toCallContext(tenantId));
-            } catch (RuntimeException e) {
-                // Handle case of plugin RuntimeException to be handled the same as a Plugin failure (PaymentPluginApiException)
-                final String formatError = String.format("Plugin threw RuntimeException for payment %s", paymentInput.getId());
-                throw new PaymentPluginApiException(formatError, e);
-            }
-            switch (paymentPluginInfo.getStatus()) {
-                case PROCESSED:
-                case PENDING:
-
-                    // Update Payment/PaymentAttempt status
-                    paymentStatus = paymentPluginInfo.getStatus() == PaymentPluginStatus.PROCESSED ? PaymentStatus.SUCCESS : PaymentStatus.PENDING;
-
-                    // In case of success we are using the amount/currency as returned by the plugin-- if plugin decides to mess with amount and currency, we track it there
-                    paymentDao.updatePaymentAndAttemptOnCompletion(paymentInput.getId(), paymentStatus, paymentPluginInfo.getAmount(), paymentPluginInfo.getCurrency(),
-                                                                   attemptInput.getId(), paymentPluginInfo.getGatewayErrorCode(), null, context);
-
-                    // Fetch latest objects
-                    allAttempts = paymentDao.getAttemptsForPayment(paymentInput.getId(), context);
-
-                    payment = paymentDao.getPayment(paymentInput.getId(), context);
-                    // NOTE that we are not using the amount/currency as returned by the plugin but the requested amount/currency; if plugin decide to change the currency
-                    // at the time of payment, we want to stay consistent with the currency on the account
-                    invoiceApi.notifyOfPayment(invoice.getId(),
-                                               payment.getAmount(),
-                                               payment.getCurrency(),
-                                               paymentPluginInfo.getCurrency(),
-                                               payment.getId(),
-                                               payment.getEffectiveDate(),
-                                               context);
-
-                    // Create Bus event
-                    event = new DefaultPaymentInfoEvent(account.getId(),
-                                                        invoice.getId(),
-                                                        payment.getId(),
-                                                        payment.getAmount(),
-                                                        payment.getPaymentNumber(),
-                                                        paymentStatus,
-                                                        payment.getEffectiveDate(),
-                                                        context.getAccountRecordId(),
-                                                        context.getTenantRecordId(),
-                                                        context.getUserToken());
-                    break;
-
-                case ERROR:
-                    allAttempts = paymentDao.getAttemptsForPayment(paymentInput.getId(), context);
-                    // Schedule if non instant payment and max attempt for retry not reached yet
-                    if (!isInstantPayment) {
-                        paymentStatus = scheduleRetryOnPaymentFailure(paymentInput.getId(), context);
-                    } else {
-                        paymentStatus = PaymentStatus.PAYMENT_FAILURE_ABORTED;
-                    }
-
-                    paymentDao.updatePaymentAndAttemptOnCompletion(paymentInput.getId(), paymentStatus, attemptInput.getRequestedAmount(), account.getCurrency(),
-                                                                   attemptInput.getId(), paymentPluginInfo.getGatewayErrorCode(), paymentPluginInfo.getGatewayError(), context);
-
-                    log.info(String.format("Could not process payment for account %s, invoice %s, error = %s",
-                                           account.getId(), invoice.getId(), paymentPluginInfo.getGatewayError()));
-
-                    event = new DefaultPaymentErrorEvent(account.getId(), invoice.getId(), paymentInput.getId(), paymentPluginInfo.getGatewayError(),
-                                                         context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()
-                    );
-                    throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT, account.getId(), paymentPluginInfo.getGatewayError());
-
-                default:
-                    final String formatError = String.format("Plugin return status %s for payment %s", paymentPluginInfo.getStatus(), paymentInput.getId());
-                    // This caught right below as a retryable Plugin failure
-                    throw new PaymentPluginApiException("", formatError);
+        // Need to filter for optimized codepaths looking up by account_record_id
+        final Iterable<PaymentTransactionModelDao> filteredTransactions = Iterables.filter(transactionsModelDao, new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao curPaymentTransactionModelDao) {
+                return curPaymentTransactionModelDao.getPaymentId().equals(curPaymentModelDao.getId());
             }
+        });
 
-        } catch (PaymentPluginApiException e) {
-            //
-            // An exception occurred, we are left in an unknown state, we need to schedule a retry
-            //
-            paymentStatus = isInstantPayment ? PaymentStatus.PAYMENT_FAILURE_ABORTED : scheduleRetryOnPluginFailure(paymentInput.getId(), context);
-            // STEPH message might need truncation to fit??
-
-            paymentDao.updatePaymentAndAttemptOnCompletion(paymentInput.getId(), paymentStatus, attemptInput.getRequestedAmount(), account.getCurrency(),
-                                                           attemptInput.getId(), null, e.getMessage(), context);
-
-            event = new DefaultPaymentPluginErrorEvent(account.getId(), invoice.getId(), paymentInput.getId(), e.getMessage(),
-                                                       context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken()
-            );
-            throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT, account.getId(), e.toString());
-
-        } catch (InvoiceApiException e) {
-            throw new PaymentApiException(ErrorCode.INVOICE_NOT_FOUND, invoice.getId(), e.toString());
-        } finally {
-            if (event != null) {
-                postPaymentEvent(event, account.getId(), context);
+        final Iterable<PaymentTransaction> transactions = Iterables.transform(filteredTransactions, new Function<PaymentTransactionModelDao, PaymentTransaction>() {
+            @Override
+            public PaymentTransaction apply(final PaymentTransactionModelDao paymentTransactionModelDao) {
+                final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin = pluginTransactions != null ?
+                                                                                  Iterables.tryFind(pluginTransactions, new Predicate<PaymentTransactionInfoPlugin>() {
+                                                                                      @Override
+                                                                                      public boolean apply(final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin) {
+                                                                                          return paymentTransactionModelDao.getId().equals(paymentTransactionInfoPlugin.getKbTransactionPaymentId());
+                                                                                      }
+                                                                                  }).orNull() : null;
+
+                return new DefaultPaymentTransaction(paymentTransactionModelDao.getId(), paymentTransactionModelDao.getAttemptId(), paymentTransactionModelDao.getTransactionExternalKey(), paymentTransactionModelDao.getCreatedDate(), paymentTransactionModelDao.getUpdatedDate(), paymentTransactionModelDao.getPaymentId(),
+                                                     paymentTransactionModelDao.getTransactionType(), paymentTransactionModelDao.getEffectiveDate(), paymentTransactionModelDao.getTransactionStatus(), paymentTransactionModelDao.getAmount(), paymentTransactionModelDao.getCurrency(),
+                                                     paymentTransactionModelDao.getProcessedAmount(), paymentTransactionModelDao.getProcessedCurrency(),
+                                                     paymentTransactionModelDao.getGatewayErrorCode(), paymentTransactionModelDao.getGatewayErrorMsg(), paymentTransactionInfoPlugin);
             }
-        }
-
-        return new DefaultPayment(payment, paymentPluginInfo, allAttempts, Collections.<RefundModelDao>emptyList());
-    }
-
-    private PaymentStatus scheduleRetryOnPluginFailure(final UUID paymentId, final InternalTenantContext context) {
-        final List<PaymentAttemptModelDao> allAttempts = paymentDao.getAttemptsForPayment(paymentId, context);
-        final int retryAttempt = getNumberAttemptsInState(allAttempts, PaymentStatus.UNKNOWN, PaymentStatus.PLUGIN_FAILURE);
-        final boolean isScheduledForRetry = pluginFailureRetryService.scheduleRetry(paymentId, retryAttempt);
-        return isScheduledForRetry ? PaymentStatus.PLUGIN_FAILURE : PaymentStatus.PLUGIN_FAILURE_ABORTED;
-    }
-
-    private PaymentStatus scheduleRetryOnPaymentFailure(final UUID paymentId, final InternalTenantContext context) {
-        final List<PaymentAttemptModelDao> allAttempts = paymentDao.getAttemptsForPayment(paymentId, context);
-        final int retryAttempt = getNumberAttemptsInState(allAttempts,
-                                                          PaymentStatus.UNKNOWN, PaymentStatus.PAYMENT_FAILURE);
-
-        final boolean isScheduledForRetry = failedPaymentRetryService.scheduleRetry(paymentId, retryAttempt);
-
-        log.debug("scheduleRetryOnPaymentFailure id = " + paymentId + ", retryAttempt = " + retryAttempt + ", retry :" + isScheduledForRetry);
+        });
 
-        return isScheduledForRetry ? PaymentStatus.PAYMENT_FAILURE : PaymentStatus.PAYMENT_FAILURE_ABORTED;
+        final List<PaymentTransaction> sortedTransactions = perPaymentTransactionOrdering.immutableSortedCopy(transactions);
+        return new DefaultPayment(curPaymentModelDao.getId(), curPaymentModelDao.getCreatedDate(), curPaymentModelDao.getUpdatedDate(), curPaymentModelDao.getAccountId(),
+                                  curPaymentModelDao.getPaymentMethodId(), curPaymentModelDao.getPaymentNumber(), curPaymentModelDao.getExternalKey(), sortedTransactions);
     }
 
-    private int getNumberAttemptsInState(final List<PaymentAttemptModelDao> allAttempts, final PaymentStatus... statuses) {
-        if (allAttempts == null || allAttempts.size() == 0) {
-            return 0;
+    private InternalTenantContext getInternalTenantContextWithAccountRecordId(final UUID accountId, final InternalTenantContext tenantContext) {
+        final InternalTenantContext tenantContextWithAccountRecordId;
+        if (tenantContext.getAccountRecordId() == null) {
+            tenantContextWithAccountRecordId = internalCallContextFactory.createInternalTenantContext(accountId, tenantContext);
+        } else {
+            tenantContextWithAccountRecordId = tenantContext;
         }
-        return Collections2.filter(allAttempts, new Predicate<PaymentAttemptModelDao>() {
-            @Override
-            public boolean apply(final PaymentAttemptModelDao input) {
-                for (final PaymentStatus cur : statuses) {
-                    if (input.getProcessingStatus() == cur) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-        }).size();
+        return tenantContextWithAccountRecordId;
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java
new file mode 100644
index 0000000..9f5fbda
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.killbill.automaton.MissingEntryException;
+import org.killbill.automaton.State;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.account.api.AccountApiException;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.RetryStateMachineHelper;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.name.Named;
+
+import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+
+public class PluginControlledPaymentProcessor extends ProcessorBase {
+
+    private final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner;
+    private final RetryStateMachineHelper retrySMHelper;
+    private final CacheControllerDispatcher controllerDispatcher;
+
+    @Inject
+    public PluginControlledPaymentProcessor(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
+                                            final AccountInternalApi accountInternalApi,
+                                            final InvoiceInternalApi invoiceApi,
+                                            final TagInternalApi tagUserApi,
+                                            final PaymentDao paymentDao,
+                                            final NonEntityDao nonEntityDao,
+                                            final GlobalLocker locker,
+                                            @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                                            final PluginControlledPaymentAutomatonRunner pluginControlledPaymentAutomatonRunner,
+                                            final RetryStateMachineHelper retrySMHelper,
+                                            final Clock clock,
+                                            final CacheControllerDispatcher controllerDispatcher) {
+        super(pluginRegistry, accountInternalApi, paymentDao, nonEntityDao, tagUserApi, locker, executor, invoiceApi, clock, controllerDispatcher);
+        this.retrySMHelper = retrySMHelper;
+        this.pluginControlledPaymentAutomatonRunner = pluginControlledPaymentAutomatonRunner;
+        this.controllerDispatcher = controllerDispatcher;
+    }
+
+    public Payment createAuthorization(final boolean isApiPayment, final Account account, final UUID paymentMethodId, @Nullable final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey, final String transactionExternalKey,
+                                             final Iterable<PluginProperty> properties, final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
+                                                                TransactionType.AUTHORIZE,
+                                                                account,
+                                                                paymentMethodId,
+                                                                paymentId,
+                                                                paymentExternalKey,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
+    }
+
+    public Payment createCapture(final boolean isApiPayment, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                       final String transactionExternalKey,
+                                       final Iterable<PluginProperty> properties, final String paymentControlPluginName,
+                                       final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
+                                                                TransactionType.CAPTURE,
+                                                                account,
+                                                                null,
+                                                                paymentId,
+                                                                null,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
+    }
+
+    public Payment createPurchase(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency,
+                                        final String paymentExternalKey, final String transactionExternalKey, final Iterable<PluginProperty> properties,
+                                        final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
+                                                                TransactionType.PURCHASE,
+                                                                account,
+                                                                paymentMethodId,
+                                                                paymentId,
+                                                                paymentExternalKey,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
+    }
+
+    public Payment createVoid(final boolean isApiPayment, final Account account, final UUID paymentId, final String transactionExternalKey,
+                                    final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
+                                                                TransactionType.VOID,
+                                                                account,
+                                                                null,
+                                                                paymentId,
+                                                                null,
+                                                                transactionExternalKey,
+                                                                null,
+                                                                null,
+                                                                properties,
+                                                                null,
+                                                                callContext, internalCallContext);
+    }
+
+    public Payment createRefund(final boolean isApiPayment, final Account account, final UUID paymentId, final BigDecimal amount, final Currency currency, final String transactionExternalKey,
+                                      final Iterable<PluginProperty> properties, final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
+                                                                TransactionType.REFUND,
+                                                                account,
+                                                                null,
+                                                                paymentId,
+                                                                null,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
+    }
+
+    public Payment createCredit(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID paymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey,
+                                      final String transactionExternalKey, final Iterable<PluginProperty> properties, final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        return pluginControlledPaymentAutomatonRunner.run(isApiPayment,
+                                                                TransactionType.CREDIT,
+                                                                account,
+                                                                paymentMethodId,
+                                                                paymentId,
+                                                                paymentExternalKey,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                properties,
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
+    }
+
+    public Payment createChargeback(final Account account, final UUID paymentId, final String transactionExternalKey, final BigDecimal amount, final Currency currency,
+                                          final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return pluginControlledPaymentAutomatonRunner.run(true,
+                                                                TransactionType.CHARGEBACK,
+                                                                account,
+                                                                null,
+                                                                paymentId,
+                                                                null,
+                                                                transactionExternalKey,
+                                                                amount,
+                                                                currency,
+                                                                ImmutableList.<PluginProperty>of(),
+                                                                paymentControlPluginName,
+                                                                callContext, internalCallContext);
+    }
+
+    public void retryPaymentTransaction(final UUID attemptId, final String pluginName, final InternalCallContext internalCallContext) {
+        try {
+
+            final PaymentAttemptModelDao attempt = paymentDao.getPaymentAttempt(attemptId, internalCallContext);
+            final PaymentModelDao payment = paymentDao.getPaymentByExternalKey(attempt.getPaymentExternalKey(), internalCallContext);
+            final UUID paymentId = payment != null ? payment.getId() : null;
+
+            final Iterable<PluginProperty> pluginProperties = PluginPropertySerializer.deserialize(attempt.getPluginProperties());
+            final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), internalCallContext);
+            final UUID tenantId = nonEntityDao.retrieveIdFromObject(internalCallContext.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID));
+            final CallContext callContext = internalCallContext.toCallContext(tenantId);
+
+            final State state = retrySMHelper.getState(attempt.getStateName());
+            pluginControlledPaymentAutomatonRunner.run(state,
+                                                             false,
+                                                             attempt.getTransactionType(),
+                                                             account,
+                                                             attempt.getPaymentMethodId(),
+                                                             paymentId,
+                                                             attempt.getPaymentExternalKey(),
+                                                             attempt.getTransactionExternalKey(),
+                                                             attempt.getAmount(),
+                                                             attempt.getCurrency(),
+                                                             pluginProperties,
+                                                             pluginName,
+                                                             callContext,
+                                                             internalCallContext);
+
+        } catch (AccountApiException e) {
+            log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
+        } catch (PaymentApiException e) {
+            log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
+        } catch (PluginPropertySerializerException e) {
+            log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
+        } catch (MissingEntryException e) {
+            log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
index 53ad47e..3cc6a94 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -24,39 +26,41 @@ import java.util.concurrent.ExecutorService;
 
 import javax.annotation.Nullable;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.commons.locker.GlobalLock;
-import org.killbill.commons.locker.GlobalLocker;
-import org.killbill.commons.locker.LockFailedException;
-import org.killbill.billing.events.BusInternalEvent;
-import org.killbill.billing.invoice.api.Invoice;
-import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentMethodModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.api.TagApiException;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.globallocker.LockerType;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.Tag;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.LockFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
 
 public abstract class ProcessorBase {
 
@@ -64,33 +68,37 @@ public abstract class ProcessorBase {
 
     protected final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
     protected final AccountInternalApi accountInternalApi;
-    protected final PersistentBus eventBus;
     protected final GlobalLocker locker;
     protected final ExecutorService executor;
     protected final PaymentDao paymentDao;
     protected final NonEntityDao nonEntityDao;
     protected final TagInternalApi tagInternalApi;
+    protected final Clock clock;
+    protected final CacheControllerDispatcher controllerDispatcher;
 
-    private static final Logger log = LoggerFactory.getLogger(ProcessorBase.class);
+    protected static final Logger log = LoggerFactory.getLogger(ProcessorBase.class);
     protected final InvoiceInternalApi invoiceApi;
 
     public ProcessorBase(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
                          final AccountInternalApi accountInternalApi,
-                         final PersistentBus eventBus,
                          final PaymentDao paymentDao,
                          final NonEntityDao nonEntityDao,
                          final TagInternalApi tagInternalApi,
                          final GlobalLocker locker,
-                         final ExecutorService executor, final InvoiceInternalApi invoiceApi) {
+                         final ExecutorService executor,
+                         final InvoiceInternalApi invoiceApi,
+                         final Clock clock,
+                         final CacheControllerDispatcher controllerDispatcher) {
         this.pluginRegistry = pluginRegistry;
         this.accountInternalApi = accountInternalApi;
-        this.eventBus = eventBus;
         this.paymentDao = paymentDao;
         this.nonEntityDao = nonEntityDao;
         this.locker = locker;
         this.executor = executor;
         this.tagInternalApi = tagInternalApi;
         this.invoiceApi = invoiceApi;
+        this.clock = clock;
+        this.controllerDispatcher = controllerDispatcher;
     }
 
     protected boolean isAccountAutoPayOff(final UUID accountId, final InternalTenantContext context) {
@@ -136,71 +144,75 @@ public abstract class ProcessorBase {
     }
 
     protected PaymentPluginApi getPaymentProviderPlugin(final Account account, final InternalTenantContext context) throws PaymentApiException {
+        final UUID paymentMethodId = getDefaultPaymentMethodId(account);
+        return getPaymentProviderPlugin(paymentMethodId, context);
+    }
+
+    protected UUID getDefaultPaymentMethodId(final Account account) throws PaymentApiException {
         final UUID paymentMethodId = account.getPaymentMethodId();
         if (paymentMethodId == null) {
             throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, account.getId());
         }
-        return getPaymentProviderPlugin(paymentMethodId, context);
+        return paymentMethodId;
     }
 
-    protected void postPaymentEvent(final BusInternalEvent ev, final UUID accountId, final InternalCallContext context) {
-        if (ev == null) {
-            return;
-        }
-        try {
-            eventBus.post(ev);
-        } catch (EventBusException e) {
-            log.error("Failed to post Payment event event for account {} ", accountId, e);
-        }
+    protected TenantContext buildTenantContext(final InternalTenantContext context) {
+        return context.toTenantContext(nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID)));
     }
 
-    protected Invoice rebalanceAndGetInvoice(final UUID accountId, final UUID invoiceId, final InternalCallContext context) throws InvoiceApiException {
-        invoiceApi.consumeExistingCBAOnAccountWithUnpaidInvoices(accountId, context);
-        final Invoice invoice = invoiceApi.getInvoiceById(invoiceId, context);
-        return invoice;
-    }
+    protected void validateUniqueTransactionExternalKey(@Nullable final String transactionExternalKey, final InternalTenantContext tenantContext) throws PaymentApiException {
+        // If no key specified, system will allocate a unique one later.
+        if (transactionExternalKey == null) {
+            return;
+        }
 
-    protected TenantContext buildTenantContext(final InternalTenantContext context) {
-        return context.toTenantContext(nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT));
+        final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(transactionExternalKey, tenantContext);
+        final PaymentTransactionModelDao transactionAlreadyExists = Iterables.tryFind(transactions, new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao input) {
+                return input.getTransactionStatus() == TransactionStatus.SUCCESS ||
+                       input.getTransactionStatus() == TransactionStatus.UNKNOWN;
+            }
+        }).orNull();
+        if (transactionAlreadyExists != null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS, transactionExternalKey);
+        }
     }
 
-    public interface WithAccountLockCallback<T> {
+    // TODO Rename - there is no lock!
+    public interface WithAccountLockCallback<PluginDispatcherReturnType, ExceptionType extends Exception> {
 
-        public T doOperation() throws PaymentApiException;
+        public PluginDispatcherReturnType doOperation() throws ExceptionType;
     }
 
-    public static class CallableWithAccountLock<T> implements Callable<T> {
+    public static class CallableWithAccountLock<ReturnType, ExceptionType extends Exception> implements Callable<PluginDispatcherReturnType<ReturnType>> {
 
         private final GlobalLocker locker;
         private final String accountExternalKey;
-        private final WithAccountLockCallback<T> callback;
+        private final WithAccountLockCallback<PluginDispatcherReturnType<ReturnType>, ExceptionType> callback;
 
         public CallableWithAccountLock(final GlobalLocker locker,
                                        final String accountExternalKey,
-                                       final WithAccountLockCallback<T> callback) {
+                                       final WithAccountLockCallback<PluginDispatcherReturnType<ReturnType>, ExceptionType> callback) {
             this.locker = locker;
             this.accountExternalKey = accountExternalKey;
             this.callback = callback;
         }
 
         @Override
-        public T call() throws Exception {
-            return new WithAccountLock<T>().processAccountWithLock(locker, accountExternalKey, callback);
+        public PluginDispatcherReturnType<ReturnType> call() throws ExceptionType, LockFailedException {
+            return new WithAccountLock<ReturnType, ExceptionType>().processAccountWithLock(locker, accountExternalKey, callback);
         }
     }
 
-    public static class WithAccountLock<T> {
+    public static class WithAccountLock<ReturnType, ExceptionType extends Exception> {
 
-        public T processAccountWithLock(final GlobalLocker locker, final String accountExternalKey, final WithAccountLockCallback<T> callback)
-                throws PaymentApiException {
+        public PluginDispatcherReturnType<ReturnType> processAccountWithLock(final GlobalLocker locker, final String accountExternalKey, final WithAccountLockCallback<PluginDispatcherReturnType<ReturnType>, ExceptionType> callback)
+                throws ExceptionType, LockFailedException {
             GlobalLock lock = null;
             try {
                 lock = locker.lockWithNumberOfTries(LockerType.ACCOUNT_FOR_INVOICE_PAYMENTS.toString(), accountExternalKey, NB_LOCK_TRY);
                 return callback.doOperation();
-            } catch (LockFailedException e) {
-                final String format = String.format("Failed to lock account %s", accountExternalKey);
-                log.error(String.format(format), e);
-                throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, format);
             } finally {
                 if (lock != null) {
                     lock.release();
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/AuthorizeOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/AuthorizeOperation.java
new file mode 100644
index 0000000..b7e2d37
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/AuthorizeOperation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AuthorizeOperation extends PaymentOperation {
+
+    private final Logger logger = LoggerFactory.getLogger(AuthorizeOperation.class);
+
+    public AuthorizeOperation(final PaymentAutomatonDAOHelper daoHelper,
+                              final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                              final PaymentStateContext paymentStateContext) throws PaymentApiException {
+        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+    }
+
+    @Override
+    protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+        logger.debug("Starting AUTHORIZE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
+        return plugin.authorizePayment(paymentStateContext.getAccount().getId(),
+                                       paymentStateContext.getPaymentId(),
+                                       paymentStateContext.getTransactionId(),
+                                       paymentStateContext.getPaymentMethodId(),
+                                       paymentStateContext.getAmount(),
+                                       paymentStateContext.getCurrency(),
+                                       paymentStateContext.getProperties(),
+                                       paymentStateContext.getCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/CaptureOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/CaptureOperation.java
new file mode 100644
index 0000000..b5e20ef
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/CaptureOperation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CaptureOperation extends PaymentOperation {
+
+    private final Logger logger = LoggerFactory.getLogger(CaptureOperation.class);
+
+    public CaptureOperation(final PaymentAutomatonDAOHelper daoHelper,
+                            final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                            final PaymentStateContext paymentStateContext) throws PaymentApiException {
+        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+    }
+
+    @Override
+    protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+        logger.debug("Starting CAPTURE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
+        return plugin.capturePayment(paymentStateContext.getAccount().getId(),
+                                     paymentStateContext.getPaymentId(),
+                                     paymentStateContext.getTransactionId(),
+                                     paymentStateContext.getPaymentMethodId(),
+                                     paymentStateContext.getAmount(),
+                                     paymentStateContext.getCurrency(),
+                                     paymentStateContext.getProperties(),
+                                     paymentStateContext.getCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java
new file mode 100644
index 0000000..04afc77
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/ChargebackOperation.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ChargebackOperation extends PaymentOperation {
+
+    private final Logger logger = LoggerFactory.getLogger(ChargebackOperation.class);
+
+    public ChargebackOperation(final PaymentAutomatonDAOHelper daoHelper,
+                               final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                               final PaymentStateContext paymentStateContext) throws PaymentApiException {
+        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+    }
+
+    @Override
+    protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+        logger.debug("Starting CHARGEBACK for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
+
+        final PaymentPluginStatus status;
+        if (!paymentStateContext.getOnLeavingStateExistingTransactions().isEmpty()) {
+            final Iterable<PaymentTransactionModelDao> purchaseTransactions = getOnLeavingStateExistingTransactionsForType(TransactionType.PURCHASE);
+            final Iterable<PaymentTransactionModelDao> captureTransactions = getOnLeavingStateExistingTransactionsForType(TransactionType.CAPTURE);
+            final Iterable<PaymentTransactionModelDao> refundTransactions = getOnLeavingStateExistingTransactionsForType(TransactionType.REFUND);
+            final Iterable<PaymentTransactionModelDao> chargebackTransactions = getOnLeavingStateExistingTransactionsForType(TransactionType.CHARGEBACK);
+
+            final BigDecimal purchasedAmount = getSumAmount(purchaseTransactions);
+            final BigDecimal capturedAmount = getSumAmount(captureTransactions);
+            final BigDecimal refundedAmount = getSumAmount(refundTransactions);
+            final BigDecimal chargebackAmount = getSumAmount(chargebackTransactions);
+            final BigDecimal chargebackAvailableAmount = purchasedAmount.add(capturedAmount).subtract(refundedAmount.add(chargebackAmount));
+
+            if (paymentStateContext.getAmount().compareTo(chargebackAvailableAmount) > 0) {
+                status = PaymentPluginStatus.ERROR;
+            } else {
+                status = PaymentPluginStatus.PROCESSED;
+            }
+        } else {
+            status = PaymentPluginStatus.PROCESSED;
+        }
+        return new DefaultNoOpPaymentInfoPlugin(paymentStateContext.getPaymentId(),
+                                                 paymentStateContext.getTransactionId(),
+                                                 TransactionType.CHARGEBACK,
+                                                 paymentStateContext.getAmount(),
+                                                 paymentStateContext.getCurrency(),
+                                                 null,
+                                                 null,
+                                                 status,
+                                                 null);
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/CreditOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/CreditOperation.java
new file mode 100644
index 0000000..06be146
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/CreditOperation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CreditOperation extends PaymentOperation {
+
+    private final Logger logger = LoggerFactory.getLogger(CreditOperation.class);
+
+    public CreditOperation(final PaymentAutomatonDAOHelper daoHelper,
+                           final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                           final PaymentStateContext paymentStateContext) throws PaymentApiException {
+        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+    }
+
+    @Override
+    protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+        logger.debug("Starting CREDIT for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
+        return plugin.creditPayment(paymentStateContext.getAccount().getId(),
+                                    paymentStateContext.getPaymentId(),
+                                    paymentStateContext.getTransactionId(),
+                                    paymentStateContext.getPaymentMethodId(),
+                                    paymentStateContext.getAmount(),
+                                    paymentStateContext.getCurrency(),
+                                    paymentStateContext.getProperties(),
+                                    paymentStateContext.getCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
new file mode 100644
index 0000000..6a81f1d
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/OperationCallbackBase.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.payment.core.ProcessorBase.CallableWithAccountLock;
+import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class OperationCallbackBase<CallbackOperationResult, CallbackOperationException extends Exception> {
+
+    protected final Logger logger = LoggerFactory.getLogger(OperationCallbackBase.class);
+
+    private final GlobalLocker locker;
+    private final PluginDispatcher<OperationResult> paymentPluginDispatcher;
+
+    protected final PaymentStateContext paymentStateContext;
+
+    protected OperationCallbackBase(final GlobalLocker locker,
+                                    final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                                    final PaymentStateContext paymentStateContext) {
+        this.locker = locker;
+        this.paymentPluginDispatcher = paymentPluginDispatcher;
+        this.paymentStateContext = paymentStateContext;
+    }
+
+    //
+    // Dispatch the Callable to the executor by first wrapping it into a CallableWithAccountLock
+    // The dispatcher may throw a TimeoutException, ExecutionException, or InterruptedException; those will be handled in specific
+    // callback to eventually throw a OperationException, that will be used to drive the state machine in the right direction.
+    //
+    protected <ExceptionType extends Exception> OperationResult dispatchWithAccountLockAndTimeout(final WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, ExceptionType> callback) throws OperationException {
+        final Account account = paymentStateContext.getAccount();
+        logger.debug("Dispatching plugin call for account {}", account.getExternalKey());
+
+        try {
+            final Callable<PluginDispatcherReturnType<OperationResult>> task = new CallableWithAccountLock<OperationResult, ExceptionType>(locker,
+                                                                                                                                           account.getExternalKey(),
+                                                                                                                                           callback);
+            final OperationResult operationResult = paymentPluginDispatcher.dispatchWithTimeout(task);
+            logger.debug("Successful plugin call for account {} with result {}", account.getExternalKey(), operationResult);
+            return operationResult;
+        } catch (final ExecutionException e) {
+            throw rewrapExecutionException(paymentStateContext, e);
+        } catch (final TimeoutException e) {
+            throw wrapTimeoutException(paymentStateContext, e);
+        } catch (final InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw wrapInterruptedException(paymentStateContext, e);
+        }
+    }
+
+    //
+    // The OperationCallback per state machine are often very similar in between operation
+    //
+    // There is a base glue code that is common to all calls and shared in a base class and then a per call specific operation
+    // using the doCallSpecificOperationCallback method below.
+    //
+    protected abstract CallbackOperationResult doCallSpecificOperationCallback() throws CallbackOperationException;
+
+    //
+    // The methods below allow to convert the exceptions thrown back by the Executor into an appropriate  OperationException
+    //
+    protected abstract OperationException rewrapExecutionException(final PaymentStateContext paymentStateContext, final ExecutionException e);
+
+    protected abstract OperationException wrapTimeoutException(final PaymentStateContext paymentStateContext, final TimeoutException e);
+
+    protected abstract OperationException wrapInterruptedException(final PaymentStateContext paymentStateContext, final InterruptedException e);
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
new file mode 100644
index 0000000..55fcb3f
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonDAOHelper.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.events.BusInternalEvent;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.DefaultPaymentErrorEvent;
+import org.killbill.billing.payment.api.DefaultPaymentInfoEvent;
+import org.killbill.billing.payment.api.DefaultPaymentPluginErrorEvent;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentMethodModelDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+public class PaymentAutomatonDAOHelper {
+
+    private static final Logger log = LoggerFactory.getLogger(PaymentAutomatonDAOHelper.class);
+
+    protected final PaymentStateContext paymentStateContext;
+    protected final DateTime utcNow;
+    protected final InternalCallContext internalCallContext;
+    protected final PaymentStateMachineHelper paymentSMHelper;
+
+    protected final PaymentDao paymentDao;
+
+    private final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
+    private final PersistentBus eventBus;
+
+    // Used to build new payments and transactions
+    public PaymentAutomatonDAOHelper(final PaymentStateContext paymentStateContext,
+                                     final DateTime utcNow, final PaymentDao paymentDao,
+                                     final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
+                                     final InternalCallContext internalCallContext,
+                                     final PersistentBus eventBus,
+                                     final PaymentStateMachineHelper paymentSMHelper) throws PaymentApiException {
+        this.paymentStateContext = paymentStateContext;
+        this.utcNow = utcNow;
+        this.paymentDao = paymentDao;
+        this.pluginRegistry = pluginRegistry;
+        this.internalCallContext = internalCallContext;
+        this.eventBus = eventBus;
+        this.paymentSMHelper = paymentSMHelper;
+    }
+
+    public void createNewPaymentTransaction() throws PaymentApiException {
+
+        final PaymentTransactionModelDao paymentTransactionModelDao;
+        final List<PaymentTransactionModelDao> existingTransactions;
+        if (paymentStateContext.getPaymentId() == null) {
+            final PaymentModelDao newPaymentModelDao = buildNewPaymentModelDao();
+            final PaymentTransactionModelDao newPaymentTransactionModelDao = buildNewPaymentTransactionModelDao(newPaymentModelDao.getId());
+
+            existingTransactions = ImmutableList.of();
+            final PaymentModelDao paymentModelDao = paymentDao.insertPaymentWithFirstTransaction(newPaymentModelDao, newPaymentTransactionModelDao, internalCallContext);
+            paymentTransactionModelDao = paymentDao.getTransactionsForPayment(paymentModelDao.getId(), internalCallContext).get(0);
+
+        } else {
+            existingTransactions = paymentDao.getTransactionsForPayment(paymentStateContext.getPaymentId(), internalCallContext);
+            if (existingTransactions.isEmpty()) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_SUCCESS_PAYMENT, paymentStateContext.getPaymentId());
+            }
+            if (paymentStateContext.getCurrency() != null && existingTransactions.get(0).getCurrency() != paymentStateContext.getCurrency()) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "currency", " should be " + existingTransactions.get(0).getCurrency() + " to match other existing transactions");
+            }
+
+            final PaymentTransactionModelDao newPaymentTransactionModelDao = buildNewPaymentTransactionModelDao(paymentStateContext.getPaymentId());
+            paymentTransactionModelDao = paymentDao.updatePaymentWithNewTransaction(paymentStateContext.getPaymentId(), newPaymentTransactionModelDao, internalCallContext);
+        }
+        // Update the context
+        paymentStateContext.setPaymentTransactionModelDao(paymentTransactionModelDao);
+        paymentStateContext.setOnLeavingStateExistingTransactions(existingTransactions);
+    }
+
+    public void processPaymentInfoPlugin(final TransactionStatus paymentStatus, @Nullable final PaymentTransactionInfoPlugin paymentInfoPlugin,
+                                         final String currentPaymentStateName) {
+        final BigDecimal processedAmount = paymentInfoPlugin == null ? null : paymentInfoPlugin.getAmount();
+        final Currency processedCurrency = paymentInfoPlugin == null ? null : paymentInfoPlugin.getCurrency();
+        final String gatewayErrorCode = paymentInfoPlugin == null ? null : paymentInfoPlugin.getGatewayErrorCode();
+        final String gatewayErrorMsg = paymentInfoPlugin == null ? null : paymentInfoPlugin.getGatewayError();
+
+        final String lastSuccessPaymentState = paymentSMHelper.isSuccessState(currentPaymentStateName) ? currentPaymentStateName : null;
+        paymentDao.updatePaymentAndTransactionOnCompletion(paymentStateContext.getAccount().getId(),
+                                                           paymentStateContext.getPaymentId(),
+                                                           paymentStateContext.getTransactionType(),
+                                                           currentPaymentStateName,
+                                                           lastSuccessPaymentState,
+                                                           paymentStateContext.getPaymentTransactionModelDao().getId(),
+                                                           paymentStatus,
+                                                           processedAmount,
+                                                           processedCurrency,
+                                                           gatewayErrorCode,
+                                                           gatewayErrorMsg,
+                                                           internalCallContext);
+
+        // Update the context
+        paymentStateContext.setPaymentTransactionModelDao(paymentDao.getPaymentTransaction(paymentStateContext.getPaymentTransactionModelDao().getId(), internalCallContext));
+    }
+
+    public PaymentPluginApi getPaymentProviderPlugin() throws PaymentApiException {
+
+        final UUID paymentMethodId = paymentStateContext.getPaymentMethodId();
+        final PaymentMethodModelDao methodDao = paymentDao.getPaymentMethodIncludedDeleted(paymentMethodId, internalCallContext);
+        if (methodDao == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD, paymentMethodId);
+        }
+        return getPaymentPluginApi(methodDao.getPluginName());
+    }
+
+    public PaymentModelDao getPayment() throws PaymentApiException {
+        final PaymentModelDao paymentModelDao;
+        paymentModelDao = paymentDao.getPayment(paymentStateContext.getPaymentId(), internalCallContext);
+        if (paymentModelDao == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentStateContext.getPaymentId());
+        }
+        return paymentModelDao;
+    }
+
+    public PersistentBus getEventBus() {
+        return eventBus;
+    }
+
+    private PaymentModelDao buildNewPaymentModelDao() {
+        final DateTime createdDate = utcNow;
+        final DateTime updatedDate = utcNow;
+
+        return new PaymentModelDao(createdDate,
+                                   updatedDate,
+                                   paymentStateContext.getAccount().getId(),
+                                   paymentStateContext.getPaymentMethodId(),
+                                   paymentStateContext.getPaymentExternalKey());
+    }
+
+    private PaymentTransactionModelDao buildNewPaymentTransactionModelDao(final UUID paymentId) {
+        final DateTime createdDate = utcNow;
+        final DateTime updatedDate = utcNow;
+        final DateTime effectiveDate = utcNow;
+        final String gatewayErrorCode = null;
+        final String gatewayErrorMsg = null;
+
+        return new PaymentTransactionModelDao(createdDate,
+                                              updatedDate,
+                                              paymentStateContext.getAttemptId(),
+                                              paymentStateContext.getPaymentTransactionExternalKey(),
+                                              paymentId,
+                                              paymentStateContext.getTransactionType(),
+                                              effectiveDate,
+                                              TransactionStatus.UNKNOWN,
+                                              paymentStateContext.getAmount(),
+                                              paymentStateContext.getCurrency(),
+                                              gatewayErrorCode,
+                                              gatewayErrorMsg);
+    }
+
+    private PaymentPluginApi getPaymentPluginApi(final String pluginName) throws PaymentApiException {
+        final PaymentPluginApi pluginApi = pluginRegistry.getServiceForName(pluginName);
+        if (pluginApi == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT_PLUGIN, pluginName);
+        }
+        return pluginApi;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
new file mode 100644
index 0000000..eeeaba6
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import org.joda.time.DateTime;
+import org.killbill.automaton.MissingEntryException;
+import org.killbill.automaton.Operation;
+import org.killbill.automaton.Operation.OperationCallback;
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.OperationResult;
+import org.killbill.automaton.State;
+import org.killbill.automaton.State.EnteringStateCallback;
+import org.killbill.automaton.State.LeavingStateCallback;
+import org.killbill.automaton.StateMachine;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.inject.name.Named;
+
+import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+
+public class PaymentAutomatonRunner {
+
+    protected final PaymentStateMachineHelper paymentSMHelper;
+    protected final PaymentDao paymentDao;
+    protected final GlobalLocker locker;
+    protected final PluginDispatcher<OperationResult> paymentPluginDispatcher;
+    protected final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
+    protected final Clock clock;
+    private final PersistentBus eventBus;
+
+    @Inject
+    public PaymentAutomatonRunner(@javax.inject.Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig,
+                                  final PaymentConfig paymentConfig,
+                                  final PaymentDao paymentDao,
+                                  final GlobalLocker locker,
+                                  final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
+                                  final Clock clock,
+                                  @Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                                  final PersistentBus eventBus,
+                                  final PaymentStateMachineHelper paymentSMHelper) {
+        this.paymentSMHelper = paymentSMHelper;
+        this.paymentDao = paymentDao;
+        this.locker = locker;
+        this.pluginRegistry = pluginRegistry;
+        this.clock = clock;
+        this.eventBus = eventBus;
+
+        final long paymentPluginTimeoutSec = TimeUnit.SECONDS.convert(paymentConfig.getPaymentPluginTimeout().getPeriod(), paymentConfig.getPaymentPluginTimeout().getUnit());
+        this.paymentPluginDispatcher = new PluginDispatcher<OperationResult>(paymentPluginTimeoutSec, executor);
+
+    }
+
+    public UUID run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID attemptId, @Nullable final UUID paymentMethodId,
+                    @Nullable final UUID paymentId,  @Nullable final UUID transactionId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
+                    @Nullable final BigDecimal amount, @Nullable final Currency currency,
+                    final boolean shouldLockAccount, final OperationResult overridePluginOperationResult, final Iterable<PluginProperty> properties,
+                    final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        final DateTime utcNow = clock.getUTCNow();
+
+        final PaymentStateContext paymentStateContext = new PaymentStateContext(isApiPayment, paymentId, transactionId, attemptId,  paymentExternalKey, paymentTransactionExternalKey, transactionType,
+                                                                                                  account, paymentMethodId, amount, currency, shouldLockAccount, overridePluginOperationResult, properties, internalCallContext, callContext);
+
+        final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, utcNow, paymentDao, pluginRegistry, internalCallContext, eventBus, paymentSMHelper);
+
+        final UUID effectivePaymentMethodId;
+        final String currentStateName;
+        if (paymentId != null) {
+            final PaymentModelDao paymentModelDao = daoHelper.getPayment();
+            effectivePaymentMethodId = paymentModelDao.getPaymentMethodId();
+            currentStateName = paymentModelDao.getLastSuccessStateName() != null ? paymentModelDao.getLastSuccessStateName() : paymentSMHelper.getInitStateNameForTransaction();
+
+            // Check for illegal states (should never happen)
+            Preconditions.checkState(currentStateName != null, "State name cannot be null for payment " + paymentId);
+            Preconditions.checkState(paymentMethodId == null || effectivePaymentMethodId.equals(paymentMethodId), "Specified payment method id " + paymentMethodId + " doesn't match the one on the payment " + effectivePaymentMethodId);
+        } else {
+            // If the payment method is not specified, retrieve the default one on the account; it could still be null, in which case
+            //
+            effectivePaymentMethodId = paymentMethodId != null ? paymentMethodId : account.getPaymentMethodId();
+            currentStateName = paymentSMHelper.getInitStateNameForTransaction();
+        }
+
+        paymentStateContext.setPaymentMethodId(effectivePaymentMethodId);
+
+        final OperationCallback operationCallback;
+        final LeavingStateCallback leavingStateCallback;
+        final EnteringStateCallback enteringStateCallback;
+        switch (transactionType) {
+            case PURCHASE:
+                operationCallback = new PurchaseOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                leavingStateCallback = new PurchaseInitiated(daoHelper, paymentStateContext);
+                enteringStateCallback = new PurchaseCompleted(daoHelper, paymentStateContext);
+                break;
+            case AUTHORIZE:
+                operationCallback = new AuthorizeOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                leavingStateCallback = new AuthorizeInitiated(daoHelper, paymentStateContext);
+                enteringStateCallback = new AuthorizeCompleted(daoHelper, paymentStateContext);
+                break;
+            case CAPTURE:
+                operationCallback = new CaptureOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                leavingStateCallback = new CaptureInitiated(daoHelper, paymentStateContext);
+                enteringStateCallback = new CaptureCompleted(daoHelper, paymentStateContext);
+                break;
+            case VOID:
+                operationCallback = new VoidOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                leavingStateCallback = new VoidInitiated(daoHelper, paymentStateContext);
+                enteringStateCallback = new VoidCompleted(daoHelper, paymentStateContext);
+                break;
+            case REFUND:
+                operationCallback = new RefundOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                leavingStateCallback = new RefundInitiated(daoHelper, paymentStateContext);
+                enteringStateCallback = new RefundCompleted(daoHelper, paymentStateContext);
+                break;
+            case CREDIT:
+                operationCallback = new CreditOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                leavingStateCallback = new CreditInitiated(daoHelper, paymentStateContext);
+                enteringStateCallback = new CreditCompleted(daoHelper, paymentStateContext);
+                break;
+            case CHARGEBACK:
+                operationCallback = new ChargebackOperation(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+                leavingStateCallback = new ChargebackInitiated(daoHelper, paymentStateContext);
+                enteringStateCallback = new ChargebackCompleted(daoHelper, paymentStateContext);
+                break;
+            default:
+                throw new IllegalStateException("Unsupported transaction type " + transactionType);
+        }
+
+        runStateMachineOperation(currentStateName, transactionType, leavingStateCallback, operationCallback, enteringStateCallback);
+
+        return paymentStateContext.getPaymentId();
+    }
+
+    protected void runStateMachineOperation(final String initialStateName, final TransactionType transactionType,
+                                            final LeavingStateCallback leavingStateCallback, final OperationCallback operationCallback, final EnteringStateCallback enteringStateCallback) throws PaymentApiException {
+        try {
+            final StateMachine initialStateMachine = paymentSMHelper.getStateMachineForStateName(initialStateName);
+            final State initialState = initialStateMachine.getState(initialStateName);
+            final Operation operation = paymentSMHelper.getOperationForTransaction(transactionType);
+
+            initialState.runOperation(operation, operationCallback, enteringStateCallback, leavingStateCallback);
+        } catch (final MissingEntryException e) {
+            throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INVALID_OPERATION, transactionType, initialStateName);
+        } catch (final OperationException e) {
+            if (e.getCause() == null) {
+                throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+            } else if (e.getCause() instanceof PaymentApiException) {
+                throw (PaymentApiException) e.getCause();
+            } else {
+                throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+            }
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java
new file mode 100644
index 0000000..fd4d928
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentEnteringStateCallback.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import javax.annotation.Nullable;
+
+import org.killbill.automaton.Operation;
+import org.killbill.automaton.OperationResult;
+import org.killbill.automaton.State;
+import org.killbill.automaton.State.EnteringStateCallback;
+import org.killbill.automaton.State.LeavingStateCallback;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.events.BusInternalEvent;
+import org.killbill.billing.payment.api.DefaultPaymentErrorEvent;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class PaymentEnteringStateCallback implements EnteringStateCallback {
+
+    private final Logger logger = LoggerFactory.getLogger(PaymentEnteringStateCallback.class);
+
+    protected final PaymentAutomatonDAOHelper daoHelper;
+    protected final PaymentStateContext paymentStateContext;
+
+    protected PaymentEnteringStateCallback(final PaymentAutomatonDAOHelper daoHelper, final PaymentStateContext paymentStateContext) throws PaymentApiException {
+        this.daoHelper = daoHelper;
+        this.paymentStateContext = paymentStateContext;
+    }
+
+    @Override
+    public void enteringState(final State newState, final Operation.OperationCallback operationCallback, final OperationResult operationResult, final LeavingStateCallback leavingStateCallback) {
+        logger.debug("Entering state {} with result {}", newState.getName(), operationResult);
+
+        // If the transaction was not created -- for instance we had an exception in leavingState callback then we bail; if not, then update state:
+        if (paymentStateContext.getPaymentTransactionModelDao() != null && paymentStateContext.getPaymentTransactionModelDao().getId() != null) {
+            final PaymentTransactionInfoPlugin paymentInfoPlugin = paymentStateContext.getPaymentInfoPlugin();
+            final TransactionStatus paymentStatus = paymentPluginStatusToPaymentStatus(paymentInfoPlugin, operationResult);
+            daoHelper.processPaymentInfoPlugin(paymentStatus, paymentInfoPlugin, newState.getName());
+        } else if (!paymentStateContext.isApiPayment()) {
+            //
+            // If there is NO transaction to update (because payment transaction did not occur), then there is something wrong happening (maybe a missing defaultPaymentMethodId, ...)
+            // so, if the does NOT call originates from api then we still want to send a bus event so the system can react to it if needed.
+            //
+            final BusInternalEvent event = new DefaultPaymentErrorEvent(paymentStateContext.getAccount().getId(),
+                                                                        null,
+                                                                        paymentStateContext.getTransactionType(),
+                                                                        "Early abortion of payment transaction",
+                                                                        paymentStateContext.getInternalCallContext().getAccountRecordId(),
+                                                                        paymentStateContext.getInternalCallContext().getTenantRecordId(),
+                                                                        paymentStateContext.getInternalCallContext().getUserToken());
+            try {
+                daoHelper.getEventBus().post(event);
+            } catch (EventBusException e) {
+                logger.error("Failed to post Payment event event for account {} ", paymentStateContext.getAccount().getId(), e);
+            }
+        }
+    }
+
+    private TransactionStatus paymentPluginStatusToPaymentStatus(@Nullable final PaymentTransactionInfoPlugin paymentInfoPlugin, final OperationResult operationResult) {
+        if (paymentInfoPlugin == null) {
+            if (OperationResult.EXCEPTION.equals(operationResult)) {
+                // We got an exception during the plugin call
+                return TransactionStatus.PLUGIN_FAILURE;
+            } else {
+                // The plugin completed the call but returned null?! Bad plugin...
+                return TransactionStatus.PLUGIN_FAILURE;
+            }
+        }
+
+        if (paymentInfoPlugin.getStatus() == null) {
+            // The plugin completed the call but returned an incomplete PaymentInfoPlugin?! Bad plugin...
+            return TransactionStatus.UNKNOWN;
+        }
+
+        switch (paymentInfoPlugin.getStatus()) {
+            case UNDEFINED:
+                return TransactionStatus.UNKNOWN;
+            case PROCESSED:
+                return TransactionStatus.SUCCESS;
+            case PENDING:
+                return TransactionStatus.PENDING;
+            case ERROR:
+                return TransactionStatus.PAYMENT_FAILURE;
+            default:
+                return TransactionStatus.UNKNOWN;
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentLeavingStateCallback.java
new file mode 100644
index 0000000..898669e
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentLeavingStateCallback.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.State;
+import org.killbill.automaton.State.LeavingStateCallback;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class PaymentLeavingStateCallback implements LeavingStateCallback {
+
+    private final Logger logger = LoggerFactory.getLogger(PaymentLeavingStateCallback.class);
+
+    protected final PaymentAutomatonDAOHelper daoHelper;
+    protected final PaymentStateContext paymentStateContext;
+
+    protected PaymentLeavingStateCallback(final PaymentAutomatonDAOHelper daoHelper, final PaymentStateContext paymentStateContext) throws PaymentApiException {
+        this.daoHelper = daoHelper;
+        this.paymentStateContext = paymentStateContext;
+    }
+
+    @Override
+    public void leavingState(final State oldState) throws OperationException {
+        logger.debug("Leaving state {}", oldState.getName());
+
+        // Create or update the payment and transaction
+        try {
+            // No paymentMethodId was passed through API and account does not have a default paymentMethodId
+            if (paymentStateContext.getPaymentMethodId() == null) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD, paymentStateContext.getAccount().getId());
+            }
+
+            // If the transactionId has been specified, it means this is an operation in several stage (INIT -> PENDING -> XXX), so we don't need to create the row,
+            // but we do need to set the transactionModelDao in the context so enteringState logic can take place.
+            if (paymentStateContext.getTransactionId() == null) {
+                daoHelper.createNewPaymentTransaction();
+            } else {
+                final PaymentTransactionModelDao transactionModelDao =  daoHelper.paymentDao.getPaymentTransaction(paymentStateContext.getTransactionId(), paymentStateContext.getInternalCallContext());
+                paymentStateContext.setPaymentTransactionModelDao(transactionModelDao);
+            }
+        } catch (PaymentApiException e) {
+            throw new OperationException(e);
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java
new file mode 100644
index 0000000..e2b2a65
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentOperation.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.Iterator;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.killbill.automaton.Operation.OperationCallback;
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.payment.provider.DefaultNoOpPaymentInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.LockFailedException;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+// Encapsulates the payment specific logic
+public abstract class PaymentOperation extends OperationCallbackBase<PaymentTransactionInfoPlugin, PaymentPluginApiException> implements OperationCallback {
+
+    protected final PaymentAutomatonDAOHelper daoHelper;
+    protected PaymentPluginApi plugin;
+
+    protected PaymentOperation(final GlobalLocker locker,
+                               final PaymentAutomatonDAOHelper daoHelper,
+                               final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                               final PaymentStateContext paymentStateContext) {
+        super(locker, paymentPluginDispatcher, paymentStateContext);
+        this.daoHelper = daoHelper;
+    }
+
+    @Override
+    public OperationResult doOperationCallback() throws OperationException {
+        try {
+            this.plugin = daoHelper.getPaymentProviderPlugin();
+
+            if (paymentStateContext.shouldLockAccountAndDispatch()) {
+                return doOperationCallbackWithDispatchAndAccountLock();
+            } else {
+                return doSimpleOperationCallback();
+            }
+        } catch (final PaymentApiException e) {
+            throw new OperationException(e, OperationResult.EXCEPTION);
+        }
+    }
+
+    @Override
+    protected OperationException rewrapExecutionException(final PaymentStateContext paymentStateContext, final ExecutionException e) {
+        final Throwable realException = Objects.firstNonNull(e.getCause(), e);
+        if (e.getCause() instanceof PaymentApiException) {
+            logger.warn("Unsuccessful plugin call for account {}", paymentStateContext.getAccount().getExternalKey(), realException);
+            return new OperationException(realException, OperationResult.FAILURE);
+        } else if (e.getCause() instanceof LockFailedException) {
+            final String format = String.format("Failed to lock account %s", paymentStateContext.getAccount().getExternalKey());
+            logger.error(String.format(format), e);
+            return new OperationException(realException, OperationResult.FAILURE);
+        } else /* if (e instanceof RuntimeException) */ {
+            logger.warn("Plugin call threw an exception for account {}", paymentStateContext.getAccount().getExternalKey(), e);
+            return new OperationException(realException, OperationResult.EXCEPTION);
+        }
+    }
+
+    @Override
+    protected OperationException wrapTimeoutException(final PaymentStateContext paymentStateContext, final TimeoutException e) {
+        logger.error("Plugin call TIMEOUT for account {}: {}", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+        return new OperationException(e, OperationResult.EXCEPTION);
+    }
+
+    @Override
+    protected OperationException wrapInterruptedException(final PaymentStateContext paymentStateContext, final InterruptedException e) {
+        logger.error("Plugin call was interrupted for account {}: {}", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+        return new OperationException(e, OperationResult.EXCEPTION);
+    }
+
+    @Override
+    protected abstract PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException;
+
+    protected Iterable<PaymentTransactionModelDao> getOnLeavingStateExistingTransactionsForType(final TransactionType transactionType) {
+        if (paymentStateContext.getOnLeavingStateExistingTransactions() == null || paymentStateContext.getOnLeavingStateExistingTransactions().isEmpty()) {
+            return ImmutableList.of();
+        }
+        return Iterables.filter(paymentStateContext.getOnLeavingStateExistingTransactions(), new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao input) {
+                return input.getTransactionStatus() == TransactionStatus.SUCCESS && input.getTransactionType() == transactionType;
+            }
+        });
+    }
+
+    protected BigDecimal getSumAmount(final Iterable<PaymentTransactionModelDao> transactions) {
+        BigDecimal result = BigDecimal.ZERO;
+        final Iterator<PaymentTransactionModelDao> iterator = transactions.iterator();
+        while (iterator.hasNext()) {
+            result = result.add(iterator.next().getAmount());
+        }
+        return result;
+    }
+
+    private OperationResult doOperationCallbackWithDispatchAndAccountLock() throws OperationException {
+        return dispatchWithAccountLockAndTimeout(new WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
+            @Override
+            public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
+                final OperationResult result = doSimpleOperationCallback();
+                return PluginDispatcher.createPluginDispatcherReturnType(result);
+            }
+        });
+    }
+
+    private OperationResult doSimpleOperationCallback() throws OperationException {
+        try {
+            return doOperation();
+        } catch (final PaymentApiException e) {
+            throw new OperationException(e, OperationResult.FAILURE);
+        } catch (final RuntimeException e) {
+            throw new OperationException(e, OperationResult.EXCEPTION);
+        }
+    }
+
+    private OperationResult doOperation() throws PaymentApiException {
+        try {
+            //
+            // If the OperationResult was specified in the plugin, it means we want to bypass the plugin and just care
+            // about running through the state machine to bring the transaction/payment into a new state.
+            //
+            if (paymentStateContext.getOverridePluginOperationResult() == null) {
+                final PaymentTransactionInfoPlugin paymentInfoPlugin = doCallSpecificOperationCallback();
+                // Throws if plugin is  ot correctly implemented (e.g returns null result, values,..)
+                sanityOnPaymentInfoPlugin(paymentInfoPlugin);
+
+                paymentStateContext.setPaymentInfoPlugin(paymentInfoPlugin);
+                return processPaymentInfoPlugin();
+            } else {
+                final PaymentTransactionInfoPlugin paymentInfoPlugin = new DefaultNoOpPaymentInfoPlugin(paymentStateContext.getPaymentId(),
+                                                                                                        paymentStateContext.getTransactionId(),
+                                                                                                        paymentStateContext.getTransactionType(),
+                                                                                                        paymentStateContext.getPaymentTransactionModelDao().getProcessedAmount(),
+                                                                                                        paymentStateContext.getPaymentTransactionModelDao().getProcessedCurrency(),
+                                                                                                        paymentStateContext.getPaymentTransactionModelDao().getEffectiveDate(),
+                                                                                                        paymentStateContext.getPaymentTransactionModelDao().getCreatedDate(),
+                                                                                                        PaymentPluginStatus.PROCESSED,
+                                                                                                        null);
+                paymentStateContext.setPaymentInfoPlugin(paymentInfoPlugin);
+                return paymentStateContext.getOverridePluginOperationResult();
+            }
+        } catch (final PaymentPluginApiException e) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, e.getErrorMessage());
+        }
+    }
+
+    private OperationResult processPaymentInfoPlugin() {
+        if (paymentStateContext.getPaymentInfoPlugin() == null) {
+            return OperationResult.FAILURE;
+        }
+
+        switch (paymentStateContext.getPaymentInfoPlugin().getStatus()) {
+            case PROCESSED:
+                return OperationResult.SUCCESS;
+            case PENDING:
+                return OperationResult.PENDING;
+            case ERROR:
+            case UNDEFINED:
+            default:
+                return OperationResult.FAILURE;
+        }
+    }
+
+    private void sanityOnPaymentInfoPlugin(final PaymentTransactionInfoPlugin paymentInfoPlugin) throws PaymentApiException {
+        if (paymentInfoPlugin == null) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, "Payment plugin returned a null result");
+        }
+        /*
+        TODO this breaks our tests so test would have to be fixed
+        if (paymentInfoPlugin.getKbTransactionPaymentId() == null || !paymentInfoPlugin.getKbTransactionPaymentId().equals(paymentStateContext.getTransactionId())) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, "Payment plugin returned invalid kbTransactionId");
+        }
+        if (paymentInfoPlugin.getKbPaymentId() == null || !paymentInfoPlugin.getKbPaymentId().equals(paymentStateContext.getPaymentId())) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, "Payment plugin returned invalid kbPaymentId");
+        }
+        if (paymentInfoPlugin.getTransactionType() == null || !paymentInfoPlugin.getKbPaymentId().equals(paymentStateContext.getTransactionType())) {
+            throw new PaymentApiException(ErrorCode.PAYMENT_PLUGIN_EXCEPTION, "Payment plugin returned invalid transaction type");
+        }
+        */
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
new file mode 100644
index 0000000..32b18a2
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateContext.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.util.callcontext.CallContext;
+
+import com.google.common.collect.ImmutableList;
+
+public class PaymentStateContext {
+
+    // HACK
+    protected UUID paymentMethodId;
+    protected UUID attemptId;
+
+    // Stateful objects created by the callbacks and passed to the other following callbacks in the automaton
+    protected List<PaymentTransactionModelDao> onLeavingStateExistingTransactions;
+    protected PaymentTransactionModelDao paymentTransactionModelDao;
+    protected PaymentTransactionInfoPlugin paymentInfoPlugin;
+    protected BigDecimal amount;
+    protected String paymentExternalKey;
+
+    // Can be updated later via paymentTransactionModelDao (e.g. for auth or purchase)
+    protected final UUID paymentId;
+    protected final UUID transactionId;
+    protected final String paymentTransactionExternalKey;
+    protected final Account account;
+    protected final Currency currency;
+    protected final TransactionType transactionType;
+    protected final boolean shouldLockAccountAndDispatch;
+    protected final Iterable<PluginProperty> properties;
+    protected final InternalCallContext internalCallContext;
+    protected final CallContext callContext;
+    protected final boolean isApiPayment;
+    protected final OperationResult overridePluginOperationResult;
+
+    // Use to create new transactions only
+    public PaymentStateContext(final boolean isApiPayment, @Nullable final UUID paymentId, @Nullable final String paymentTransactionExternalKey, final TransactionType transactionType,
+                               final Account account, @Nullable final UUID paymentMethodId, final BigDecimal amount, final Currency currency,
+                               final boolean shouldLockAccountAndDispatch, final Iterable<PluginProperty> properties,
+                               final InternalCallContext internalCallContext, final CallContext callContext) {
+        this(isApiPayment, paymentId, null, null, null, paymentTransactionExternalKey, transactionType, account, paymentMethodId,
+             amount, currency, shouldLockAccountAndDispatch, null, properties, internalCallContext, callContext);
+    }
+
+    // Used to create new payment and transactions
+    public PaymentStateContext(final boolean isApiPayment, @Nullable final UUID paymentId, final UUID transactionId, @Nullable final UUID attemptId, @Nullable final String paymentExternalKey,
+                               @Nullable final String paymentTransactionExternalKey, final TransactionType transactionType,
+                               final Account account, @Nullable final UUID paymentMethodId, final BigDecimal amount, final Currency currency,
+                               final boolean shouldLockAccountAndDispatch, final OperationResult overridePluginOperationResult, final Iterable<PluginProperty> properties,
+                               final InternalCallContext internalCallContext, final CallContext callContext) {
+        this.isApiPayment = isApiPayment;
+        this.paymentId = paymentId;
+        this.transactionId = transactionId;
+        this.attemptId= attemptId;
+        this.paymentExternalKey = paymentExternalKey;
+        this.paymentTransactionExternalKey = paymentTransactionExternalKey;
+        this.transactionType = transactionType;
+        this.account = account;
+        this.paymentMethodId = paymentMethodId;
+        this.amount = amount;
+        this.currency = currency;
+        this.shouldLockAccountAndDispatch = shouldLockAccountAndDispatch;
+        this.overridePluginOperationResult = overridePluginOperationResult;
+        this.properties = properties;
+        this.internalCallContext = internalCallContext;
+        this.callContext = callContext;
+        this.onLeavingStateExistingTransactions = ImmutableList.of();
+    }
+
+    public boolean isApiPayment() {
+        return isApiPayment;
+    }
+
+    public void setPaymentMethodId(final UUID paymentMethodId) {
+        this.paymentMethodId = paymentMethodId;
+    }
+
+    public PaymentTransactionModelDao getPaymentTransactionModelDao() {
+        return paymentTransactionModelDao;
+    }
+
+    public void setPaymentTransactionModelDao(final PaymentTransactionModelDao paymentTransactionModelDao) {
+        this.paymentTransactionModelDao = paymentTransactionModelDao;
+    }
+
+    public List<PaymentTransactionModelDao> getOnLeavingStateExistingTransactions() {
+        return onLeavingStateExistingTransactions;
+    }
+
+    public void setOnLeavingStateExistingTransactions(final List<PaymentTransactionModelDao> onLeavingStateExistingTransactions) {
+        this.onLeavingStateExistingTransactions = onLeavingStateExistingTransactions;
+    }
+
+    public PaymentTransactionInfoPlugin getPaymentInfoPlugin() {
+        return paymentInfoPlugin;
+    }
+
+    public void setPaymentInfoPlugin(final PaymentTransactionInfoPlugin paymentInfoPlugin) {
+        this.paymentInfoPlugin = paymentInfoPlugin;
+    }
+
+    public UUID getPaymentId() {
+        return paymentId != null ? paymentId : (paymentTransactionModelDao != null ? paymentTransactionModelDao.getPaymentId() : null);
+    }
+
+    public UUID getTransactionId() {
+        return transactionId != null ? transactionId : (paymentTransactionModelDao != null ? paymentTransactionModelDao.getId() : null);
+    }
+
+    public String getPaymentExternalKey() {
+        return paymentExternalKey;
+    }
+
+    public void setPaymentExternalKey(final String paymentExternalKey) {
+        this.paymentExternalKey = paymentExternalKey;
+    }
+
+    public String getPaymentTransactionExternalKey() {
+        return paymentTransactionExternalKey;
+    }
+
+    public Account getAccount() {
+        return account;
+    }
+
+    public UUID getPaymentMethodId() {
+        return paymentMethodId;
+    }
+
+    public UUID getAttemptId() {
+        return attemptId;
+    }
+
+    public void setAttemptId(final UUID attemptId) {
+        this.attemptId = attemptId;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    public TransactionType getTransactionType() {
+        return transactionType;
+    }
+
+    public boolean shouldLockAccountAndDispatch() {
+        return shouldLockAccountAndDispatch;
+    }
+
+    public OperationResult getOverridePluginOperationResult() {
+        return overridePluginOperationResult;
+    }
+
+    public Iterable<PluginProperty> getProperties() {
+        return properties;
+    }
+
+    public InternalCallContext getInternalCallContext() {
+        return internalCallContext;
+    }
+
+    public CallContext getCallContext() {
+        return callContext;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
new file mode 100644
index 0000000..763ec9c
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentStateMachineHelper.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import javax.inject.Inject;
+
+import org.killbill.automaton.MissingEntryException;
+import org.killbill.automaton.Operation;
+import org.killbill.automaton.OperationResult;
+import org.killbill.automaton.State;
+import org.killbill.automaton.StateMachine;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.automaton.Transition;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.glue.PaymentModule;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class PaymentStateMachineHelper {
+
+    private static final String BIG_BANG_STATE_MACHINE_NAME = "BIG_BANG";
+    private static final String AUTHORIZE_STATE_MACHINE_NAME = "AUTHORIZE";
+    private static final String CAPTURE_STATE_MACHINE_NAME = "CAPTURE";
+    private static final String PURCHASE_STATE_MACHINE_NAME = "PURCHASE";
+    private static final String REFUND_STATE_MACHINE_NAME = "REFUND";
+    private static final String CREDIT_STATE_MACHINE_NAME = "CREDIT";
+    private static final String VOID_STATE_MACHINE_NAME = "VOID";
+    private static final String CHARGEBACK_STATE_MACHINE_NAME = "CHARGEBACK";
+
+
+    private static final String BIG_BANG_INIT_STATE_NAME = "BIG_BANG_INIT";
+    private static final String AUTHORIZE_INIT_STATE_NAME = "AUTH_INIT";
+    private static final String CAPTURE_INIT_STATE_NAME = "CAPTURE_INIT";
+    private static final String PURCHASE_INIT_STATE_NAME = "PURCHASE_INIT";
+    private static final String REFUND_INIT_STATE_NAME = "REFUND_INIT";
+    private static final String CREDIT_INIT_STATE_NAME = "CREDIT_INIT";
+    private static final String VOID_INIT_STATE_NAME = "VOID_INIT";
+    private static final String CHARGEBACK_INIT_STATE_NAME = "CHARGEBACK_INIT";
+
+    private final StateMachineConfig stateMachineConfig;
+
+    @Inject
+    public PaymentStateMachineHelper(@javax.inject.Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig) {
+        this.stateMachineConfig = stateMachineConfig;
+    }
+
+    public State getState(final String stateName) throws MissingEntryException {
+        final StateMachine stateMachine  = stateMachineConfig.getStateMachineForState(stateName);
+        return stateMachine.getState(stateName);
+    }
+
+    public String getInitStateNameForTransaction() {
+        return BIG_BANG_INIT_STATE_NAME;
+    }
+
+    public StateMachine getStateMachineForStateName(final String stateName) throws MissingEntryException {
+        return stateMachineConfig.getStateMachineForState(stateName);
+    }
+
+    public Operation getOperationForTransaction(final TransactionType transactionType) throws MissingEntryException {
+        final StateMachine stateMachine = getStateMachineForTransaction(transactionType);
+        // Only one operation defined, this is the current PaymentStates.xml model
+        return stateMachine.getOperations()[0];
+    }
+
+    public StateMachine getStateMachineForTransaction(final TransactionType transactionType) throws MissingEntryException {
+        switch (transactionType) {
+            case AUTHORIZE:
+                return stateMachineConfig.getStateMachine(AUTHORIZE_STATE_MACHINE_NAME);
+            case CAPTURE:
+                return stateMachineConfig.getStateMachine(CAPTURE_STATE_MACHINE_NAME);
+            case PURCHASE:
+                return stateMachineConfig.getStateMachine(PURCHASE_STATE_MACHINE_NAME);
+            case REFUND:
+                return stateMachineConfig.getStateMachine(REFUND_STATE_MACHINE_NAME);
+            case CREDIT:
+                return stateMachineConfig.getStateMachine(CREDIT_STATE_MACHINE_NAME);
+            case VOID:
+                return stateMachineConfig.getStateMachine(VOID_STATE_MACHINE_NAME);
+            case CHARGEBACK:
+                return stateMachineConfig.getStateMachine(CHARGEBACK_STATE_MACHINE_NAME);
+            default:
+                throw new IllegalStateException("Unsupported transaction type " + transactionType + " for null payment id");
+        }
+    }
+
+    // A better way would be to change the xml to add attributes to the state (e.g isTerminal, isSuccess, isInit,...)
+    public boolean isSuccessState(final String stateName) {
+        return stateName.endsWith("SUCCESS");
+    }
+
+    public final State fetchNextState(final String prevStateName, final boolean isSuccess) throws MissingEntryException {
+        final StateMachine stateMachine = getStateMachineForStateName(prevStateName);
+        final Transition transition = Iterables.tryFind(ImmutableList.copyOf(stateMachine.getTransitions()), new Predicate<Transition>() {
+            @Override
+            public boolean apply(final Transition input) {
+                // This works because there is only one operation defined for a given state machine, which is our model for PaymentStates.xml
+                return input.getInitialState().getName().equals(prevStateName) &&
+                       input.getOperationResult().equals(isSuccess ? OperationResult.SUCCESS : OperationResult.FAILURE);
+            }
+        }).orNull();
+        return transition != null ? transition.getFinalState() : null;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlledPaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlledPaymentAutomatonRunner.java
new file mode 100644
index 0000000..ec4280c
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PluginControlledPaymentAutomatonRunner.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.killbill.automaton.MissingEntryException;
+import org.killbill.automaton.Operation.OperationCallback;
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.State;
+import org.killbill.automaton.State.EnteringStateCallback;
+import org.killbill.automaton.State.LeavingStateCallback;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+
+import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED;
+
+public class PluginControlledPaymentAutomatonRunner extends PaymentAutomatonRunner {
+
+    private final PaymentProcessor paymentProcessor;
+    private final RetryServiceScheduler retryServiceScheduler;
+
+    private final RetryStateMachineHelper retrySMHelper;
+
+    protected final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry;
+
+    @Inject
+    public PluginControlledPaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
+                                                  final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry, final Clock clock, final PaymentProcessor paymentProcessor, @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler,
+                                                  final PaymentConfig paymentConfig, @com.google.inject.name.Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor, PaymentStateMachineHelper paymentSMHelper, RetryStateMachineHelper retrySMHelper, final PersistentBus eventBus) {
+        super(stateMachineConfig, paymentConfig, paymentDao, locker, pluginRegistry, clock, executor, eventBus, paymentSMHelper);
+        this.paymentProcessor = paymentProcessor;
+        this.paymentControlPluginRegistry = retryPluginRegistry;
+        this.retryServiceScheduler = retryServiceScheduler;
+        this.retrySMHelper = retrySMHelper;
+    }
+
+    public Payment run(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
+                       @Nullable final UUID paymentId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
+                       @Nullable final BigDecimal amount, @Nullable final Currency currency,
+                       final Iterable<PluginProperty> properties, @Nullable final String pluginName,
+                       final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return run(retrySMHelper.getInitialState(), isApiPayment, transactionType, account, paymentMethodId, paymentId, paymentExternalKey, paymentTransactionExternalKey,
+                   amount, currency, properties, pluginName, callContext, internalCallContext);
+    }
+
+    public Payment run(final State state, final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
+                       @Nullable final UUID paymentId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
+                       @Nullable final BigDecimal amount, @Nullable final Currency currency,
+                       final Iterable<PluginProperty> properties, @Nullable final String pluginName,
+                       final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+
+        final RetryablePaymentStateContext paymentStateContext = createContext(isApiPayment, transactionType, account, paymentMethodId,
+                                                                                     paymentId, paymentExternalKey,
+                                                                                     paymentTransactionExternalKey,
+                                                                                     amount, currency,
+                                                                                     properties, pluginName, callContext, internalCallContext);
+        try {
+
+            final OperationCallback callback = createOperationCallback(transactionType, paymentStateContext);
+            final LeavingStateCallback leavingStateCallback = new RetryLeavingStateCallback(this, paymentStateContext, paymentDao, retrySMHelper.getInitialState(), retrySMHelper.getRetriedState(), transactionType);
+            final EnteringStateCallback enteringStateCallback = new RetryEnteringStateCallback(this, paymentStateContext, retryServiceScheduler);
+
+            state.runOperation(retrySMHelper.getRetryOperation(), callback, enteringStateCallback, leavingStateCallback);
+
+        } catch (MissingEntryException e) {
+            throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+        } catch (OperationException e) {
+            if (e.getCause() == null) {
+                // Unclear if we should check whether there is a result that was set and return that result.
+                throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+            } else if (e.getCause() instanceof PaymentApiException) {
+                throw (PaymentApiException) e.getCause();
+            } else {
+                throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+            }
+        }
+        return paymentStateContext.getResult();
+    }
+
+    public Payment completeRun(final RetryablePaymentStateContext paymentStateContext) throws PaymentApiException {
+
+        try {
+
+            final OperationCallback callback = new RetryCompletionOperationCallback(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+            final LeavingStateCallback leavingStateCallback = new RetryNoopLeavingStateCallback();
+            final EnteringStateCallback enteringStateCallback = new RetryEnteringStateCallback(this, paymentStateContext, retryServiceScheduler);
+
+            retrySMHelper.getInitialState().runOperation(retrySMHelper.getRetryOperation(), callback, enteringStateCallback, leavingStateCallback);
+
+        } catch (MissingEntryException e) {
+            throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+        } catch (OperationException e) {
+            if (e.getCause() == null) {
+                throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+            } else if (e.getCause() instanceof PaymentApiException) {
+                throw (PaymentApiException) e.getCause();
+            } else {
+                throw new PaymentApiException(e.getCause(), ErrorCode.PAYMENT_INTERNAL_ERROR, Objects.firstNonNull(e.getMessage(), ""));
+            }
+        }
+        return paymentStateContext.getResult();
+    }
+
+    @VisibleForTesting
+    RetryablePaymentStateContext createContext(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
+                                               @Nullable final UUID paymentId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
+                                               @Nullable final BigDecimal amount, @Nullable final Currency currency, final Iterable<PluginProperty> properties,
+                                               final String pluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        return new RetryablePaymentStateContext(pluginName, isApiPayment, paymentId, paymentExternalKey, paymentTransactionExternalKey, transactionType, account,
+                                                paymentMethodId, amount, currency, properties, internalCallContext, callContext);
+    }
+
+    @VisibleForTesting
+    OperationCallback createOperationCallback(final TransactionType transactionType, final RetryablePaymentStateContext paymentStateContext) {
+        final OperationCallback callback;
+        switch (transactionType) {
+            case AUTHORIZE:
+                callback = new RetryAuthorizeOperationCallback(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                break;
+            case CAPTURE:
+                callback = new RetryCaptureOperationCallback(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                break;
+            case PURCHASE:
+                callback = new RetryPurchaseOperationCallback(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                break;
+            case VOID:
+                callback = new RetryVoidOperationCallback(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                break;
+            case CREDIT:
+                callback = new RetryCreditOperationCallback(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                break;
+            case REFUND:
+                callback = new RetryRefundOperationCallback(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                break;
+            case CHARGEBACK:
+                callback = new RetryChargebackOperationCallback(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+                break;
+            default:
+                throw new IllegalStateException("Unsupported transaction type " + transactionType);
+        }
+        return callback;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PurchaseOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PurchaseOperation.java
new file mode 100644
index 0000000..38757f5
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PurchaseOperation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PurchaseOperation extends PaymentOperation {
+
+    private final Logger logger = LoggerFactory.getLogger(PurchaseOperation.class);
+
+    public PurchaseOperation(final PaymentAutomatonDAOHelper daoHelper,
+                             final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                             final PaymentStateContext paymentStateContext) throws PaymentApiException {
+        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+    }
+
+    @Override
+    protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+        logger.debug("Starting PURCHASE for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
+        return plugin.purchasePayment(paymentStateContext.getAccount().getId(),
+                                     paymentStateContext.getPaymentId(),
+                                     paymentStateContext.getTransactionId(),
+                                     paymentStateContext.getPaymentMethodId(),
+                                     paymentStateContext.getAmount(),
+                                     paymentStateContext.getCurrency(),
+                                     paymentStateContext.getProperties(),
+                                     paymentStateContext.getCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RefundOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RefundOperation.java
new file mode 100644
index 0000000..3a7fca4
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RefundOperation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RefundOperation extends PaymentOperation {
+
+    private final Logger logger = LoggerFactory.getLogger(RefundOperation.class);
+
+    public RefundOperation(final PaymentAutomatonDAOHelper daoHelper,
+                           final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                           final PaymentStateContext paymentStateContext) throws PaymentApiException {
+        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+    }
+
+    @Override
+    protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+        logger.debug("Starting REFUND for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
+        return plugin.refundPayment(paymentStateContext.getAccount().getId(),
+                                    paymentStateContext.getPaymentId(),
+                                    paymentStateContext.getTransactionId(),
+                                    paymentStateContext.getPaymentMethodId(),
+                                    paymentStateContext.getAmount(),
+                                    paymentStateContext.getCurrency(),
+                                    paymentStateContext.getProperties(),
+                                    paymentStateContext.getCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryablePaymentStateContext.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryablePaymentStateContext.java
new file mode 100644
index 0000000..678d987
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryablePaymentStateContext.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.api.DefaultPaymentTransaction;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.util.callcontext.CallContext;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+public class RetryablePaymentStateContext extends PaymentStateContext {
+
+    private DateTime retryDate;
+    private String pluginName;
+    private Payment result;
+
+    public RetryablePaymentStateContext(@Nullable final String pluginName, final boolean isApiPayment, @Nullable final UUID paymentId, final String paymentExternalKey,
+                                        @Nullable final String paymentTransactionExternalKey, final TransactionType transactionType,
+                                        final Account account, @Nullable final UUID paymentMethodId, final BigDecimal amount, final Currency currency,
+                                        final Iterable<PluginProperty> properties, final InternalCallContext internalCallContext, final CallContext callContext) {
+        super(isApiPayment, paymentId, null, null, paymentExternalKey, paymentTransactionExternalKey, transactionType, account, paymentMethodId, amount, currency, true, null, properties, internalCallContext, callContext);
+        this.pluginName = pluginName;
+    }
+
+    public DateTime getRetryDate() {
+        return retryDate;
+    }
+
+    public void setRetryDate(final DateTime retryDate) {
+        this.retryDate = retryDate;
+    }
+
+    public String getPluginName() {
+        return pluginName;
+    }
+
+    public void setPluginName(final String pluginName) {
+        this.pluginName = pluginName;
+    }
+
+    public Payment getResult() {
+        return result;
+    }
+
+    public void setResult(final Payment result) {
+        this.result = result;
+    }
+
+    public void setAmount(final BigDecimal adjustedAmount) {
+        this.amount = adjustedAmount;
+    }
+
+    public PaymentTransaction getCurrentTransaction() {
+        if (result == null || result.getTransactions() == null) {
+            return null;
+        }
+        return Iterables.tryFind(result.getTransactions(), new Predicate<PaymentTransaction>() {
+            @Override
+            public boolean apply(final PaymentTransaction input) {
+                return ((DefaultPaymentTransaction) input).getAttemptId().equals(attemptId);
+            }
+        }).orNull();
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryAuthorizeOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryAuthorizeOperationCallback.java
new file mode 100644
index 0000000..e92d14d
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryAuthorizeOperationCallback.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class RetryAuthorizeOperationCallback extends RetryOperationCallback {
+
+    public RetryAuthorizeOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    }
+
+    @Override
+    protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+        return paymentProcessor.createAuthorization(retryablePaymentStateContext.isApiPayment(),
+                                                          retryablePaymentStateContext.getAttemptId(),
+                                                          retryablePaymentStateContext.getAccount(),
+                                                          retryablePaymentStateContext.getPaymentMethodId(),
+                                                          retryablePaymentStateContext.getPaymentId(),
+                                                          retryablePaymentStateContext.getAmount(),
+                                                          retryablePaymentStateContext.getCurrency(),
+                                                          retryablePaymentStateContext.getPaymentExternalKey(),
+                                                          retryablePaymentStateContext.getPaymentTransactionExternalKey(),
+                                                          false,
+                                                          retryablePaymentStateContext.getProperties(),
+                                                          retryablePaymentStateContext.getCallContext(),
+                                                          retryablePaymentStateContext.getInternalCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCaptureOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCaptureOperationCallback.java
new file mode 100644
index 0000000..ad4d31b
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCaptureOperationCallback.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class RetryCaptureOperationCallback extends RetryOperationCallback {
+
+    public RetryCaptureOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    }
+
+    @Override
+    protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+        return paymentProcessor.createCapture(retryablePaymentStateContext.isApiPayment(),
+                                                    retryablePaymentStateContext.getAttemptId(),
+                                                    retryablePaymentStateContext.getAccount(),
+                                                    retryablePaymentStateContext.getPaymentMethodId(),
+                                                    retryablePaymentStateContext.getAmount(),
+                                                    retryablePaymentStateContext.getCurrency(),
+                                                    retryablePaymentStateContext.getPaymentTransactionExternalKey(),
+                                                    false,
+                                                    retryablePaymentStateContext.getProperties(),
+                                                    retryablePaymentStateContext.getCallContext(),
+                                                    retryablePaymentStateContext.getInternalCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryChargebackOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryChargebackOperationCallback.java
new file mode 100644
index 0000000..5992f9a
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryChargebackOperationCallback.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class RetryChargebackOperationCallback extends RetryOperationCallback {
+
+    public RetryChargebackOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    }
+
+    @Override
+    protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+        return paymentProcessor.createChargeback(retryablePaymentStateContext.isApiPayment(),
+                                                       retryablePaymentStateContext.getAttemptId(),
+                                                       retryablePaymentStateContext.getAccount(),
+                                                       retryablePaymentStateContext.getPaymentId(),
+                                                       retryablePaymentStateContext.getPaymentTransactionExternalKey(),
+                                                       retryablePaymentStateContext.getAmount(),
+                                                       retryablePaymentStateContext.getCurrency(),
+                                                       false,
+                                                       retryablePaymentStateContext.getCallContext(),
+                                                       retryablePaymentStateContext.getInternalCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCompletionOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCompletionOperationCallback.java
new file mode 100644
index 0000000..91e1f92
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCompletionOperationCallback.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.billing.retry.plugin.api.PaymentControlContext;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class RetryCompletionOperationCallback extends RetryOperationCallback {
+
+    public RetryCompletionOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry);
+    }
+
+    @Override
+    public OperationResult doOperationCallback() throws OperationException {
+
+        return dispatchWithAccountLockAndTimeout(new WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
+            @Override
+            public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
+                final PaymentTransactionModelDao transaction = paymentStateContext.getPaymentTransactionModelDao();
+                final PaymentControlContext updatedPaymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(),
+                                                                                                            paymentStateContext.getPaymentMethodId(),
+                                                                                                            retryablePaymentStateContext.getAttemptId(),
+                                                                                                            transaction.getPaymentId(),
+                                                                                                            paymentStateContext.getPaymentExternalKey(),
+                                                                                                            transaction.getId(),
+                                                                                                            paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                                            paymentStateContext.getTransactionType(),
+                                                                                                            transaction.getAmount(),
+                                                                                                            transaction.getCurrency(),
+                                                                                                            transaction.getProcessedAmount(),
+                                                                                                            transaction.getProcessedCurrency(),
+                                                                                                            paymentStateContext.getProperties(),
+                                                                                                            retryablePaymentStateContext.isApiPayment(),
+                                                                                                            paymentStateContext.callContext);
+
+                onCompletion(retryablePaymentStateContext.getPluginName(), updatedPaymentControlContext);
+                return PluginDispatcher.createPluginDispatcherReturnType(OperationResult.SUCCESS);
+            }
+        });
+    }
+
+    @Override
+    protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+        return null;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCreditOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCreditOperationCallback.java
new file mode 100644
index 0000000..90c8ae3
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryCreditOperationCallback.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class RetryCreditOperationCallback extends RetryOperationCallback {
+
+    public RetryCreditOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    }
+
+    @Override
+    protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+        return paymentProcessor.createCredit(retryablePaymentStateContext.isApiPayment(),
+                                                   retryablePaymentStateContext.getAttemptId(),
+                                                   retryablePaymentStateContext.getAccount(),
+                                                   retryablePaymentStateContext.getPaymentMethodId(),
+                                                   retryablePaymentStateContext.getPaymentId(),
+                                                   retryablePaymentStateContext.getAmount(),
+                                                   retryablePaymentStateContext.getCurrency(),
+                                                   retryablePaymentStateContext.getPaymentExternalKey(),
+                                                   retryablePaymentStateContext.getPaymentTransactionExternalKey(),
+                                                   false,
+                                                   retryablePaymentStateContext.getProperties(),
+                                                   retryablePaymentStateContext.getCallContext(),
+                                                   retryablePaymentStateContext.getInternalCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java
new file mode 100644
index 0000000..0b7889f
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryEnteringStateCallback.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.util.UUID;
+
+import org.killbill.automaton.Operation.OperationCallback;
+import org.killbill.automaton.OperationResult;
+import org.killbill.automaton.State;
+import org.killbill.automaton.State.EnteringStateCallback;
+import org.killbill.automaton.State.LeavingStateCallback;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
+
+public class RetryEnteringStateCallback implements EnteringStateCallback {
+
+    private PluginControlledPaymentAutomatonRunner retryablePaymentAutomatonRunner;
+    private final RetryablePaymentStateContext paymentStateContext;
+    private final RetryServiceScheduler retryServiceScheduler;
+
+    public RetryEnteringStateCallback(final PluginControlledPaymentAutomatonRunner retryablePaymentAutomatonRunner, final RetryablePaymentStateContext paymentStateContext,
+                                      final RetryServiceScheduler retryServiceScheduler) {
+        this.retryablePaymentAutomatonRunner = retryablePaymentAutomatonRunner;
+        this.paymentStateContext = paymentStateContext;
+        this.retryServiceScheduler = retryServiceScheduler;
+    }
+
+    @Override
+    public void enteringState(final State state, final OperationCallback operationCallback, final OperationResult operationResult, final LeavingStateCallback leavingStateCallback) {
+
+        final PaymentAttemptModelDao attempt = retryablePaymentAutomatonRunner.paymentDao.getPaymentAttempt(paymentStateContext.getAttemptId(), paymentStateContext.internalCallContext);
+        final UUID transactionId = paymentStateContext.getCurrentTransaction() != null ?
+                                   paymentStateContext.getCurrentTransaction().getId() :
+                                   null;
+        retryablePaymentAutomatonRunner.paymentDao.updatePaymentAttempt(attempt.getId(), transactionId, state.getName(), paymentStateContext.internalCallContext);
+
+        if ("RETRIED".equals(state.getName())) {
+            retryServiceScheduler.scheduleRetry(ObjectType.PAYMENT_ATTEMPT, attempt.getId(), attempt.getId(),
+                                                paymentStateContext.getPluginName(), paymentStateContext.getRetryDate());
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
new file mode 100644
index 0000000..4a4b2ed
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.joda.time.DateTime;
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.State;
+import org.killbill.automaton.State.LeavingStateCallback;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+
+import com.google.common.base.Preconditions;
+
+public class RetryLeavingStateCallback implements LeavingStateCallback {
+
+    private PluginControlledPaymentAutomatonRunner retryablePaymentAutomatonRunner;
+    private final RetryablePaymentStateContext stateContext;
+    private final State initialState;
+    private final State retriedState;
+    private final TransactionType transactionType;
+    private final PaymentDao paymentDao;
+
+    public RetryLeavingStateCallback(final PluginControlledPaymentAutomatonRunner retryablePaymentAutomatonRunner, final PaymentStateContext stateContext, final PaymentDao paymentDao,
+                                     final State initialState, final State retriedState, final TransactionType transactionType) {
+        this.retryablePaymentAutomatonRunner = retryablePaymentAutomatonRunner;
+        this.paymentDao = paymentDao;
+        this.initialState = initialState;
+        this.retriedState = retriedState;
+        this.stateContext = (RetryablePaymentStateContext) stateContext;
+        this.transactionType = transactionType;
+    }
+
+    @Override
+    public void leavingState(final State state) throws OperationException {
+
+        final DateTime utcNow = retryablePaymentAutomatonRunner.clock.getUTCNow();
+
+        Preconditions.checkState(stateContext.getPaymentExternalKey() != null || /* CAPTURE, PURCHASE, CREDIT calls will provide the paymentId */
+                                 stateContext.getPaymentId() != null);
+        if (stateContext.getPaymentExternalKey() == null) {
+            final PaymentModelDao payment = paymentDao.getPayment(stateContext.getPaymentId(), stateContext.internalCallContext);
+            Preconditions.checkState(payment != null);
+            stateContext.setPaymentExternalKey(payment.getExternalKey());
+        }
+
+
+        if (state.getName().equals(initialState.getName()) ||
+            state.getName().equals(retriedState.getName())) {
+
+            try {
+                final byte [] serializedProperties = PluginPropertySerializer.serialize(stateContext.getProperties());
+
+
+                final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(stateContext.getAccount().getId(), stateContext.getPaymentMethodId(),
+                                                                                  utcNow, utcNow, stateContext.getPaymentExternalKey(), null,
+                                                                                  stateContext.paymentTransactionExternalKey, transactionType, initialState.getName(),
+                                                                                  stateContext.getAmount(), stateContext.getCurrency(),
+                                                                                  stateContext.getPluginName(), serializedProperties);
+
+                retryablePaymentAutomatonRunner.paymentDao.insertPaymentAttemptWithProperties(attempt, stateContext.internalCallContext);
+                stateContext.setAttemptId(attempt.getId());
+            } catch (PluginPropertySerializerException e) {
+                throw new OperationException(e);
+            }
+
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
new file mode 100644
index 0000000..0e44d5c
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryOperationCallback.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.killbill.automaton.Operation.OperationCallback;
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.DefaultCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.billing.retry.plugin.api.FailureCallResult;
+import org.killbill.billing.retry.plugin.api.PaymentControlApiException;
+import org.killbill.billing.retry.plugin.api.PaymentControlContext;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.retry.plugin.api.PriorPaymentControlResult;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.LockFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class RetryOperationCallback extends OperationCallbackBase<Payment, PaymentApiException> implements OperationCallback {
+
+    private final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry;
+
+    protected final PaymentProcessor paymentProcessor;
+    protected final RetryablePaymentStateContext retryablePaymentStateContext;
+
+    private final Logger logger = LoggerFactory.getLogger(RetryOperationCallback.class);
+
+    protected RetryOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry) {
+        super(locker, paymentPluginDispatcher, paymentStateContext);
+        this.paymentProcessor = paymentProcessor;
+        this.paymentControlPluginRegistry = retryPluginRegistry;
+        this.retryablePaymentStateContext = paymentStateContext;
+    }
+
+    @Override
+    protected abstract Payment doCallSpecificOperationCallback() throws PaymentApiException;
+
+    @Override
+    public OperationResult doOperationCallback() throws OperationException {
+
+        return dispatchWithAccountLockAndTimeout(new WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, OperationException>() {
+
+            @Override
+            public PluginDispatcherReturnType<OperationResult> doOperation() throws OperationException {
+
+                final PaymentControlContext paymentControlContext = new DefaultPaymentControlContext(paymentStateContext.getAccount(),
+                                                                                                     paymentStateContext.getPaymentMethodId(),
+                                                                                                     retryablePaymentStateContext.getAttemptId(),
+                                                                                                     paymentStateContext.getPaymentId(),
+                                                                                                     paymentStateContext.getPaymentExternalKey(),
+                                                                                                     paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                                     paymentStateContext.getTransactionType(),
+                                                                                                     paymentStateContext.getAmount(),
+                                                                                                     paymentStateContext.getCurrency(),
+                                                                                                     paymentStateContext.getProperties(),
+                                                                                                     retryablePaymentStateContext.isApiPayment(),
+                                                                                                     paymentStateContext.callContext);
+
+                final PriorPaymentControlResult pluginResult;
+                try {
+                    pluginResult = getPluginResult(retryablePaymentStateContext.getPluginName(), paymentControlContext);
+                    if (pluginResult.isAborted()) {
+                        // Transition to ABORTED
+                        return PluginDispatcher.createPluginDispatcherReturnType(OperationResult.EXCEPTION);
+                    }
+                } catch (final PaymentControlApiException e) {
+                    // Transition to ABORTED and throw PaymentControlApiException to caller.
+                    throw new OperationException(e, OperationResult.EXCEPTION);
+                }
+
+                final boolean success;
+                try {
+                    // Adjust amount with value returned by plugin if necessary
+                    if (paymentStateContext.getAmount() == null ||
+                        (pluginResult.getAdjustedAmount() != null && pluginResult.getAdjustedAmount().compareTo(paymentStateContext.getAmount()) != 0)) {
+                        ((RetryablePaymentStateContext) paymentStateContext).setAmount(pluginResult.getAdjustedAmount());
+                    }
+
+                    final Payment result = doCallSpecificOperationCallback();
+                    ((RetryablePaymentStateContext) paymentStateContext).setResult(result);
+                    final PaymentTransaction transaction = ((RetryablePaymentStateContext) paymentStateContext).getCurrentTransaction();
+
+                    success = transaction.getTransactionStatus() == TransactionStatus.SUCCESS || transaction.getTransactionStatus() == TransactionStatus.PENDING;
+                    if (success) {
+                        final PaymentControlContext updatedPaymentControlContext = new DefaultPaymentControlContext(paymentStateContext.account,
+                                                                                                                    paymentStateContext.paymentMethodId,
+                                                                                                                    retryablePaymentStateContext.getAttemptId(),
+                                                                                                                    result.getId(),
+                                                                                                                    result.getExternalKey(),
+                                                                                                                    transaction.getId(),
+                                                                                                                    paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                                                    paymentStateContext.getTransactionType(),
+                                                                                                                    transaction.getAmount(),
+                                                                                                                    transaction.getCurrency(),
+                                                                                                                    transaction.getProcessedAmount(),
+                                                                                                                    transaction.getProcessedCurrency(),
+                                                                                                                    paymentStateContext.properties,
+                                                                                                                    retryablePaymentStateContext.isApiPayment(),
+                                                                                                                    paymentStateContext.callContext);
+
+                        onCompletion(retryablePaymentStateContext.getPluginName(), updatedPaymentControlContext);
+                        return PluginDispatcher.createPluginDispatcherReturnType(OperationResult.SUCCESS);
+                    } else {
+                        throw new OperationException(null, getOperationResultAndSetContext(retryablePaymentStateContext, paymentControlContext));
+                    }
+                } catch (final PaymentApiException e) {
+                    // Wrap PaymentApiException, and throw a new OperationException with an ABORTED/FAILURE state based on the retry result.
+                    throw new OperationException(e, getOperationResultAndSetContext(retryablePaymentStateContext, paymentControlContext));
+                } catch (final RuntimeException e) {
+                    // Attempts to set the retry date in context if needed.
+                    getOperationResultAndSetContext(retryablePaymentStateContext, paymentControlContext);
+                    throw e;
+                }
+            }
+        });
+    }
+
+    @Override
+    protected OperationException rewrapExecutionException(final PaymentStateContext paymentStateContext, final ExecutionException e) {
+        if (e.getCause() instanceof OperationException) {
+            return (OperationException) e.getCause();
+        } else if (e.getCause() instanceof LockFailedException) {
+            final String format = String.format("Failed to lock account %s", paymentStateContext.getAccount().getExternalKey());
+            logger.error(String.format(format), e);
+            return new OperationException(e, getOperationResultOnException(paymentStateContext));
+        } else /* most probably RuntimeException */ {
+            logger.warn("RetryOperationCallback failed for account {}", paymentStateContext.getAccount().getExternalKey(), e);
+            return new OperationException(e, getOperationResultOnException(paymentStateContext));
+        }
+    }
+
+    @Override
+    protected OperationException wrapTimeoutException(final PaymentStateContext paymentStateContext, final TimeoutException e) {
+        logger.error("RetryOperationCallback call TIMEOUT for account {}: {}", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+        return new OperationException(e, getOperationResultOnException(paymentStateContext));
+    }
+
+    @Override
+    protected OperationException wrapInterruptedException(final PaymentStateContext paymentStateContext, final InterruptedException e) {
+        logger.error("RetryOperationCallback call was interrupted for account {}: {}", paymentStateContext.getAccount().getExternalKey(), e.getMessage());
+        return new OperationException(e, getOperationResultOnException(paymentStateContext));
+    }
+
+    protected void onCompletion(final String pluginName, final PaymentControlContext paymentControlContext) {
+        final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+        try {
+            plugin.onSuccessCall(paymentControlContext);
+        } catch (final PaymentControlApiException e) {
+            logger.warn("Plugin " + pluginName + " failed to complete onCompletion call for " + paymentControlContext.getPaymentExternalKey(), e);
+        }
+    }
+
+    private OperationResult getOperationResultOnException(final PaymentStateContext paymentStateContext) {
+        final RetryablePaymentStateContext retryablePaymentStateContext = (RetryablePaymentStateContext) paymentStateContext;
+        final OperationResult operationResult = retryablePaymentStateContext.getRetryDate() != null ? OperationResult.FAILURE : OperationResult.EXCEPTION;
+        return operationResult;
+    }
+
+    private PriorPaymentControlResult getPluginResult(final String pluginName, final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
+        final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+        final PriorPaymentControlResult result = plugin.priorCall(paymentControlContext);
+        return result;
+    }
+
+    private OperationResult getOperationResultAndSetContext(final RetryablePaymentStateContext retryablePaymentStateContext, final PaymentControlContext paymentControlContext) {
+        final DateTime retryDate = getNextRetryDate(retryablePaymentStateContext.getPluginName(), paymentControlContext);
+        if (retryDate != null) {
+            ((RetryablePaymentStateContext) paymentStateContext).setRetryDate(retryDate);
+            return OperationResult.FAILURE;
+        } else {
+            return OperationResult.EXCEPTION;
+        }
+    }
+
+    private DateTime getNextRetryDate(final String pluginName, final PaymentControlContext paymentControlContext) {
+        try {
+            final PaymentControlPluginApi plugin = paymentControlPluginRegistry.getServiceForName(pluginName);
+            final FailureCallResult result = plugin.onFailureCall(paymentControlContext);
+            return result.getNextRetryDate();
+        } catch (final PaymentControlApiException e) {
+            logger.warn("Plugin " + pluginName + " failed to return next retryDate for payment " + paymentControlContext.getPaymentExternalKey(), e);
+            return null;
+        }
+    }
+
+    public static class DefaultPaymentControlContext extends DefaultCallContext implements PaymentControlContext {
+
+        private final Account account;
+        private final UUID paymentMethodId;
+        private final UUID attemptId;
+        private final UUID paymentId;
+        private final String paymentExternalKey;
+        private final UUID transactionId;
+        private final String transactionExternalKey;
+        private final TransactionType transactionType;
+        private final BigDecimal amount;
+        private final Currency currency;
+        private final BigDecimal processedAmount;
+        private final Currency processedCurrency;
+        private final boolean isApiPayment;
+        private final Iterable<PluginProperty> properties;
+
+        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, final String transactionExternalKey, final TransactionType transactionType, final BigDecimal amount, final Currency currency,
+                                            final Iterable<PluginProperty> properties, final boolean isApiPayment, final CallContext callContext) {
+            this(account, paymentMethodId, attemptId, paymentId, paymentExternalKey, null, transactionExternalKey, transactionType, amount, currency, null, null, properties, isApiPayment, callContext);
+        }
+
+        public DefaultPaymentControlContext(final Account account, final UUID paymentMethodId, final UUID attemptId, @Nullable final UUID paymentId, final String paymentExternalKey, @Nullable final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType,
+                                            final BigDecimal amount, final Currency currency, @Nullable final BigDecimal processedAmount, @Nullable final Currency processedCurrency, final Iterable<PluginProperty> properties, final boolean isApiPayment, final CallContext callContext) {
+            super(callContext.getTenantId(), callContext.getUserName(), callContext.getCallOrigin(), callContext.getUserType(), callContext.getReasonCode(), callContext.getComments(), callContext.getUserToken(), callContext.getCreatedDate(), callContext.getUpdatedDate());
+            this.account = account;
+            this.paymentMethodId = paymentMethodId;
+            this.attemptId = attemptId;
+            this.paymentId = paymentId;
+            this.paymentExternalKey = paymentExternalKey;
+            this.transactionId = transactionId;
+            this.transactionExternalKey = transactionExternalKey;
+            this.transactionType = transactionType;
+            this.amount = amount;
+            this.currency = currency;
+            this.processedAmount = processedAmount;
+            this.processedCurrency = processedCurrency;
+            this.properties = properties;
+            this.isApiPayment = isApiPayment;
+        }
+
+        @Override
+        public UUID getAccountId() {
+            return account.getId();
+        }
+
+        @Override
+        public String getPaymentExternalKey() {
+            return paymentExternalKey;
+        }
+
+        @Override
+        public String getTransactionExternalKey() {
+            return transactionExternalKey;
+        }
+
+        @Override
+        public TransactionType getTransactionType() {
+            return transactionType;
+        }
+
+        @Override
+        public BigDecimal getAmount() {
+            return amount;
+        }
+
+        @Override
+        public Currency getCurrency() {
+            return currency;
+        }
+
+        @Override
+        public UUID getPaymentMethodId() {
+            return paymentMethodId;
+        }
+
+        @Override
+        public UUID getPaymentId() {
+            return paymentId;
+        }
+
+        @Override
+        public UUID getAttemptPaymentId() {
+            return attemptId;
+        }
+
+        @Override
+        public BigDecimal getProcessedAmount() {
+            return processedAmount;
+        }
+
+        @Override
+        public Currency getProcessedCurrency() {
+            return processedCurrency;
+        }
+
+        @Override
+        public boolean isApiPayment() {
+            return isApiPayment;
+        }
+
+        public UUID getTransactionId() {
+            return transactionId;
+        }
+
+        @Override
+        public Iterable<PluginProperty> getPluginProperties() {
+            return properties;
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryPurchaseOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryPurchaseOperationCallback.java
new file mode 100644
index 0000000..5154d4f
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryPurchaseOperationCallback.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class RetryPurchaseOperationCallback extends RetryOperationCallback {
+
+    public RetryPurchaseOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry);
+    }
+
+    @Override
+    protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+        return paymentProcessor.createPurchase(retryablePaymentStateContext.isApiPayment(),
+                                                     retryablePaymentStateContext.getAttemptId(),
+                                                     retryablePaymentStateContext.getAccount(),
+                                                     retryablePaymentStateContext.getPaymentMethodId(),
+                                                     retryablePaymentStateContext.getPaymentId(),
+                                                     retryablePaymentStateContext.getAmount(),
+                                                     retryablePaymentStateContext.getCurrency(),
+                                                     retryablePaymentStateContext.getPaymentExternalKey(),
+                                                     retryablePaymentStateContext.getPaymentTransactionExternalKey(),
+                                                     false,
+                                                     retryablePaymentStateContext.getProperties(),
+                                                     retryablePaymentStateContext.getCallContext(),
+                                                     retryablePaymentStateContext.getInternalCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryRefundOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryRefundOperationCallback.java
new file mode 100644
index 0000000..f4cb3c0
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryRefundOperationCallback.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class RetryRefundOperationCallback extends RetryOperationCallback {
+
+    public RetryRefundOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    }
+
+    @Override
+    protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+        return paymentProcessor.createRefund(retryablePaymentStateContext.isApiPayment(),
+                                                   retryablePaymentStateContext.getAttemptId(),
+                                                   retryablePaymentStateContext.getAccount(),
+                                                   retryablePaymentStateContext.getPaymentId(),
+                                                   retryablePaymentStateContext.getAmount(),
+                                                   retryablePaymentStateContext.getCurrency(),
+                                                   retryablePaymentStateContext.getPaymentTransactionExternalKey(),
+                                                   false,
+                                                   retryablePaymentStateContext.getProperties(),
+                                                   retryablePaymentStateContext.getCallContext(),
+                                                   retryablePaymentStateContext.getInternalCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java
new file mode 100644
index 0000000..30bb1ed
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryStateMachineHelper.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.killbill.automaton.MissingEntryException;
+import org.killbill.automaton.Operation;
+import org.killbill.automaton.State;
+import org.killbill.automaton.StateMachine;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.payment.glue.PaymentModule;
+
+public class RetryStateMachineHelper {
+
+    /**
+     * Those need to match RetryStates.xml
+     */
+    private static final String RETRY_STATE_MACHINE_NAME = "PAYMENT_RETRY";
+    private final String RETRY_OPERATION_NAME = "OP_RETRY";
+    private static final String INIT_STATE_NAME = "INIT";
+    private static final String RETRIED_STATE_NAME = "RETRIED";
+
+    private final StateMachineConfig retryStateMachineConfig;
+    private final StateMachine retryStateMachine;
+    private final Operation retryOperation;
+    private final State initialState;
+    private final State retriedState;
+
+    @Inject
+    public RetryStateMachineHelper(@Named(PaymentModule.STATE_MACHINE_RETRY) final StateMachineConfig retryStateMachineConfig) throws MissingEntryException {
+        this.retryStateMachineConfig = retryStateMachineConfig;
+        this.retryStateMachine = retryStateMachineConfig.getStateMachine(RETRY_STATE_MACHINE_NAME);
+        this.retryOperation = retryStateMachine.getOperation(RETRY_OPERATION_NAME);
+        this.initialState = retryStateMachine.getState(INIT_STATE_NAME);
+        this.retriedState = retryStateMachine.getState(RETRIED_STATE_NAME);
+    }
+
+    public State getState(final String stateName) throws MissingEntryException {
+        return retryStateMachine.getState(stateName);
+    }
+
+    public StateMachineConfig getRetryStateMachineConfig() {
+        return retryStateMachineConfig;
+    }
+
+    public StateMachine getRetryStateMachine() {
+        return retryStateMachine;
+    }
+
+    public Operation getRetryOperation() {
+        return retryOperation;
+    }
+
+    public State getInitialState() {
+        return initialState;
+    }
+
+    public State getRetriedState() {
+        return retriedState;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryVoidOperationCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryVoidOperationCallback.java
new file mode 100644
index 0000000..090e2e0
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryVoidOperationCallback.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class RetryVoidOperationCallback extends RetryOperationCallback {
+
+    public RetryVoidOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> paymentControlPluginRegistry) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, paymentControlPluginRegistry);
+    }
+
+    @Override
+    protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+        return paymentProcessor.createVoid(retryablePaymentStateContext.isApiPayment(),
+                                                 retryablePaymentStateContext.getAttemptId(),
+                                                 retryablePaymentStateContext.getAccount(),
+                                                 retryablePaymentStateContext.getPaymentId(),
+                                                 retryablePaymentStateContext.getPaymentTransactionExternalKey(),
+                                                 false,
+                                                 retryablePaymentStateContext.getProperties(),
+                                                 retryablePaymentStateContext.getCallContext(),
+                                                 retryablePaymentStateContext.getInternalCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/VoidOperation.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/VoidOperation.java
new file mode 100644
index 0000000..0262abf
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/VoidOperation.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class VoidOperation extends PaymentOperation {
+
+    private final Logger logger = LoggerFactory.getLogger(VoidOperation.class);
+
+    public VoidOperation(final PaymentAutomatonDAOHelper daoHelper,
+                         final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher,
+                         final PaymentStateContext paymentStateContext) throws PaymentApiException {
+        super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+    }
+
+    @Override
+    protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+        logger.debug("Starting VOID for payment {} ({} {})", paymentStateContext.getPaymentId(), paymentStateContext.getAmount(), paymentStateContext.getCurrency());
+        return plugin.voidPayment(paymentStateContext.getAccount().getId(),
+                                  paymentStateContext.getPaymentId(),
+                                  paymentStateContext.getTransactionId(),
+                                  paymentStateContext.getPaymentMethodId(),
+                                  paymentStateContext.getProperties(),
+                                  paymentStateContext.getCallContext());
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
index f21c20d..6423866 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -22,20 +24,22 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
 import javax.inject.Inject;
 
-import org.skife.jdbi.v2.IDBI;
-
+import org.joda.time.DateTime;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.clock.Clock;
 import org.killbill.billing.entity.EntityPersistenceException;
+import org.killbill.billing.events.BusInternalEvent;
+import org.killbill.billing.payment.api.DefaultPaymentErrorEvent;
+import org.killbill.billing.payment.api.DefaultPaymentInfoEvent;
+import org.killbill.billing.payment.api.DefaultPaymentPluginErrorEvent;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.payment.api.PaymentMethod;
-import org.killbill.billing.payment.api.PaymentStatus;
-import org.killbill.billing.payment.api.Refund;
-import org.killbill.billing.payment.api.RefundStatus;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.entity.Pagination;
@@ -45,19 +49,33 @@ import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.clock.Clock;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 
 public class DefaultPaymentDao implements PaymentDao {
 
+    private final static Logger log = LoggerFactory.getLogger(DefaultPaymentDao.class);
+
     private final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
     private final DefaultPaginationSqlDaoHelper paginationHelper;
+    private final PersistentBus eventBus;
+    private final Clock clock;
 
     @Inject
-    public DefaultPaymentDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
+    public DefaultPaymentDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao, final PersistentBus eventBus) {
         this.transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao);
         this.paginationHelper = new DefaultPaginationSqlDaoHelper(transactionalSqlDao);
+        this.eventBus = eventBus;
+        this.clock = clock;
     }
 
     @Override
@@ -71,112 +89,142 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    public PaymentModelDao insertPaymentWithFirstAttempt(final PaymentModelDao payment, final PaymentAttemptModelDao attempt, final InternalCallContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentModelDao>() {
+    public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final InternalCallContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentAttemptModelDao>() {
 
             @Override
-            public PaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                final PaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentSqlDao.class);
-                transactional.create(payment, context);
+            public PaymentAttemptModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
+                transactional.create(attempt, context);
+                final PaymentAttemptModelDao result = transactional.getById(attempt.getId().toString(), context);
+                return result;
+            }
+        });
+    }
 
-                entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class).create(attempt, context);
+    @Override
+    public void updatePaymentAttempt(final UUID paymentAttemptId, @Nullable final UUID transactionId, final String state, final InternalCallContext context) {
+        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
 
-                return transactional.getById(payment.getId().toString(), context);
+            @Override
+            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                final String transactionIdStr = transactionId != null ? transactionId.toString() : null;
+                final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
+                transactional.updateAttempt(paymentAttemptId.toString(), transactionIdStr, state, context);
+                return null;
             }
         });
     }
 
     @Override
-    public PaymentAttemptModelDao updatePaymentWithNewAttempt(final UUID paymentId, final PaymentAttemptModelDao attempt, final InternalCallContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentAttemptModelDao>() {
+    public List<PaymentAttemptModelDao> getPaymentAttemptsByState(final String stateName, final DateTime createdBeforeDate, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentAttemptModelDao>>() {
             @Override
-            public PaymentAttemptModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+            public List<PaymentAttemptModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
                 final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
-                transactional.create(attempt, context);
-                final PaymentAttemptModelDao savedAttempt = transactional.getById(attempt.getId().toString(), context);
-
-                entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentForNewAttempt(paymentId.toString(), attempt.getPaymentMethodId().toString(),
-                                                                                                  savedAttempt.getRequestedAmount(), attempt.getEffectiveDate().toDate(), context);
-
-                return savedAttempt;
+                return transactional.getByStateName(stateName, createdBeforeDate.toDate(), context);
             }
         });
     }
 
     @Override
-    public void updatePaymentAndAttemptOnCompletion(final UUID paymentId,
-                                                    final PaymentStatus paymentStatus,
-                                                    final BigDecimal processedAmount,
-                                                    final Currency processedCurrency,
-                                                    final UUID attemptId,
-                                                    final String gatewayErrorCode,
-                                                    final String gatewayErrorMsg,
-                                                    final InternalCallContext context) {
-        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+    public List<PaymentAttemptModelDao> getPaymentAttempts(final String paymentExternalKey, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentAttemptModelDao>>() {
 
             @Override
-            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentStatus(paymentId.toString(), processedAmount, processedCurrency, paymentStatus.toString(), context);
-                entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class).updatePaymentAttemptStatus(attemptId.toString(), paymentStatus.toString(), gatewayErrorCode, gatewayErrorMsg, context);
-                return null;
+            public List<PaymentAttemptModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
+                return transactional.getByPaymentExternalKey(paymentExternalKey, context);
             }
         });
     }
 
     @Override
-    public PaymentMethodModelDao insertPaymentMethod(final PaymentMethodModelDao paymentMethod, final InternalCallContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentMethodModelDao>() {
+    public List<PaymentAttemptModelDao> getPaymentAttemptByTransactionExternalKey(final String externalKey, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentAttemptModelDao>>() {
+
             @Override
-            public PaymentMethodModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return insertPaymentMethodInTransaction(entitySqlDaoWrapperFactory, paymentMethod, context);
+            public List<PaymentAttemptModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
+                return transactional.getByTransactionExternalKey(externalKey, context);
             }
         });
     }
 
-    private PaymentMethodModelDao insertPaymentMethodInTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final PaymentMethodModelDao paymentMethod, final InternalCallContext context)
-            throws EntityPersistenceException {
-        final PaymentMethodSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class);
-        transactional.create(paymentMethod, context);
-
-        return transactional.getById(paymentMethod.getId().toString(), context);
+    @Override
+    public int failOldPendingTransactions(final TransactionStatus newTransactionStatus, final DateTime createdBeforeDate, final InternalCallContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Integer>() {
+            @Override
+            public Integer inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                final TransactionSqlDao transactional = entitySqlDaoWrapperFactory.become(TransactionSqlDao.class);
+                final List<PaymentTransactionModelDao> oldPendingTransactions = transactional.getByTransactionStatusPriorDate(TransactionStatus.PENDING.toString(), createdBeforeDate.toDate(), context);
+                if (oldPendingTransactions.size() > 0) {
+                    final Collection<String> oldPendingTransactionIds = Collections2.transform(oldPendingTransactions, new Function<PaymentTransactionModelDao, String>() {
+                        @Override
+                        public String apply(final PaymentTransactionModelDao input) {
+                            return input.getId().toString();
+                        }
+                    });
+                    return transactional.failOldPendingTransactions(oldPendingTransactionIds, TransactionStatus.PAYMENT_FAILURE.toString(), context);
+                }
+                return 0;
+            }
+        });
     }
 
     @Override
-    public RefundModelDao insertRefund(final RefundModelDao refundInfo, final InternalCallContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<RefundModelDao>() {
-
+    public List<PaymentTransactionModelDao> getPaymentTransactionsByExternalKey(final String transactionExternalKey, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentTransactionModelDao>>() {
             @Override
-            public RefundModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                final RefundSqlDao transactional = entitySqlDaoWrapperFactory.become(RefundSqlDao.class);
-                transactional.create(refundInfo, context);
-                return transactional.getById(refundInfo.getId().toString(), context);
+            public List<PaymentTransactionModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).getPaymentTransactionsByExternalKey(transactionExternalKey, context);
             }
         });
     }
 
     @Override
-    public void updateRefundStatus(final UUID refundId, final RefundStatus refundStatus, final BigDecimal processedAmount, final Currency processedCurrency, final InternalCallContext context) {
-        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+    public PaymentModelDao getPaymentByExternalKey(final String paymentExternalKey, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentModelDao>() {
             @Override
-            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                entitySqlDaoWrapperFactory.become(RefundSqlDao.class).updateStatus(refundId.toString(), refundStatus.toString(), processedAmount, processedCurrency, context);
-                return null;
+            public PaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).getPaymentByExternalKey(paymentExternalKey, context);
             }
         });
     }
 
     @Override
-    public Pagination<RefundModelDao> getRefunds(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
-        return paginationHelper.getPagination(RefundSqlDao.class,
-                                              new PaginationIteratorBuilder<RefundModelDao, Refund, RefundSqlDao>() {
+    public Pagination<PaymentModelDao> getPayments(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
+        return paginationHelper.getPagination(PaymentSqlDao.class,
+                                              new PaginationIteratorBuilder<PaymentModelDao, Payment, PaymentSqlDao>() {
+                                                  @Override
+                                                  public Long getCount(final PaymentSqlDao paymentSqlDao, final InternalTenantContext context) {
+                                                      return paymentSqlDao.getCountByPluginName(pluginName, context);
+                                                  }
+
+                                                  @Override
+                                                  public Iterator<PaymentModelDao> build(final PaymentSqlDao paymentSqlDao, final Long limit, final InternalTenantContext context) {
+                                                      final Iterator<PaymentModelDao> result = paymentSqlDao.getByPluginName(pluginName, offset, limit, context);
+                                                      return result;
+                                                  }
+                                              },
+                                              offset,
+                                              limit,
+                                              context
+                                             );
+    }
+
+    @Override
+    public Pagination<PaymentModelDao> searchPayments(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+        return paginationHelper.getPagination(PaymentSqlDao.class,
+                                              new PaginationIteratorBuilder<PaymentModelDao, Payment, PaymentSqlDao>() {
                                                   @Override
-                                                  public Long getCount(final RefundSqlDao refundSqlDao, final InternalTenantContext context) {
-                                                      return refundSqlDao.getCountByPluginName(pluginName, context);
+                                                  public Long getCount(final PaymentSqlDao paymentSqlDao, final InternalTenantContext context) {
+                                                      return paymentSqlDao.getSearchCount(searchKey, String.format("%%%s%%", searchKey), context);
                                                   }
 
                                                   @Override
-                                                  public Iterator<RefundModelDao> build(final RefundSqlDao refundSqlDao, final Long limit, final InternalTenantContext context) {
-                                                      return refundSqlDao.getByPluginName(pluginName, offset, limit, context);
+                                                  public Iterator<PaymentModelDao> build(final PaymentSqlDao paymentSqlDao, final Long limit, final InternalTenantContext context) {
+                                                      return paymentSqlDao.search(searchKey, String.format("%%%s%%", searchKey), offset, limit, context);
                                                   }
                                               },
                                               offset,
@@ -185,147 +233,195 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    public RefundModelDao getRefund(final UUID refundId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<RefundModelDao>() {
+    public PaymentModelDao insertPaymentWithFirstTransaction(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {
+
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentModelDao>() {
+
             @Override
-            public RefundModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(RefundSqlDao.class).getById(refundId.toString(), context);
+            public PaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                final PaymentSqlDao paymentSqlDao = entitySqlDaoWrapperFactory.become(PaymentSqlDao.class);
+                paymentSqlDao.create(payment, context);
+                entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).create(paymentTransaction, context);
+                return paymentSqlDao.getById(payment.getId().toString(), context);
             }
         });
     }
 
     @Override
-    public List<RefundModelDao> getRefundsForPayment(final UUID paymentId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<RefundModelDao>>() {
+    public PaymentTransactionModelDao updatePaymentWithNewTransaction(final UUID paymentId, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentTransactionModelDao>() {
             @Override
-            public List<RefundModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(RefundSqlDao.class).getRefundsForPayment(paymentId.toString(), context);
+            public PaymentTransactionModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                final TransactionSqlDao transactional = entitySqlDaoWrapperFactory.become(TransactionSqlDao.class);
+                transactional.create(paymentTransaction, context);
+                final PaymentTransactionModelDao paymentTransactionModelDao = transactional.getById(paymentTransaction.getId().toString(), context);
+
+                entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentForNewTransaction(paymentId.toString(), context);
+
+                return paymentTransactionModelDao;
             }
         });
     }
 
     @Override
-    public List<RefundModelDao> getRefundsForAccount(final UUID accountId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<RefundModelDao>>() {
+    public void updatePaymentAndTransactionOnCompletion(final UUID accountId, final UUID paymentId, final TransactionType transactionType,
+                                                        final String currentPaymentStateName, @Nullable final String lastPaymentSuccessStateName,
+                                                        final UUID transactionId, final TransactionStatus transactionStatus,
+                                                        final BigDecimal processedAmount, final Currency processedCurrency,
+                                                        final String gatewayErrorCode, final String gatewayErrorMsg,
+                                                        final InternalCallContext context) {
+        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+
             @Override
-            public List<RefundModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(RefundSqlDao.class).getRefundsForAccount(accountId.toString(), context);
+            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).updateTransactionStatus(transactionId.toString(),
+                                                                                                   processedAmount, processedCurrency == null ? null : processedCurrency.toString(),
+                                                                                                   transactionStatus == null ? null : transactionStatus.toString(),
+                                                                                                   gatewayErrorCode, gatewayErrorMsg, context);
+                if (lastPaymentSuccessStateName != null) {
+                    entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updateLastSuccessPaymentStateName(paymentId.toString(), currentPaymentStateName, lastPaymentSuccessStateName, context);
+                } else {
+                    entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).updatePaymentStateName(paymentId.toString(), currentPaymentStateName, context);
+                }
+                postPaymentEventFromTransaction(accountId, transactionStatus, transactionType, paymentId, processedAmount, processedCurrency, clock.getUTCNow(), gatewayErrorCode, entitySqlDaoWrapperFactory, context);
+                return null;
             }
         });
+
     }
 
     @Override
-    public PaymentMethodModelDao getPaymentMethod(final UUID paymentMethodId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentMethodModelDao>() {
+    public PaymentModelDao getPayment(final UUID paymentId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentModelDao>() {
             @Override
-            public PaymentMethodModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getById(paymentMethodId.toString(), context);
+            public PaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).getById(paymentId.toString(), context);
             }
         });
     }
 
     @Override
-    public PaymentMethodModelDao getPaymentMethodIncludedDeleted(final UUID paymentMethodId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentMethodModelDao>() {
+    public PaymentTransactionModelDao getPaymentTransaction(final UUID transactionId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentTransactionModelDao>() {
             @Override
-            public PaymentMethodModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getPaymentMethodIncludedDelete(paymentMethodId.toString(), context);
+            public PaymentTransactionModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).getById(transactionId.toString(), context);
             }
         });
     }
 
     @Override
-    public List<PaymentMethodModelDao> getPaymentMethods(final UUID accountId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentMethodModelDao>>() {
+    public List<PaymentModelDao> getPaymentsForAccount(final UUID accountId, final InternalTenantContext context) {
+        Preconditions.checkArgument(context.getAccountRecordId() != null);
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentModelDao>>() {
             @Override
-            public List<PaymentMethodModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getByAccountId(accountId.toString(), context);
+            public List<PaymentModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).getByAccountRecordId(context);
             }
         });
     }
 
     @Override
-    public Pagination<PaymentMethodModelDao> getPaymentMethods(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
-        return paginationHelper.getPagination(PaymentMethodSqlDao.class,
-                                              new PaginationIteratorBuilder<PaymentMethodModelDao, PaymentMethod, PaymentMethodSqlDao>() {
-                                                  @Override
-                                                  public Long getCount(final PaymentMethodSqlDao paymentMethodSqlDao, final InternalTenantContext context) {
-                                                      return paymentMethodSqlDao.getCountByPluginName(pluginName, context);
-                                                  }
+    public List<PaymentTransactionModelDao> getTransactionsForAccount(final UUID accountId, final InternalTenantContext context) {
+        Preconditions.checkArgument(context.getAccountRecordId() != null);
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentTransactionModelDao>>() {
+            @Override
+            public List<PaymentTransactionModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).getByAccountRecordId(context);
+            }
+        });
+    }
 
-                                                  @Override
-                                                  public Iterator<PaymentMethodModelDao> build(final PaymentMethodSqlDao paymentMethodSqlDao, final Long limit, final InternalTenantContext context) {
-                                                      return paymentMethodSqlDao.getByPluginName(pluginName, offset, limit, context);
-                                                  }
-                                              },
-                                              offset,
-                                              limit,
-                                              context);
+    @Override
+    public List<PaymentTransactionModelDao> getTransactionsForPayment(final UUID paymentId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentTransactionModelDao>>() {
+            @Override
+            public List<PaymentTransactionModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(TransactionSqlDao.class).getByPaymentId(paymentId, context);
+            }
+        });
     }
 
     @Override
-    public void deletedPaymentMethod(final UUID paymentMethodId, final InternalCallContext context) {
-        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+    public PaymentMethodModelDao insertPaymentMethod(final PaymentMethodModelDao paymentMethod, final InternalCallContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentMethodModelDao>() {
             @Override
-            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                deletedPaymentMethodInTransaction(entitySqlDaoWrapperFactory, paymentMethodId, context);
-                return null;
+            public PaymentMethodModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return insertPaymentMethodInTransaction(entitySqlDaoWrapperFactory, paymentMethod, context);
             }
         });
     }
 
-    private void deletedPaymentMethodInTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID paymentMethodId, final InternalCallContext context) {
-        entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).markPaymentMethodAsDeleted(paymentMethodId.toString(), context);
+    private PaymentMethodModelDao insertPaymentMethodInTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final PaymentMethodModelDao paymentMethod, final InternalCallContext context)
+            throws EntityPersistenceException {
+        final PaymentMethodSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class);
+        transactional.create(paymentMethod, context);
+
+        return transactional.getById(paymentMethod.getId().toString(), context);
     }
 
     @Override
-    public void undeletedPaymentMethod(final UUID paymentMethodId, final InternalCallContext context) {
-        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+    public PaymentMethodModelDao getPaymentMethod(final UUID paymentMethodId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentMethodModelDao>() {
             @Override
-            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                undeletedPaymentMethodInTransaction(entitySqlDaoWrapperFactory, paymentMethodId, context);
-                return null;
+            public PaymentMethodModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getById(paymentMethodId.toString(), context);
             }
         });
     }
 
-    private void undeletedPaymentMethodInTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID paymentMethodId, final InternalCallContext context) {
-        final PaymentMethodSqlDao paymentMethodSqlDao = entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class);
-        paymentMethodSqlDao.unmarkPaymentMethodAsDeleted(paymentMethodId.toString(), context);
+    @Override
+    public PaymentMethodModelDao getPaymentMethodByExternalKey(final String paymentMethodExternalKey, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentMethodModelDao>() {
+            @Override
+            public PaymentMethodModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getByExternalKey(paymentMethodExternalKey, context);
+            }
+        });
     }
 
     @Override
-    public List<PaymentModelDao> getPaymentsForInvoice(final UUID invoiceId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentModelDao>>() {
+    public PaymentMethodModelDao getPaymentMethodIncludedDeleted(final UUID paymentMethodId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentMethodModelDao>() {
             @Override
-            public List<PaymentModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).getPaymentsForInvoice(invoiceId.toString(), context);
+            public PaymentMethodModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getPaymentMethodIncludedDelete(paymentMethodId.toString(), context);
             }
         });
     }
 
     @Override
-    public PaymentModelDao getLastPaymentForPaymentMethod(final UUID accountId, final UUID paymentMethodId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentModelDao>() {
+    public PaymentMethodModelDao getPaymentMethodByExternalKeyIncludedDeleted(final String paymentMethodExternalKey, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentMethodModelDao>() {
             @Override
-            public PaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).getLastPaymentForAccountAndPaymentMethod(accountId.toString(), paymentMethodId.toString(), context);
+            public PaymentMethodModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getPaymentMethodByExternalKeyIncludedDeleted(paymentMethodExternalKey, context);
             }
         });
     }
 
     @Override
-    public Pagination<PaymentModelDao> getPayments(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
-        return paginationHelper.getPagination(PaymentSqlDao.class,
-                                              new PaginationIteratorBuilder<PaymentModelDao, Payment, PaymentSqlDao>() {
+    public List<PaymentMethodModelDao> getPaymentMethods(final UUID accountId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentMethodModelDao>>() {
+            @Override
+            public List<PaymentMethodModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).getByAccountId(accountId.toString(), context);
+            }
+        });
+    }
+
+    @Override
+    public Pagination<PaymentMethodModelDao> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+        return paginationHelper.getPagination(PaymentMethodSqlDao.class,
+                                              new PaginationIteratorBuilder<PaymentMethodModelDao, PaymentMethod, PaymentMethodSqlDao>() {
                                                   @Override
-                                                  public Long getCount(final PaymentSqlDao paymentSqlDao, final InternalTenantContext context) {
-                                                      return paymentSqlDao.getCountByPluginName(pluginName, context);
+                                                  public Long getCount(final PaymentMethodSqlDao paymentMethodSqlDao, final InternalTenantContext context) {
+                                                      return paymentMethodSqlDao.getSearchCount(searchKey, String.format("%%%s%%", searchKey), context);
                                                   }
 
                                                   @Override
-                                                  public Iterator<PaymentModelDao> build(final PaymentSqlDao paymentSqlDao, final Long limit, final InternalTenantContext context) {
-                                                      return paymentSqlDao.getByPluginName(pluginName, offset, limit, context);
+                                                  public Iterator<PaymentMethodModelDao> build(final PaymentMethodSqlDao paymentMethodSqlDao, final Long limit, final InternalTenantContext context) {
+                                                      return paymentMethodSqlDao.search(searchKey, String.format("%%%s%%", searchKey), offset, limit, context);
                                                   }
                                               },
                                               offset,
@@ -334,33 +430,43 @@ public class DefaultPaymentDao implements PaymentDao {
     }
 
     @Override
-    public PaymentModelDao getPayment(final UUID paymentId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentModelDao>() {
-            @Override
-            public PaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).getById(paymentId.toString(), context);
-            }
-        });
+    public Pagination<PaymentMethodModelDao> getPaymentMethods(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
+        return paginationHelper.getPagination(PaymentMethodSqlDao.class,
+                                              new PaginationIteratorBuilder<PaymentMethodModelDao, PaymentMethod, PaymentMethodSqlDao>() {
+                                                  @Override
+                                                  public Long getCount(final PaymentMethodSqlDao paymentMethodSqlDao, final InternalTenantContext context) {
+                                                      return paymentMethodSqlDao.getCountByPluginName(pluginName, context);
+                                                  }
+
+                                                  @Override
+                                                  public Iterator<PaymentMethodModelDao> build(final PaymentMethodSqlDao paymentMethodSqlDao, final Long limit, final InternalTenantContext context) {
+                                                      return paymentMethodSqlDao.getByPluginName(pluginName, offset, limit, context);
+                                                  }
+                                              },
+                                              offset,
+                                              limit,
+                                              context
+                                             );
     }
 
     @Override
-    public List<PaymentModelDao> getPaymentsForAccount(final UUID accountId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentModelDao>>() {
+    public void deletedPaymentMethod(final UUID paymentMethodId, final InternalCallContext context) {
+        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
             @Override
-            public List<PaymentModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(PaymentSqlDao.class).getPaymentsForAccount(accountId.toString(), context);
+            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                deletedPaymentMethodInTransaction(entitySqlDaoWrapperFactory, paymentMethodId, context);
+                return null;
             }
         });
     }
 
-    @Override
-    public List<PaymentAttemptModelDao> getAttemptsForPayment(final UUID paymentId, final InternalTenantContext context) {
-        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PaymentAttemptModelDao>>() {
-            @Override
-            public List<PaymentAttemptModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                return entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class).getByPaymentId(paymentId.toString(), context);
-            }
-        });
+    private void deletedPaymentMethodInTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID paymentMethodId, final InternalCallContext context) {
+        entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class).markPaymentMethodAsDeleted(paymentMethodId.toString(), context);
+    }
+
+    private void undeletedPaymentMethodInTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory, final UUID paymentMethodId, final InternalCallContext context) {
+        final PaymentMethodSqlDao paymentMethodSqlDao = entitySqlDaoWrapperFactory.become(PaymentMethodSqlDao.class);
+        paymentMethodSqlDao.unmarkPaymentMethodAsDeleted(paymentMethodId.toString(), context);
     }
 
     @Override
@@ -383,7 +489,8 @@ public class DefaultPaymentDao implements PaymentDao {
                                                                                                          public boolean apply(final PaymentMethodModelDao paymentMethod) {
                                                                                                              return pluginName.equals(paymentMethod.getPluginName());
                                                                                                          }
-                                                                                                     });
+                                                                                                     }
+                                                                                                    );
 
                 for (final PaymentMethodModelDao finalPaymentMethod : newPaymentMethods) {
                     PaymentMethodModelDao foundExistingPaymentMethod = null;
@@ -421,4 +528,59 @@ public class DefaultPaymentDao implements PaymentDao {
             }
         });
     }
+
+    private void postPaymentEventFromTransaction(final UUID accountId,
+                                                 final TransactionStatus transactionStatus,
+                                                 final TransactionType transactionType,
+                                                 final UUID paymentId,
+                                                 final BigDecimal processedAmount,
+                                                 final Currency processedCurrency,
+                                                 final DateTime effectiveDate,
+                                                 final String gatewayErrorCode,
+                                                 final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory,
+                                                 final InternalCallContext context) {
+
+        final BusInternalEvent event;
+        switch (transactionStatus) {
+            case SUCCESS:
+            case PENDING:
+                event = new DefaultPaymentInfoEvent(accountId,
+                                                    paymentId,
+                                                    processedAmount,
+                                                    processedCurrency,
+                                                    transactionStatus,
+                                                    transactionType,
+                                                    effectiveDate,
+                                                    context.getAccountRecordId(),
+                                                    context.getTenantRecordId(),
+                                                    context.getUserToken());
+                break;
+
+            case PAYMENT_FAILURE:
+                event = new DefaultPaymentErrorEvent(accountId,
+                                                     paymentId,
+                                                     transactionType,
+                                                     gatewayErrorCode,
+                                                     context.getAccountRecordId(),
+                                                     context.getTenantRecordId(),
+                                                     context.getUserToken());
+                break;
+
+            case PLUGIN_FAILURE:
+            default:
+                event = new DefaultPaymentPluginErrorEvent(accountId,
+                                                           paymentId,
+                                                           transactionType,
+                                                           gatewayErrorCode,
+                                                           context.getAccountRecordId(),
+                                                           context.getTenantRecordId(),
+                                                           context.getUserToken());
+                break;
+        }
+        try {
+            eventBus.postFromTransaction(event, entitySqlDaoWrapperFactory.getSqlDao());
+        } catch (EventBusException e) {
+            log.error("Failed to post Payment event event for account {} ", accountId, e);
+        }
+    }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
index 56d4d01..fdc1354 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
@@ -17,163 +17,146 @@
 package org.killbill.billing.payment.dao;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.payment.api.PaymentAttempt;
-import org.killbill.billing.payment.api.PaymentStatus;
-import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.util.dao.TableName;
+import org.killbill.billing.util.entity.Entity;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao<PaymentAttempt> {
+public class PaymentAttemptModelDao extends EntityModelDaoBase implements EntityModelDao<Entity> {
 
     private UUID accountId;
-    private UUID invoiceId;
-    private UUID paymentId;
     private UUID paymentMethodId;
-    private PaymentStatus processingStatus;
-    private DateTime effectiveDate;
-    private String gatewayErrorCode;
-    private String gatewayErrorMsg;
-    private BigDecimal requestedAmount;
-    private Currency requestedCurrency;
+    private String paymentExternalKey;
+    private UUID transactionId;
+    private String transactionExternalKey;
+    private TransactionType transactionType;
+    private String stateName;
+    private BigDecimal amount;
+    private Currency currency;
+    private String pluginName;
+    private byte [] pluginProperties;
 
     public PaymentAttemptModelDao() { /* For the DAO mapper */ }
 
-    public PaymentAttemptModelDao(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
-                                  final UUID accountId, final UUID invoiceId,
-                                  final UUID paymentId, final UUID paymentMethodId,
-                                  final PaymentStatus processingStatus, final DateTime effectiveDate,
-                                  final BigDecimal requestedAmount, final Currency requestedCurrency,
-                                  final String gatewayErrorCode, final String gatewayErrorMsg) {
+    public PaymentAttemptModelDao(final UUID accountId, final UUID paymentMethodId, final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
+                                  final String paymentExternalKey, final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType,
+                                  final String stateName, final BigDecimal amount, final Currency currency, final String pluginName, final byte [] pluginProperties) {
         super(id, createdDate, updatedDate);
         this.accountId = accountId;
-        this.invoiceId = invoiceId;
-        this.paymentId = paymentId;
         this.paymentMethodId = paymentMethodId;
-        this.processingStatus = processingStatus;
-        this.effectiveDate = effectiveDate;
-        this.requestedAmount = requestedAmount;
-        this.requestedCurrency = requestedCurrency;
-        this.gatewayErrorCode = gatewayErrorCode;
-        this.gatewayErrorMsg = gatewayErrorMsg;
+        this.paymentExternalKey = paymentExternalKey;
+        this.transactionId = transactionId;
+        this.transactionExternalKey = transactionExternalKey;
+        this.transactionType = transactionType;
+        this.stateName = stateName;
+        this.amount = amount;
+        this.currency = currency;
+        this.pluginName = pluginName;
+        this.pluginProperties = pluginProperties;
     }
 
-    public PaymentAttemptModelDao(final UUID accountId, final UUID invoiceId, final UUID paymentId, final UUID paymentMethodId, final PaymentStatus paymentStatus, final DateTime effectiveDate,
-                                  final BigDecimal requestedAmount, final Currency requestedCurrency) {
-        this(UUID.randomUUID(), null, null, accountId, invoiceId, paymentId, paymentMethodId, paymentStatus, effectiveDate, requestedAmount, requestedCurrency, null, null);
+    public PaymentAttemptModelDao(final UUID accountId, final UUID paymentMethodId, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
+                                  final String paymentExternalKey, final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType, final String stateName,
+                                  final BigDecimal amount, final Currency currency, final String pluginName,  final byte [] pluginProperties) {
+        this(accountId, paymentMethodId, UUID.randomUUID(), createdDate, updatedDate, paymentExternalKey, transactionId, transactionExternalKey, transactionType, stateName,
+             amount, currency, pluginName, pluginProperties);
     }
 
-    public PaymentAttemptModelDao(final UUID accountId, final UUID invoiceId, final UUID paymentId, final UUID paymentMethodId, final DateTime effectiveDate,
-                                  final BigDecimal requestedAmount, final Currency requestedCurrency) {
-        this(UUID.randomUUID(), null, null, accountId, invoiceId, paymentId, paymentMethodId, PaymentStatus.UNKNOWN, effectiveDate, requestedAmount, requestedCurrency, null, null);
+    public String getPaymentExternalKey() {
+        return paymentExternalKey;
     }
 
-    public PaymentAttemptModelDao(final PaymentAttemptModelDao src, final PaymentStatus newProcessingStatus, final String gatewayErrorCode, final String gatewayErrorMsg) {
-        this(src.getId(), src.getCreatedDate(), src.getUpdatedDate(), src.getAccountId(), src.getInvoiceId(), src.getPaymentId(), src.getPaymentMethodId(),
-             newProcessingStatus, src.getEffectiveDate(), src.getRequestedAmount(), src.getRequestedCurrency(), gatewayErrorCode, gatewayErrorMsg);
+    public void setPaymentExternalKey(final String paymentExternalKey) {
+        this.paymentExternalKey = paymentExternalKey;
     }
 
-    public UUID getAccountId() {
-        return accountId;
+    public UUID getTransactionId() {
+        return transactionId;
     }
 
-    public UUID getInvoiceId() {
-        return invoiceId;
+    public void setTransactionId(final UUID transactionId) {
+        this.transactionId = transactionId;
     }
 
-    public UUID getPaymentId() {
-        return paymentId;
+    public String getTransactionExternalKey() {
+        return transactionExternalKey;
     }
 
-    public UUID getPaymentMethodId() {
-        return paymentMethodId;
+    public void setTransactionExternalKey(final String transactionExternalKey) {
+        this.transactionExternalKey = transactionExternalKey;
     }
 
-    public PaymentStatus getProcessingStatus() {
-        return processingStatus;
+    public String getStateName() {
+        return stateName;
     }
 
-    public DateTime getEffectiveDate() {
-        return effectiveDate;
+    public void setStateName(final String stateName) {
+        this.stateName = stateName;
     }
 
-    public String getGatewayErrorCode() {
-        return gatewayErrorCode;
+    public String getPluginName() {
+        return pluginName;
     }
 
-    public String getGatewayErrorMsg() {
-        return gatewayErrorMsg;
+    public void setPluginName(final String pluginName) {
+        this.pluginName = pluginName;
     }
 
-    public BigDecimal getRequestedAmount() {
-        return requestedAmount;
+    public byte [] getPluginProperties() {
+        return pluginProperties;
     }
 
-    public Currency getRequestedCurrency() {
-        return requestedCurrency;
+    public void setPluginProperties(final byte [] pluginProperties) {
+        this.pluginProperties = pluginProperties;
     }
 
-    public void setAccountId(final UUID accountId) {
-        this.accountId = accountId;
-    }
-
-    public void setInvoiceId(final UUID invoiceId) {
-        this.invoiceId = invoiceId;
+    public UUID getAccountId() {
+        return accountId;
     }
 
-    public void setPaymentId(final UUID paymentId) {
-        this.paymentId = paymentId;
+    public UUID getPaymentMethodId() {
+        return paymentMethodId;
     }
 
-    public void setPaymentMethodId(final UUID paymentMethodId) {
-        this.paymentMethodId = paymentMethodId;
+    public TransactionType getTransactionType() {
+        return transactionType;
     }
 
-    public void setProcessingStatus(final PaymentStatus processingStatus) {
-        this.processingStatus = processingStatus;
+    public BigDecimal getAmount() {
+        return amount;
     }
 
-    public void setEffectiveDate(final DateTime effectiveDate) {
-        this.effectiveDate = effectiveDate;
+    public Currency getCurrency() {
+        return currency;
     }
 
-    public void setGatewayErrorCode(final String gatewayErrorCode) {
-        this.gatewayErrorCode = gatewayErrorCode;
+    public void setAccountId(final UUID accountId) {
+        this.accountId = accountId;
     }
 
-    public void setGatewayErrorMsg(final String gatewayErrorMsg) {
-        this.gatewayErrorMsg = gatewayErrorMsg;
+    public void setPaymentMethodId(final UUID paymentMethodId) {
+        this.paymentMethodId = paymentMethodId;
     }
 
-    public void setRequestedAmount(final BigDecimal requestedAmount) {
-        this.requestedAmount = requestedAmount;
+    public void setTransactionType(final TransactionType transactionType) {
+        this.transactionType = transactionType;
     }
 
-    public void setRequestedCurrency(final Currency requestedCurrency) {
-        this.requestedCurrency = requestedCurrency;
+    public void setAmount(final BigDecimal amount) {
+        this.amount = amount;
     }
 
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("PaymentAttemptModelDao");
-        sb.append("{accountId=").append(accountId);
-        sb.append(", invoiceId=").append(invoiceId);
-        sb.append(", paymentId=").append(paymentId);
-        sb.append(", processingStatus=").append(processingStatus);
-        sb.append(", effectiveDate=").append(effectiveDate);
-        sb.append(", gatewayErrorCode='").append(gatewayErrorCode).append('\'');
-        sb.append(", gatewayErrorMsg='").append(gatewayErrorMsg).append('\'');
-        sb.append(", requestedAmount=").append(requestedAmount);
-        sb.append(", requestedCurrency=").append(requestedCurrency);
-        sb.append('}');
-        return sb.toString();
+    public void setCurrency(final Currency currency) {
+        this.currency = currency;
     }
 
     @Override
@@ -181,7 +164,7 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!(o instanceof PaymentAttemptModelDao)) {
             return false;
         }
         if (!super.equals(o)) {
@@ -193,33 +176,34 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
         if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
         }
-        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
+        if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) {
             return false;
         }
-        if (gatewayErrorCode != null ? !gatewayErrorCode.equals(that.gatewayErrorCode) : that.gatewayErrorCode != null) {
+        if (currency != that.currency) {
             return false;
         }
-        if (gatewayErrorMsg != null ? !gatewayErrorMsg.equals(that.gatewayErrorMsg) : that.gatewayErrorMsg != null) {
+        if (paymentExternalKey != null ? !paymentExternalKey.equals(that.paymentExternalKey) : that.paymentExternalKey != null) {
             return false;
         }
-        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+        if (paymentMethodId != null ? !paymentMethodId.equals(that.paymentMethodId) : that.paymentMethodId != null) {
             return false;
         }
-        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+        if (pluginName != null ? !pluginName.equals(that.pluginName) : that.pluginName != null) {
             return false;
         }
-        if (paymentMethodId != null ? !paymentMethodId.equals(that.paymentMethodId) : that.paymentMethodId != null) {
+        if (stateName != null ? !stateName.equals(that.stateName) : that.stateName != null) {
             return false;
         }
-        if (processingStatus != that.processingStatus) {
+        if (transactionExternalKey != null ? !transactionExternalKey.equals(that.transactionExternalKey) : that.transactionExternalKey != null) {
             return false;
         }
-        if (requestedAmount != null ? !requestedAmount.equals(that.requestedAmount) : that.requestedAmount != null) {
+        if (transactionId != null ? !transactionId.equals(that.transactionId) : that.transactionId != null) {
             return false;
         }
-        if (requestedCurrency != that.requestedCurrency) {
+        if (transactionType != that.transactionType) {
             return false;
         }
+
         return true;
     }
 
@@ -227,15 +211,15 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
     public int hashCode() {
         int result = super.hashCode();
         result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
-        result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
-        result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
         result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
-        result = 31 * result + (processingStatus != null ? processingStatus.hashCode() : 0);
-        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
-        result = 31 * result + (gatewayErrorCode != null ? gatewayErrorCode.hashCode() : 0);
-        result = 31 * result + (gatewayErrorMsg != null ? gatewayErrorMsg.hashCode() : 0);
-        result = 31 * result + (requestedAmount != null ? requestedAmount.hashCode() : 0);
-        result = 31 * result + (requestedCurrency != null ? requestedCurrency.hashCode() : 0);
+        result = 31 * result + (paymentExternalKey != null ? paymentExternalKey.hashCode() : 0);
+        result = 31 * result + (transactionId != null ? transactionId.hashCode() : 0);
+        result = 31 * result + (transactionExternalKey != null ? transactionExternalKey.hashCode() : 0);
+        result = 31 * result + (transactionType != null ? transactionType.hashCode() : 0);
+        result = 31 * result + (stateName != null ? stateName.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (pluginName != null ? pluginName.hashCode() : 0);
         return result;
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java
index 01355ca..e3a3fe4 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.java
@@ -16,33 +16,42 @@
 
 package org.killbill.billing.payment.dao;
 
+import java.util.Date;
 import java.util.List;
 
-import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
-import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-
-import org.killbill.billing.payment.api.PaymentAttempt;
-import org.killbill.billing.util.audit.ChangeType;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.entity.Entity;
 import org.killbill.billing.util.entity.dao.Audited;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 
 @EntitySqlDaoStringTemplate
-public interface PaymentAttemptSqlDao extends EntitySqlDao<PaymentAttemptModelDao, PaymentAttempt> {
+public interface PaymentAttemptSqlDao extends EntitySqlDao<PaymentAttemptModelDao, Entity> {
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
-    void updatePaymentAttemptStatus(@Bind("id") final String attemptId,
-                                    @Bind("processingStatus") final String processingStatus,
-                                    @Bind("gatewayErrorCode") final String gatewayErrorCode,
-                                    @Bind("gatewayErrorMsg") final String gatewayErrorMsg,
-                                    @BindBean final InternalCallContext context);
+    void updateAttempt(@Bind("id") final String attemptId,
+                       @Bind("transactionId") final String transactionId,
+                       @Bind("stateName") final String stateName,
+                       @BindBean final InternalCallContext context);
+
+    @SqlQuery
+    List<PaymentAttemptModelDao> getByTransactionExternalKey(@Bind("transactionExternalKey") final String transactionExternalKey,
+                                                             @BindBean final InternalTenantContext context);
+
+    @SqlQuery
+    List<PaymentAttemptModelDao> getByPaymentExternalKey(@Bind("paymentExternalKey") final String paymentExternalKey,
+                                                         @BindBean final InternalTenantContext context);
 
     @SqlQuery
-    List<PaymentAttemptModelDao> getByPaymentId(@Bind("paymentId") final String paymentId,
+    List<PaymentAttemptModelDao> getByStateName(@Bind("stateName") final String stateName,
+                                                @Bind("createdBeforeDate") final Date createdBeforeDate,
                                                 @BindBean final InternalTenantContext context);
+
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
index 7649fc0..8055938 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
@@ -20,62 +20,73 @@ import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.payment.api.PaymentStatus;
-import org.killbill.billing.payment.api.RefundStatus;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.entity.Pagination;
 
 public interface PaymentDao {
 
-    public PaymentModelDao insertPaymentWithFirstAttempt(PaymentModelDao paymentInfo, PaymentAttemptModelDao attempt, InternalCallContext context);
+    public int failOldPendingTransactions(final TransactionStatus newTransactionStatus, final DateTime createdBeforeDate, final InternalCallContext context);
 
-    public PaymentAttemptModelDao updatePaymentWithNewAttempt(UUID paymentId, PaymentAttemptModelDao attempt, InternalCallContext context);
+    public PaymentAttemptModelDao insertPaymentAttemptWithProperties(PaymentAttemptModelDao attempt, InternalCallContext context);
 
-    public void updatePaymentAndAttemptOnCompletion(UUID paymentId, PaymentStatus paymentStatus,
-                                                    BigDecimal processedAmount, Currency processedCurrency,
-                                                    UUID attemptId, String gatewayErrorMsg, String gatewayErrorCode, InternalCallContext context);
+    public void updatePaymentAttempt(UUID paymentAttemptId, UUID transactionId, String state, InternalCallContext context);
 
-    public PaymentAttemptModelDao getPaymentAttempt(UUID attemptId, InternalTenantContext context);
+    public List<PaymentAttemptModelDao> getPaymentAttemptsByState(String stateName, final DateTime createdBeforeDate, InternalTenantContext context);
 
-    public List<PaymentModelDao> getPaymentsForInvoice(UUID invoiceId, InternalTenantContext context);
+    public List<PaymentAttemptModelDao> getPaymentAttempts(String paymentExternalKey, InternalTenantContext context);
 
-    public List<PaymentModelDao> getPaymentsForAccount(UUID accountId, InternalTenantContext context);
+    public List<PaymentAttemptModelDao> getPaymentAttemptByTransactionExternalKey(String externalKey, InternalTenantContext context);
 
-    public PaymentModelDao getLastPaymentForPaymentMethod(UUID accountId, UUID paymentMethodId, InternalTenantContext context);
+    public List<PaymentTransactionModelDao> getPaymentTransactionsByExternalKey(String transactionExternalKey, InternalTenantContext context);
+
+    public PaymentModelDao getPaymentByExternalKey(String externalKey, InternalTenantContext context);
 
     public Pagination<PaymentModelDao> getPayments(String pluginName, Long offset, Long limit, InternalTenantContext context);
 
-    public PaymentModelDao getPayment(UUID paymentId, InternalTenantContext context);
+    public Pagination<PaymentModelDao> searchPayments(String searchKey, Long offset, Long limit, InternalTenantContext context);
 
-    public List<PaymentAttemptModelDao> getAttemptsForPayment(UUID paymentId, InternalTenantContext context);
+    public PaymentModelDao insertPaymentWithFirstTransaction(PaymentModelDao payment, PaymentTransactionModelDao paymentTransaction, InternalCallContext context);
 
-    public RefundModelDao insertRefund(RefundModelDao refundInfo, InternalCallContext context);
+    public PaymentTransactionModelDao updatePaymentWithNewTransaction(UUID paymentId, PaymentTransactionModelDao paymentTransaction, InternalCallContext context);
 
-    public void updateRefundStatus(UUID refundId, RefundStatus status, BigDecimal processedAmount, Currency processedCurrency, InternalCallContext context);
+    public void updatePaymentAndTransactionOnCompletion(UUID accountId, UUID paymentId, final TransactionType transactionType, String currentPaymentStateName, String lastPaymentSuccessStateName, UUID transactionId,
+                                                        TransactionStatus paymentStatus, BigDecimal processedAmount, Currency processedCurrency,
+                                                        String gatewayErrorCode, String gatewayErrorMsg, InternalCallContext context);
 
-    public Pagination<RefundModelDao> getRefunds(String pluginName, Long offset, Long limit, InternalTenantContext context);
+    public PaymentModelDao getPayment(UUID paymentId, InternalTenantContext context);
+
+    public PaymentTransactionModelDao getPaymentTransaction(UUID transactionId, InternalTenantContext context);
 
-    public RefundModelDao getRefund(UUID refundId, InternalTenantContext context);
+    public List<PaymentModelDao> getPaymentsForAccount(UUID accountId, InternalTenantContext context);
 
-    public List<RefundModelDao> getRefundsForPayment(UUID paymentId, InternalTenantContext context);
+    public List<PaymentTransactionModelDao> getTransactionsForAccount(UUID accountId, InternalTenantContext context);
 
-    public List<RefundModelDao> getRefundsForAccount(UUID accountId, InternalTenantContext context);
+    public List<PaymentTransactionModelDao> getTransactionsForPayment(UUID paymentId, InternalTenantContext context);
+
+    public PaymentAttemptModelDao getPaymentAttempt(UUID attemptId, InternalTenantContext context);
 
     public PaymentMethodModelDao insertPaymentMethod(PaymentMethodModelDao paymentMethod, InternalCallContext context);
 
     public PaymentMethodModelDao getPaymentMethod(UUID paymentMethodId, InternalTenantContext context);
 
+    public PaymentMethodModelDao getPaymentMethodByExternalKey(String paymentMethodExternalKey, InternalTenantContext context);
+
     public PaymentMethodModelDao getPaymentMethodIncludedDeleted(UUID paymentMethodId, InternalTenantContext context);
 
+    public PaymentMethodModelDao getPaymentMethodByExternalKeyIncludedDeleted(String paymentMethodExternalKey, InternalTenantContext context);
+
     public List<PaymentMethodModelDao> getPaymentMethods(UUID accountId, InternalTenantContext context);
 
     public Pagination<PaymentMethodModelDao> getPaymentMethods(String pluginName, Long offset, Long limit, InternalTenantContext context);
 
+    public Pagination<PaymentMethodModelDao> searchPaymentMethods(String searchKey, Long offset, Long limit, InternalTenantContext context);
+
     public void deletedPaymentMethod(UUID paymentMethodId, InternalCallContext context);
 
     public List<PaymentMethodModelDao> refreshPaymentMethods(UUID accountId, String pluginName, List<PaymentMethodModelDao> paymentMethods, InternalCallContext context);
-
-    public void undeletedPaymentMethod(UUID paymentMethodId, InternalCallContext context);
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodModelDao.java
index 7d0e05e..7e284de 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodModelDao.java
@@ -26,24 +26,37 @@ import org.killbill.billing.payment.api.PaymentMethod;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class PaymentMethodModelDao extends EntityBase implements EntityModelDao<PaymentMethod> {
+import com.google.common.base.Objects;
 
+public class PaymentMethodModelDao extends EntityModelDaoBase implements EntityModelDao<PaymentMethod> {
+
+    private String externalKey;
     private UUID accountId;
     private String pluginName;
     private Boolean isActive;
 
     public PaymentMethodModelDao() { /* For the DAO mapper */ }
 
-    public PaymentMethodModelDao(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
+    public PaymentMethodModelDao(final UUID id,  @Nullable final String externalKey, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
                                  final UUID accountId, final String pluginName,
                                  final Boolean isActive) {
         super(id, createdDate, updatedDate);
+        this.externalKey = Objects.firstNonNull(externalKey, id.toString());
         this.accountId = accountId;
         this.pluginName = pluginName;
         this.isActive = isActive;
     }
 
+    public String getExternalKey() {
+        return externalKey;
+    }
+
+    public void setExternalKey(final String externalKey) {
+        this.externalKey = externalKey;
+    }
+
     public UUID getAccountId() {
         return accountId;
     }
@@ -52,6 +65,18 @@ public class PaymentMethodModelDao extends EntityBase implements EntityModelDao<
         return pluginName;
     }
 
+    public void setAccountId(final UUID accountId) {
+        this.accountId = accountId;
+    }
+
+    public void setPluginName(final String pluginName) {
+        this.pluginName = pluginName;
+    }
+
+    public void setIsActive(final Boolean isActive) {
+        this.isActive = isActive;
+    }
+
     // TODO  Required for making the BindBeanFactory with Introspector work
     public Boolean getIsActive() {
         return isActive;
@@ -98,6 +123,12 @@ public class PaymentMethodModelDao extends EntityBase implements EntityModelDao<
         if (id != null ? !id.equals(that.id) : that.id != null) {
             return false;
         }
+        /*
+        TODO unclear
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
+            return false;
+        }
+        */
         if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
         }
@@ -111,6 +142,7 @@ public class PaymentMethodModelDao extends EntityBase implements EntityModelDao<
     @Override
     public int hashCode() {
         int result = accountId != null ? accountId.hashCode() : 0;
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
         result = 31 * result + (pluginName != null ? pluginName.hashCode() : 0);
         result = 31 * result + (isActive != null ? isActive.hashCode() : 0);
         return result;
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
index 659d086..0db4703 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentMethodSqlDao.java
@@ -47,6 +47,12 @@ public interface PaymentMethodSqlDao extends EntitySqlDao<PaymentMethodModelDao,
                                       @BindBean final InternalCallContext context);
 
     @SqlQuery
+    PaymentMethodModelDao getByExternalKey(@Bind("externalKey") String paymentMethodExternalKey, @BindBean InternalTenantContext context);
+
+    @SqlQuery
+    PaymentMethodModelDao getPaymentMethodByExternalKeyIncludedDeleted(@Bind("externalKey") String paymentMethodExternalKey, @BindBean InternalTenantContext context);
+
+    @SqlQuery
     PaymentMethodModelDao getPaymentMethodIncludedDelete(@Bind("id") final String paymentMethodId,
                                                          @BindBean final InternalTenantContext context);
 
@@ -66,4 +72,5 @@ public interface PaymentMethodSqlDao extends EntitySqlDao<PaymentMethodModelDao,
     @SqlQuery
     public Long getCountByPluginName(@Bind("pluginName") final String pluginName,
                                      @BindBean final InternalTenantContext context);
+
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java
index 1a970b1..1fffb07 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentModelDao.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * Groupon 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:
  *
@@ -16,185 +16,91 @@
 
 package org.killbill.billing.payment.dao;
 
-import java.math.BigDecimal;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
-
-import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.payment.api.Payment;
-import org.killbill.billing.payment.api.PaymentStatus;
 import org.killbill.billing.util.dao.TableName;
-import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class PaymentModelDao extends EntityBase implements EntityModelDao<Payment> {
+import com.google.common.base.Objects;
 
-    public static final Integer INVALID_PAYMENT_NUMBER = new Integer(-13);
+public class PaymentModelDao extends EntityModelDaoBase implements EntityModelDao<Payment> {
+
+    public static final Integer INVALID_PAYMENT_NUMBER = new Integer(-17);
 
     private UUID accountId;
-    private UUID invoiceId;
-    private UUID paymentMethodId;
-    private BigDecimal amount;
-    private Currency currency;
-    private BigDecimal processedAmount;
-    private Currency processedCurrency;
-    private DateTime effectiveDate;
     private Integer paymentNumber;
-    private PaymentStatus paymentStatus;
-    private String extFirstPaymentRefId;
-    private String extSecondPaymentRefId;
+    private UUID paymentMethodId;
+    private String externalKey;
+    private String stateName;
+    private String lastSuccessStateName;
+
 
     public PaymentModelDao() { /* For the DAO mapper */ }
 
     public PaymentModelDao(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate, final UUID accountId,
-                           final UUID invoiceId, final UUID paymentMethodId,
-                           final Integer paymentNumber, final BigDecimal amount, final Currency currency, final BigDecimal processedAmount, final Currency processedCurrency,
-                           final PaymentStatus paymentStatus, final DateTime effectiveDate, final String extFirstPaymentRefId, final String extSecondPaymentRefId) {
+                           final UUID paymentMethodId, final Integer paymentNumber, @Nullable final String externalKey) {
         super(id, createdDate, updatedDate);
         this.accountId = accountId;
-        this.invoiceId = invoiceId;
         this.paymentMethodId = paymentMethodId;
         this.paymentNumber = paymentNumber;
-        this.amount = amount;
-        this.currency = currency;
-        this.processedAmount = processedAmount;
-        this.processedCurrency = processedCurrency;
-        this.paymentStatus = paymentStatus;
-        this.effectiveDate = effectiveDate;
-        this.extFirstPaymentRefId = extFirstPaymentRefId;
-        this.extSecondPaymentRefId = extSecondPaymentRefId;
+        this.externalKey = Objects.firstNonNull(externalKey, id.toString());
     }
 
-    public PaymentModelDao(final UUID accountId, final UUID invoiceId, final UUID paymentMethodId,
-                           final BigDecimal amount, final Currency currency, final DateTime effectiveDate, final PaymentStatus paymentStatus) {
-        this(UUID.randomUUID(), null, null, accountId, invoiceId, paymentMethodId, INVALID_PAYMENT_NUMBER, amount, currency, amount, currency, paymentStatus, effectiveDate, null, null);
+    public PaymentModelDao(@Nullable final DateTime createdDate, @Nullable final DateTime updatedDate, final UUID accountId,
+                           final UUID paymentMethodId, @Nullable final String externalKey) {
+        this(UUID.randomUUID(), createdDate, updatedDate, accountId, paymentMethodId, INVALID_PAYMENT_NUMBER, externalKey);
     }
 
-    public PaymentModelDao(final UUID accountId, final UUID invoiceId, final UUID paymentMethodId,
-                           final BigDecimal amount, final Currency currency, final DateTime effectiveDate) {
-        this(UUID.randomUUID(), null, null, accountId, invoiceId, paymentMethodId, INVALID_PAYMENT_NUMBER, amount, currency, amount, currency, PaymentStatus.UNKNOWN, effectiveDate, null, null);
-    }
+    public UUID getAccountId() { return accountId; }
 
-    public PaymentModelDao(final PaymentModelDao src, final PaymentStatus newPaymentStatus) {
-        this(src.getId(), src.getCreatedDate(), src.getUpdatedDate(), src.getAccountId(), src.getInvoiceId(), src.getPaymentMethodId(),
-             src.getPaymentNumber(), src.getAmount(), src.getCurrency(), src.getProcessedAmount(), src.getProcessedCurrency(), newPaymentStatus, src.getEffectiveDate(), null, null);
-    }
-
-    public UUID getAccountId() {
-        return accountId;
-    }
-
-    public UUID getInvoiceId() {
-        return invoiceId;
-    }
-
-    public UUID getPaymentMethodId() {
-        return paymentMethodId;
+    public void setAccountId(final UUID accountId) {
+        this.accountId = accountId;
     }
 
     public Integer getPaymentNumber() {
         return paymentNumber;
     }
 
-    public BigDecimal getAmount() {
-        return amount;
-    }
-
-    public Currency getCurrency() {
-        return currency;
-    }
-
-    public BigDecimal getProcessedAmount() {
-        return processedAmount;
-    }
-
-    public Currency getProcessedCurrency() {
-        return processedCurrency;
-    }
-
-    public PaymentStatus getPaymentStatus() {
-        return paymentStatus;
-    }
-
-    public DateTime getEffectiveDate() {
-        return effectiveDate;
-    }
-
-    public String getExtFirstPaymentRefId() {
-        return extFirstPaymentRefId;
-    }
-
-    public String getExtSecondPaymentRefId() {
-        return extSecondPaymentRefId;
-    }
-
-    public void setAccountId(final UUID accountId) {
-        this.accountId = accountId;
+    public void setPaymentNumber(final Integer paymentNumber) {
+        this.paymentNumber = paymentNumber;
     }
 
-    public void setInvoiceId(final UUID invoiceId) {
-        this.invoiceId = invoiceId;
+    public UUID getPaymentMethodId() {
+        return paymentMethodId;
     }
 
     public void setPaymentMethodId(final UUID paymentMethodId) {
         this.paymentMethodId = paymentMethodId;
     }
 
-    public void setAmount(final BigDecimal amount) {
-        this.amount = amount;
+    public String getExternalKey() {
+        return externalKey;
     }
 
-    public void setCurrency(final Currency currency) {
-        this.currency = currency;
+    public void setExternalKey(final String externalKey) {
+        this.externalKey = externalKey;
     }
 
-    public void setProcessedAmount(final BigDecimal processedAmount) {
-        this.processedAmount = processedAmount;
+    public String getStateName() {
+        return stateName;
     }
 
-    public void setProcessedCurrency(final Currency processedCurrency) {
-        this.processedCurrency = processedCurrency;
+    public void setStateName(final String stateName) {
+        this.stateName = stateName;
     }
 
-    public void setEffectiveDate(final DateTime effectiveDate) {
-        this.effectiveDate = effectiveDate;
+    public String getLastSuccessStateName() {
+        return lastSuccessStateName;
     }
 
-    public void setPaymentNumber(final Integer paymentNumber) {
-        this.paymentNumber = paymentNumber;
-    }
-
-    public void setPaymentStatus(final PaymentStatus paymentStatus) {
-        this.paymentStatus = paymentStatus;
-    }
-
-    public void setExtFirstPaymentRefId(final String extFirstPaymentRefId) {
-        this.extFirstPaymentRefId = extFirstPaymentRefId;
-    }
-
-    public void setExtSecondPaymentRefId(final String extSecondPaymentRefId) {
-        this.extSecondPaymentRefId = extSecondPaymentRefId;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("PaymentModelDao");
-        sb.append("{accountId=").append(accountId);
-        sb.append(", invoiceId=").append(invoiceId);
-        sb.append(", paymentMethodId=").append(paymentMethodId);
-        sb.append(", amount=").append(amount);
-        sb.append(", currency=").append(currency);
-        sb.append(", effectiveDate=").append(effectiveDate);
-        sb.append(", paymentNumber=").append(paymentNumber);
-        sb.append(", paymentStatus=").append(paymentStatus);
-        sb.append(", extFirstPaymentRefId='").append(extFirstPaymentRefId).append('\'');
-        sb.append(", extSecondPaymentRefId='").append(extSecondPaymentRefId).append('\'');
-        sb.append('}');
-        return sb.toString();
+    public void setLastSuccessStateName(final String lastSuccessStateName) {
+        this.lastSuccessStateName = lastSuccessStateName;
     }
 
     @Override
@@ -214,28 +120,13 @@ public class PaymentModelDao extends EntityBase implements EntityModelDao<Paymen
         if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
         }
-        if (amount != null ? !amount.equals(that.amount) : that.amount != null) {
-            return false;
-        }
-        if (currency != that.currency) {
-            return false;
-        }
-        if (processedAmount != null ? !processedAmount.equals(that.processedAmount) : that.processedAmount != null) {
-            return false;
-        }
-        if (processedCurrency != that.processedCurrency) {
-            return false;
-        }
-        if (effectiveDate != null ? !effectiveDate.equals(that.effectiveDate) : that.effectiveDate != null) {
-            return false;
-        }
-        if (extFirstPaymentRefId != null ? !extFirstPaymentRefId.equals(that.extFirstPaymentRefId) : that.extFirstPaymentRefId != null) {
+        if (stateName != null ? !stateName.equals(that.stateName) : that.stateName != null) {
             return false;
         }
-        if (extSecondPaymentRefId != null ? !extSecondPaymentRefId.equals(that.extSecondPaymentRefId) : that.extSecondPaymentRefId != null) {
+        if (lastSuccessStateName != null ? !lastSuccessStateName.equals(that.lastSuccessStateName) : that.lastSuccessStateName != null) {
             return false;
         }
-        if (invoiceId != null ? !invoiceId.equals(that.invoiceId) : that.invoiceId != null) {
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
             return false;
         }
         if (paymentMethodId != null ? !paymentMethodId.equals(that.paymentMethodId) : that.paymentMethodId != null) {
@@ -244,9 +135,6 @@ public class PaymentModelDao extends EntityBase implements EntityModelDao<Paymen
         if (paymentNumber != null ? !paymentNumber.equals(that.paymentNumber) : that.paymentNumber != null) {
             return false;
         }
-        if (paymentStatus != that.paymentStatus) {
-            return false;
-        }
         return true;
     }
 
@@ -254,17 +142,11 @@ public class PaymentModelDao extends EntityBase implements EntityModelDao<Paymen
     public int hashCode() {
         int result = super.hashCode();
         result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
-        result = 31 * result + (invoiceId != null ? invoiceId.hashCode() : 0);
-        result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
-        result = 31 * result + (amount != null ? amount.hashCode() : 0);
-        result = 31 * result + (currency != null ? currency.hashCode() : 0);
-        result = 31 * result + (processedAmount != null ? processedAmount.hashCode() : 0);
-        result = 31 * result + (processedCurrency != null ? processedCurrency.hashCode() : 0);
-        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
         result = 31 * result + (paymentNumber != null ? paymentNumber.hashCode() : 0);
-        result = 31 * result + (paymentStatus != null ? paymentStatus.hashCode() : 0);
-        result = 31 * result + (extFirstPaymentRefId != null ? extFirstPaymentRefId.hashCode() : 0);
-        result = 31 * result + (extSecondPaymentRefId != null ? extSecondPaymentRefId.hashCode() : 0);
+        result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
+        result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
+        result = 31 * result + (stateName != null ? stateName.hashCode() : 0);
+        result = 31 * result + (lastSuccessStateName != null ? lastSuccessStateName.hashCode() : 0);
         return result;
     }
 
@@ -277,5 +159,4 @@ public class PaymentModelDao extends EntityBase implements EntityModelDao<Paymen
     public TableName getHistoryTableName() {
         return TableName.PAYMENT_HISTORY;
     }
-
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
index 3783c86..432d130 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentSqlDao.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * Groupon 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:
  *
@@ -16,67 +16,54 @@
 
 package org.killbill.billing.payment.dao;
 
-import java.math.BigDecimal;
-import java.util.Date;
 import java.util.Iterator;
-import java.util.List;
-
-import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
-import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.billing.catalog.api.Currency;
-import org.killbill.commons.jdbi.statement.SmartFetchSize;
 import org.killbill.billing.payment.api.Payment;
 import org.killbill.billing.util.audit.ChangeType;
 import org.killbill.billing.util.entity.dao.Audited;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoStringTemplate;
+import org.killbill.commons.jdbi.statement.SmartFetchSize;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 
 @EntitySqlDaoStringTemplate
 public interface PaymentSqlDao extends EntitySqlDao<PaymentModelDao, Payment> {
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
-    void updatePaymentStatus(@Bind("id") final String paymentId,
-                             @Bind("processedAmount") final BigDecimal processedAmount,
-                             @Bind("processedCurrency") final Currency processedCurrency,
-                             @Bind("paymentStatus") final String paymentStatus,
-                             @BindBean final InternalCallContext context);
+    void updatePaymentForNewTransaction(@Bind("id") final String paymentId,
+                                        @BindBean final InternalCallContext context);
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
-    void updatePaymentForNewAttempt(@Bind("id") final String paymentId,
-                                    @Bind("paymentMethodId") final String paymentMethodId,
-                                    @Bind("amount") final BigDecimal amount,
-                                    @Bind("effectiveDate") final Date effectiveDate,
-                                    @BindBean final InternalCallContext context);
+    void updatePaymentStateName(@Bind("id") final String paymentId,
+                                @Bind("stateName") final String stateName,
+                                @BindBean final InternalCallContext context);
 
-    @SqlQuery
-    PaymentModelDao getLastPaymentForAccountAndPaymentMethod(@Bind("accountId") final String accountId,
-                                                             @Bind("paymentMethodId") final String paymentMethodId,
-                                                             @BindBean final InternalTenantContext context);
 
-    @SqlQuery
-    List<PaymentModelDao> getPaymentsForInvoice(@Bind("invoiceId") final String invoiceId,
-                                                @BindBean final InternalTenantContext context);
+    @SqlUpdate
+    @Audited(ChangeType.UPDATE)
+    void updateLastSuccessPaymentStateName(@Bind("id") final String paymentId,
+                                @Bind("stateName") final String stateName,
+                                @Bind("lastSuccessStateName") final String lastSuccessStateName,
+                                @BindBean final InternalCallContext context);
 
     @SqlQuery
-    List<PaymentModelDao> getPaymentsForAccount(@Bind("accountId") final String accountId,
-                                                @BindBean final InternalTenantContext context);
-
+    public PaymentModelDao getPaymentByExternalKey(@Bind("externalKey") final String externalKey,
+                                                   @BindBean final InternalTenantContext context);
     @SqlQuery
     @SmartFetchSize(shouldStream = true)
     public Iterator<PaymentModelDao> getByPluginName(@Bind("pluginName") final String pluginName,
-                                                     @Bind("offset") final Long offset,
-                                                     @Bind("rowCount") final Long rowCount,
-                                                     @BindBean final InternalTenantContext context);
+                                                           @Bind("offset") final Long offset,
+                                                           @Bind("rowCount") final Long rowCount,
+                                                           @BindBean final InternalTenantContext context);
 
     @SqlQuery
     public Long getCountByPluginName(@Bind("pluginName") final String pluginName,
                                      @BindBean final InternalTenantContext context);
 }
-
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentTransactionModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentTransactionModelDao.java
new file mode 100644
index 0000000..1f88513
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentTransactionModelDao.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.dao;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.entity.EntityBase;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.util.dao.TableName;
+import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
+
+import com.google.common.base.Objects;
+
+public class PaymentTransactionModelDao extends EntityModelDaoBase implements EntityModelDao<PaymentTransaction> {
+
+    private UUID attemptId;
+    private UUID paymentId;
+    private String transactionExternalKey;
+    private TransactionType transactionType;
+    private DateTime effectiveDate;
+    private TransactionStatus transactionStatus;
+    private BigDecimal amount;
+    private Currency currency;
+    private BigDecimal processedAmount;
+    private Currency processedCurrency;
+    private String gatewayErrorCode;
+    private String gatewayErrorMsg;
+
+
+    public PaymentTransactionModelDao() { /* For the DAO mapper */ }
+
+    public PaymentTransactionModelDao(final UUID id, @Nullable final UUID attemptId, @Nullable final String transactionExternalKey, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
+                                      final UUID paymentId, final TransactionType transactionType, final DateTime effectiveDate,
+                                      final TransactionStatus paymentStatus, final BigDecimal amount, final Currency currency, final String gatewayErrorCode, final String gatewayErrorMsg) {
+        super(id, createdDate, updatedDate);
+        this.attemptId = attemptId;
+        this.transactionExternalKey = Objects.firstNonNull(transactionExternalKey, id.toString());
+        this.paymentId = paymentId;
+        this.transactionType = transactionType;
+        this.effectiveDate = effectiveDate;
+        this.transactionStatus = paymentStatus;
+        this.amount = amount;
+        this.currency = currency;
+        this.processedAmount = null;
+        this.processedCurrency = null;
+        this.gatewayErrorCode = gatewayErrorCode;
+        this.gatewayErrorMsg = gatewayErrorMsg;
+    }
+
+    public PaymentTransactionModelDao(@Nullable final DateTime createdDate, @Nullable final DateTime updatedDate, @Nullable final UUID attemptId,
+                                      @Nullable final String transactionExternalKey, final UUID paymentId, final TransactionType transactionType, final DateTime effectiveDate,
+                                      final TransactionStatus paymentStatus, final BigDecimal amount, final Currency currency, final String gatewayErrorCode, final String gatewayErrorMsg) {
+        this(UUID.randomUUID(), attemptId, transactionExternalKey, createdDate, updatedDate, paymentId, transactionType, effectiveDate, paymentStatus, amount, currency, gatewayErrorCode, gatewayErrorMsg);
+    }
+
+    public UUID getPaymentId() {
+        return paymentId;
+    }
+
+    public UUID getAttemptId() {
+        return attemptId;
+    }
+
+    public void setAttemptId(final UUID attemptId) {
+        this.attemptId = attemptId;
+    }
+
+    public String getTransactionExternalKey() {
+        return transactionExternalKey;
+    }
+
+    public TransactionType getTransactionType() {
+        return transactionType;
+    }
+
+    public DateTime getEffectiveDate() {
+        return effectiveDate;
+    }
+
+    public TransactionStatus getTransactionStatus() {
+        return transactionStatus;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    public BigDecimal getProcessedAmount() {
+        return processedAmount;
+    }
+
+    public Currency getProcessedCurrency() {
+        return processedCurrency;
+    }
+
+    public String getGatewayErrorCode() {
+        return gatewayErrorCode;
+    }
+
+    public String getGatewayErrorMsg() {
+        return gatewayErrorMsg;
+    }
+
+    public void setPaymentId(final UUID paymentId) {
+        this.paymentId = paymentId;
+    }
+
+    public void setTransactionExternalKey(final String transactionExternalKey) {
+        this.transactionExternalKey = transactionExternalKey;
+    }
+
+    public void setTransactionType(final TransactionType transactionType) {
+        this.transactionType = transactionType;
+    }
+
+    public void setEffectiveDate(final DateTime effectiveDate) {
+        this.effectiveDate = effectiveDate;
+    }
+
+    public void setTransactionStatus(final TransactionStatus transactionStatus) {
+        this.transactionStatus = transactionStatus;
+    }
+
+    public void setAmount(final BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public void setCurrency(final Currency currency) {
+        this.currency = currency;
+    }
+
+    public void setProcessedAmount(final BigDecimal processedAmount) {
+        this.processedAmount = processedAmount;
+    }
+
+    public void setProcessedCurrency(final Currency processedCurrency) {
+        this.processedCurrency = processedCurrency;
+    }
+
+    public void setGatewayErrorCode(final String gatewayErrorCode) {
+        this.gatewayErrorCode = gatewayErrorCode;
+    }
+
+    public void setGatewayErrorMsg(final String gatewayErrorMsg) {
+        this.gatewayErrorMsg = gatewayErrorMsg;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        final PaymentTransactionModelDao that = (PaymentTransactionModelDao) o;
+
+        if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (attemptId != null ? !attemptId.equals(that.attemptId) : that.attemptId != null) {
+            return false;
+        }
+        if (paymentId != null ? !paymentId.equals(that.paymentId) : that.paymentId != null) {
+            return false;
+        }
+        if (effectiveDate != null ? effectiveDate.compareTo(that.effectiveDate) != 0 : that.effectiveDate != null) {
+            return false;
+        }
+        if (transactionExternalKey != null ? !transactionExternalKey.equals(that.transactionExternalKey) : that.transactionExternalKey != null) {
+            return false;
+        }
+        if (gatewayErrorCode != null ? !gatewayErrorCode.equals(that.gatewayErrorCode) : that.gatewayErrorCode != null) {
+            return false;
+        }
+        if (gatewayErrorMsg != null ? !gatewayErrorMsg.equals(that.gatewayErrorMsg) : that.gatewayErrorMsg != null) {
+            return false;
+        }
+        if (transactionStatus != that.transactionStatus) {
+            return false;
+        }
+        if (processedAmount != null ? processedAmount.compareTo(that.processedAmount) != 0 : that.processedAmount != null) {
+            return false;
+        }
+        if (processedCurrency != that.processedCurrency) {
+            return false;
+        }
+        if (transactionType != that.transactionType) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (paymentId != null ? paymentId.hashCode() : 0);
+        result = 31 * result + (attemptId != null ? attemptId.hashCode() : 0);
+        result = 31 * result + (transactionExternalKey != null ? transactionExternalKey.hashCode() : 0);
+        result = 31 * result + (transactionType != null ? transactionType.hashCode() : 0);
+        result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+        result = 31 * result + (transactionStatus != null ? transactionStatus.hashCode() : 0);
+        result = 31 * result + (amount != null ? amount.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (processedAmount != null ? processedAmount.hashCode() : 0);
+        result = 31 * result + (processedCurrency != null ? processedCurrency.hashCode() : 0);
+        result = 31 * result + (gatewayErrorCode != null ? gatewayErrorCode.hashCode() : 0);
+        result = 31 * result + (gatewayErrorMsg != null ? gatewayErrorMsg.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public TableName getTableName() {
+        return TableName.TRANSACTIONS;
+    }
+
+    @Override
+    public TableName getHistoryTableName() {
+        return TableName.TRANSACTION_HISTORY;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySerializer.java b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySerializer.java
new file mode 100644
index 0000000..6ddd3bc
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySerializer.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.dao;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.killbill.billing.payment.api.PluginProperty;
+
+import com.ning.compress.lzf.LZFDecoder;
+import com.ning.compress.lzf.LZFEncoder;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class PluginPropertySerializer {
+
+    private static final int MAX_SIZE_PROPERTIES_BYTES = (8 * 1024); // As defined in payment_attempt ddl
+
+    private static final JsonFactory jsonFactory = new JsonFactory();
+    private static ObjectMapper mapper = new ObjectMapper(jsonFactory);
+
+    static {
+        mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
+    }
+
+    public static byte[] serialize(final Iterable<PluginProperty> input) throws PluginPropertySerializerException {
+
+        final ByteArrayOutputStream out = new ByteArrayOutputStream(MAX_SIZE_PROPERTIES_BYTES);
+        try {
+            final JsonGenerator jsonGenerator = jsonFactory.createGenerator(out, JsonEncoding.UTF8);
+            jsonGenerator.writeStartArray();
+            for (final PluginProperty cur : input) {
+                final String key = cur.getKey();
+                final Object value = cur.getValue();
+                jsonGenerator.writeStartObject();
+                jsonGenerator.writeFieldName(key);
+                mapper.writeValue(jsonGenerator, value);
+                jsonGenerator.writeEndObject();
+            }
+            jsonGenerator.writeEndArray();
+            jsonGenerator.close();
+            final byte[] data = out.toByteArray();
+            return LZFEncoder.encode(data);
+        } catch (final IOException e) {
+            throw new PluginPropertySerializerException(e);
+        }
+    }
+
+    public static Iterable<PluginProperty> deserialize(final byte[] input) throws PluginPropertySerializerException {
+
+        final List<PluginProperty> result = new ArrayList<PluginProperty>();
+        try {
+            final byte[] uncompressed = LZFDecoder.decode(input);
+            final InputStream in = new ByteArrayInputStream(uncompressed);
+            final JsonParser jsonParser = jsonFactory.createParser(in);
+
+            PluginProperty prop = null;
+            String key = null;
+            JsonToken nextToken = jsonParser.nextToken();
+            while (nextToken != null && nextToken != JsonToken.END_ARRAY) {
+                if (nextToken != JsonToken.START_ARRAY) {
+                    if (nextToken == JsonToken.FIELD_NAME && key == null) {
+                        key = jsonParser.getText();
+                    } else if (key != null) {
+                        final Object value = mapper.readValue(jsonParser, Object.class);
+                        prop = new PluginProperty(key, value, false);
+                        key = null;
+                    } else if (nextToken == JsonToken.END_OBJECT) {
+                        result.add(prop);
+                        prop = null;
+                    }
+                }
+                nextToken = jsonParser.nextToken();
+            }
+            jsonParser.close();
+            return result;
+        } catch (final UnsupportedEncodingException e) {
+            throw new PluginPropertySerializerException(e);
+        } catch (final JsonParseException e) {
+            throw new PluginPropertySerializerException(e);
+        } catch (final IOException e) {
+            throw new PluginPropertySerializerException(e);
+        }
+    }
+
+    public static class PluginPropertySerializerException extends Exception {
+
+        public PluginPropertySerializerException() {
+        }
+
+        public PluginPropertySerializerException(final String message) {
+            super(message);
+        }
+
+        public PluginPropertySerializerException(final Throwable cause) {
+            super(cause);
+        }
+
+        public PluginPropertySerializerException(final String message, final Throwable cause) {
+            super(message, cause);
+        }
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
index 6bb30d0..a60eff6 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dispatcher/PluginDispatcher.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -13,6 +15,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package org.killbill.billing.payment.dispatcher;
 
 import java.util.concurrent.Callable;
@@ -22,49 +25,72 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.killbill.billing.ErrorCode;
-import org.killbill.billing.payment.api.PaymentApiException;
-
-public class PluginDispatcher<T> {
+import org.killbill.commons.profiling.Profiling;
+import org.killbill.commons.profiling.ProfilingData;
 
-    private static final Logger log = LoggerFactory.getLogger(PluginDispatcher.class);
+public class PluginDispatcher<ReturnType> {
 
     private final TimeUnit DEEFAULT_PLUGIN_TIMEOUT_UNIT = TimeUnit.SECONDS;
 
     private final long timeoutSeconds;
     private final ExecutorService executor;
 
-    public PluginDispatcher(final long tiemoutSeconds, final ExecutorService executor) {
-        this.timeoutSeconds = tiemoutSeconds;
+    public PluginDispatcher(final long timeoutSeconds, final ExecutorService executor) {
+        this.timeoutSeconds = timeoutSeconds;
         this.executor = executor;
     }
 
-
-    public T dispatchWithAccountLock(final Callable<T> task)
-            throws PaymentApiException, TimeoutException {
-        return dispatchWithAccountLockAndTimeout(task, timeoutSeconds, DEEFAULT_PLUGIN_TIMEOUT_UNIT);
+    // TODO Once we switch fully to automata, should this throw PaymentPluginApiException instead?
+    public ReturnType dispatchWithTimeout(final Callable<PluginDispatcherReturnType<ReturnType>> task) throws TimeoutException, ExecutionException, InterruptedException {
+        return dispatchWithTimeout(task, timeoutSeconds, DEEFAULT_PLUGIN_TIMEOUT_UNIT);
     }
 
-    public T dispatchWithAccountLockAndTimeout(final Callable<T> task, final long timeout, final TimeUnit unit)
-            throws PaymentApiException, TimeoutException {
-
-        try {
-            final Future<T> future = executor.submit(task);
-            return future.get(timeout, unit);
-        } catch (ExecutionException e) {
-            if (e.getCause() instanceof PaymentApiException) {
-                throw (PaymentApiException) e.getCause();
-            } else {
-                throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, e.getMessage());
+    public ReturnType dispatchWithTimeout(final Callable<PluginDispatcherReturnType<ReturnType>> task, final long timeout, final TimeUnit unit)
+            throws TimeoutException, ExecutionException, InterruptedException {
+
+        final Future<PluginDispatcherReturnType<ReturnType>> future = executor.submit(task);
+        final PluginDispatcherReturnType<ReturnType> pluginDispatcherResult = future.get(timeout, unit);
+
+        if (pluginDispatcherResult instanceof WithProfilingPluginDispatcherReturnType) {
+            // Transfer state from dispatch thread into current one.
+            final ProfilingData currentThreadProfilingData = Profiling.getPerThreadProfilingData();
+            if (currentThreadProfilingData != null) {
+                currentThreadProfilingData.merge(((WithProfilingPluginDispatcherReturnType)pluginDispatcherResult).getProfilingData());
             }
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, e.getMessage());
         }
+        return pluginDispatcherResult.getReturnType();
+    }
+
+    public interface PluginDispatcherReturnType<ReturnType> {
+        public ReturnType getReturnType();
+    }
+
+    public interface WithProfilingPluginDispatcherReturnType<ReturnType> extends PluginDispatcherReturnType<ReturnType> {
+        public ProfilingData getProfilingData();
     }
 
+    public static class DefaultWithProfilingPluginDispatcherReturnType<ReturnType> implements WithProfilingPluginDispatcherReturnType<ReturnType> {
+        private final ReturnType returnType;
+        private final ProfilingData profilingData;
+
+        public DefaultWithProfilingPluginDispatcherReturnType(final ReturnType returnType, final ProfilingData profilingData) {
+            this.returnType = returnType;
+            this.profilingData = profilingData;
+        }
+
+        @Override
+        public ReturnType getReturnType() {
+            return returnType;
+        }
+
+        @Override
+        public ProfilingData getProfilingData() {
+            return profilingData;
+        }
+    }
+
+    public static <ReturnType> PluginDispatcherReturnType<ReturnType> createPluginDispatcherReturnType(final ReturnType returnType) {
+        return new DefaultWithProfilingPluginDispatcherReturnType(returnType, Profiling.getPerThreadProfilingData());
+    }
 
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentControlProviderPluginRegistryProvider.java b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentControlProviderPluginRegistryProvider.java
new file mode 100644
index 0000000..4362f08
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentControlProviderPluginRegistryProvider.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.glue;
+
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
+import org.killbill.billing.payment.provider.DefaultPaymentControlProviderPlugin;
+import org.killbill.billing.payment.provider.DefaultPaymentControlProviderPluginRegistry;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class DefaultPaymentControlProviderPluginRegistryProvider implements Provider<OSGIServiceRegistration<PaymentControlPluginApi>> {
+
+    private final PaymentConfig paymentConfig;
+    private final DefaultPaymentControlProviderPlugin externalPaymentControlProviderPlugin;
+    private final InvoicePaymentControlPluginApi invoicePaymentControlPlugin;
+
+    @Inject
+    public DefaultPaymentControlProviderPluginRegistryProvider(final PaymentConfig paymentConfig,
+                                                               final DefaultPaymentControlProviderPlugin externalPaymentControlProviderPlugin,
+                                                              final InvoicePaymentControlPluginApi invoicePaymentControlPlugin) {
+        this.paymentConfig = paymentConfig;
+        this.externalPaymentControlProviderPlugin = externalPaymentControlProviderPlugin;
+        this.invoicePaymentControlPlugin = invoicePaymentControlPlugin;
+    }
+
+    @Override
+    public OSGIServiceRegistration<PaymentControlPluginApi> get() {
+        final DefaultPaymentControlProviderPluginRegistry pluginRegistry = new DefaultPaymentControlProviderPluginRegistry(paymentConfig);
+
+        // Make the external payment provider plugin available by default
+        final OSGIServiceDescriptor desc = new OSGIServiceDescriptor() {
+            @Override
+            public String getPluginSymbolicName() {
+                return null;
+            }
+
+            @Override
+            public String getRegistrationName() {
+                return DefaultPaymentControlProviderPlugin.PLUGIN_NAME;
+            }
+        };
+        pluginRegistry.registerService(desc, externalPaymentControlProviderPlugin);
+
+        // Hack, because this is not a real plugin, so it can't register itself during lifecycle as it should.
+        final OSGIServiceDescriptor desc2 = new OSGIServiceDescriptor() {
+            @Override
+            public String getPluginSymbolicName() {
+                return null;
+            }
+
+            @Override
+            public String getRegistrationName() {
+                return InvoicePaymentControlPluginApi.PLUGIN_NAME;
+            }
+        };
+        pluginRegistry.registerService(desc2, invoicePaymentControlPlugin);
+
+        return pluginRegistry;
+    }
+
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
index 4ef9a43..2a8e0aa 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/DefaultPaymentService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,21 +18,19 @@
 
 package org.killbill.billing.payment.glue;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.billing.lifecycle.LifecycleHandlerType;
-import org.killbill.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
-import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
-import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentService;
 import org.killbill.billing.payment.bus.InvoiceHandler;
-import org.killbill.billing.payment.bus.PaymentTagHandler;
-import org.killbill.billing.payment.retry.AutoPayRetryService;
-import org.killbill.billing.payment.retry.FailedPaymentRetryService;
-import org.killbill.billing.payment.retry.PluginFailureRetryService;
+import org.killbill.billing.payment.control.PaymentTagHandler;
+import org.killbill.billing.payment.core.Janitor;
+import org.killbill.billing.payment.retry.DefaultRetryService;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 
@@ -44,24 +44,22 @@ public class DefaultPaymentService implements PaymentService {
     private final PaymentTagHandler tagHandler;
     private final PersistentBus eventBus;
     private final PaymentApi api;
-    private final FailedPaymentRetryService failedRetryService;
-    private final PluginFailureRetryService timedoutRetryService;
-    private final AutoPayRetryService autoPayoffRetryService;
+    private final DefaultRetryService retryService;
+    private final Janitor janitor;
 
     @Inject
     public DefaultPaymentService(final InvoiceHandler invoiceHandler,
                                  final PaymentTagHandler tagHandler,
-                                 final PaymentApi api, final PersistentBus eventBus,
-                                 final FailedPaymentRetryService failedRetryService,
-                                 final PluginFailureRetryService timedoutRetryService,
-                                 final AutoPayRetryService autoPayoffRetryService) {
+                                 final PaymentApi api,
+                                 final DefaultRetryService retryService,
+                                 final PersistentBus eventBus,
+                                 final Janitor janitor) {
         this.invoiceHandler = invoiceHandler;
         this.tagHandler = tagHandler;
         this.eventBus = eventBus;
         this.api = api;
-        this.failedRetryService = failedRetryService;
-        this.timedoutRetryService = timedoutRetryService;
-        this.autoPayoffRetryService = autoPayoffRetryService;
+        this.retryService = retryService;
+        this.janitor = janitor;
     }
 
     @Override
@@ -74,19 +72,16 @@ public class DefaultPaymentService implements PaymentService {
         try {
             eventBus.register(invoiceHandler);
             eventBus.register(tagHandler);
-        } catch (PersistentBus.EventBusException e) {
+        } catch (final PersistentBus.EventBusException e) {
             log.error("Unable to register with the EventBus!", e);
         }
-        failedRetryService.initialize(SERVICE_NAME);
-        timedoutRetryService.initialize(SERVICE_NAME);
-        autoPayoffRetryService.initialize(SERVICE_NAME);
+        retryService.initialize(SERVICE_NAME);
     }
 
     @LifecycleHandlerType(LifecycleLevel.START_SERVICE)
     public void start() {
-        failedRetryService.start();
-        timedoutRetryService.start();
-        autoPayoffRetryService.start();
+        retryService.start();
+        janitor.start();
     }
 
     @LifecycleHandlerType(LifecycleLevel.STOP_SERVICE)
@@ -94,12 +89,11 @@ public class DefaultPaymentService implements PaymentService {
         try {
             eventBus.unregister(invoiceHandler);
             eventBus.unregister(tagHandler);
-        } catch (PersistentBus.EventBusException e) {
+        } catch (final PersistentBus.EventBusException e) {
             throw new RuntimeException("Unable to unregister to the EventBus!", e);
         }
-        failedRetryService.stop();
-        timedoutRetryService.stop();
-        autoPayoffRetryService.stop();
+        retryService.stop();
+        janitor.stop();
     }
 
     @Override
diff --git a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
index a221bbd..f5a8722 100644
--- a/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/org/killbill/billing/payment/glue/PaymentModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,98 +19,165 @@
 package org.killbill.billing.payment.glue;
 
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
+import javax.inject.Provider;
 
+import org.killbill.automaton.DefaultStateMachineConfig;
+import org.killbill.automaton.StateMachineConfig;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.DefaultPaymentApi;
+import org.killbill.billing.payment.api.DefaultPaymentGatewayApi;
 import org.killbill.billing.payment.api.PaymentApi;
-import org.killbill.billing.payment.api.PaymentInternalApi;
+import org.killbill.billing.payment.api.PaymentGatewayApi;
 import org.killbill.billing.payment.api.PaymentService;
-import org.killbill.billing.payment.api.svcs.DefaultPaymentInternalApi;
 import org.killbill.billing.payment.bus.InvoiceHandler;
-import org.killbill.billing.payment.bus.PaymentTagHandler;
+import org.killbill.billing.payment.control.PaymentTagHandler;
+import org.killbill.billing.payment.control.dao.InvoicePaymentControlDao;
+import org.killbill.billing.payment.core.Janitor;
+import org.killbill.billing.payment.core.PaymentGatewayProcessor;
 import org.killbill.billing.payment.core.PaymentMethodProcessor;
 import org.killbill.billing.payment.core.PaymentProcessor;
-import org.killbill.billing.payment.core.RefundProcessor;
+import org.killbill.billing.payment.core.PluginControlledPaymentProcessor;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.RetryStateMachineHelper;
 import org.killbill.billing.payment.dao.DefaultPaymentDao;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
-import org.killbill.billing.payment.retry.AutoPayRetryService;
-import org.killbill.billing.payment.retry.AutoPayRetryService.AutoPayRetryServiceScheduler;
-import org.killbill.billing.payment.retry.FailedPaymentRetryService;
-import org.killbill.billing.payment.retry.FailedPaymentRetryService.FailedPaymentRetryServiceScheduler;
-import org.killbill.billing.payment.retry.PluginFailureRetryService;
-import org.killbill.billing.payment.retry.PluginFailureRetryService.PluginFailureRetryServiceScheduler;
+import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
+import org.killbill.billing.payment.retry.DefaultRetryService;
+import org.killbill.billing.payment.retry.DefaultRetryService.DefaultRetryServiceScheduler;
+import org.killbill.billing.payment.retry.RetryService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
 import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.killbill.commons.concurrent.WithProfilingThreadPoolExecutor;
+import org.killbill.xmlloader.XMLLoader;
+import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.AbstractModule;
+import com.google.common.io.Resources;
+import com.google.inject.Key;
 import com.google.inject.TypeLiteral;
 import com.google.inject.name.Names;
 
-public class PaymentModule extends AbstractModule {
+public class PaymentModule extends KillBillModule {
 
     private static final String PLUGIN_THREAD_PREFIX = "Plugin-th-";
 
+    public static final String JANITOR_EXECUTOR_NAMED = "JanitorExecutor";
     public static final String PLUGIN_EXECUTOR_NAMED = "PluginExecutor";
+    public static final String RETRYABLE_NAMED = "Retryable";
 
-    protected ConfigSource configSource;
+    public static final String STATE_MACHINE_RETRY = "RetryStateMachine";
+    public static final String STATE_MACHINE_PAYMENT = "PaymentStateMachine";
 
-    public PaymentModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public PaymentModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     protected void installPaymentDao() {
         bind(PaymentDao.class).to(DefaultPaymentDao.class).asEagerSingleton();
+        // Payment Control Plugin Dao
+        bind(InvoicePaymentControlDao.class).asEagerSingleton();
     }
 
     protected void installPaymentProviderPlugins(final PaymentConfig config) {
     }
 
+    protected void installJanitor() {
+        final ScheduledExecutorService janitorExecutor = org.killbill.commons.concurrent.Executors.newSingleThreadScheduledExecutor("PaymentJanitor");
+        bind(ScheduledExecutorService.class).annotatedWith(Names.named(JANITOR_EXECUTOR_NAMED)).toInstance(janitorExecutor);
+
+        bind(Janitor.class).asEagerSingleton();
+    }
+
     protected void installRetryEngines() {
-        bind(FailedPaymentRetryService.class).asEagerSingleton();
-        bind(PluginFailureRetryService.class).asEagerSingleton();
-        bind(AutoPayRetryService.class).asEagerSingleton();
-        bind(FailedPaymentRetryServiceScheduler.class).asEagerSingleton();
-        bind(PluginFailureRetryServiceScheduler.class).asEagerSingleton();
-        bind(AutoPayRetryServiceScheduler.class).asEagerSingleton();
+        bind(DefaultRetryService.class).asEagerSingleton();
+        bind(RetryService.class).annotatedWith(Names.named(RETRYABLE_NAMED)).to(DefaultRetryService.class);
+
+        bind(DefaultRetryServiceScheduler.class).asEagerSingleton();
+        bind(RetryServiceScheduler.class).annotatedWith(Names.named(RETRYABLE_NAMED)).to(DefaultRetryServiceScheduler.class);
     }
 
-    protected void installProcessors(final PaymentConfig paymentConfig) {
-        final ExecutorService pluginExecutorService = Executors.newFixedThreadPool(paymentConfig.getPaymentPluginThreadNb(), new ThreadFactory() {
+    protected void installStateMachines() {
+
+        bind(StateMachineProvider.class).annotatedWith(Names.named(STATE_MACHINE_RETRY)).toInstance(new StateMachineProvider("org/killbill/billing/payment/retry/RetryStates.xml"));
+        bind(StateMachineConfig.class).annotatedWith(Names.named(STATE_MACHINE_RETRY)).toProvider(Key.get(StateMachineProvider.class, Names.named(STATE_MACHINE_RETRY)));
+        bind(RetryStateMachineHelper.class).asEagerSingleton();
+
+        bind(StateMachineProvider.class).annotatedWith(Names.named(STATE_MACHINE_PAYMENT)).toInstance(new StateMachineProvider("org/killbill/billing/payment/PaymentStates.xml"));
+        bind(StateMachineConfig.class).annotatedWith(Names.named(STATE_MACHINE_PAYMENT)).toProvider(Key.get(StateMachineProvider.class, Names.named(STATE_MACHINE_PAYMENT)));
+        bind(PaymentStateMachineHelper.class).asEagerSingleton();
+    }
 
-            @Override
-            public Thread newThread(final Runnable r) {
-                final Thread th = new Thread(r);
-                th.setName(PLUGIN_THREAD_PREFIX + th.getId());
-                return th;
+    public static final class StateMachineProvider implements Provider<StateMachineConfig> {
+
+        private final String stateMachineConfig;
+
+        public StateMachineProvider(final String stateMachineConfig) {
+            this.stateMachineConfig = stateMachineConfig;
+        }
+
+        @Override
+        public StateMachineConfig get() {
+            try {
+                return XMLLoader.getObjectFromString(Resources.getResource(stateMachineConfig).toExternalForm(), DefaultStateMachineConfig.class);
+            } catch (final Exception e) {
+                throw new IllegalStateException(e);
             }
-        });
+        }
+    }
+
+    protected void installAutomatonRunner() {
+        bind(PluginControlledPaymentAutomatonRunner.class).asEagerSingleton();
+    }
+
+    protected void installProcessors(final PaymentConfig paymentConfig) {
+
+        final ExecutorService pluginExecutorService = new WithProfilingThreadPoolExecutor(paymentConfig.getPaymentPluginThreadNb(), paymentConfig.getPaymentPluginThreadNb(),
+                                                                                          0L, TimeUnit.MILLISECONDS,
+                                                                                          new LinkedBlockingQueue<Runnable>(),
+                                                                                          new ThreadFactory() {
+
+                                                                                              @Override
+                                                                                              public Thread newThread(final Runnable r) {
+                                                                                                  final Thread th = new Thread(r);
+                                                                                                  th.setName(PLUGIN_THREAD_PREFIX + th.getId());
+                                                                                                  return th;
+                                                                                              }
+                                                                                          });
         bind(ExecutorService.class).annotatedWith(Names.named(PLUGIN_EXECUTOR_NAMED)).toInstance(pluginExecutorService);
         bind(PaymentProcessor.class).asEagerSingleton();
-        bind(RefundProcessor.class).asEagerSingleton();
+        bind(PluginControlledPaymentProcessor.class).asEagerSingleton();
+        bind(PaymentGatewayProcessor.class).asEagerSingleton();
         bind(PaymentMethodProcessor.class).asEagerSingleton();
     }
 
     @Override
     protected void configure() {
-        final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(configSource);
+        final ConfigurationObjectFactory factory = new ConfigurationObjectFactory(skifeConfigSource);
         final PaymentConfig paymentConfig = factory.build(PaymentConfig.class);
 
         bind(PaymentConfig.class).toInstance(paymentConfig);
         bind(new TypeLiteral<OSGIServiceRegistration<PaymentPluginApi>>() {}).toProvider(DefaultPaymentProviderPluginRegistryProvider.class).asEagerSingleton();
+        bind(new TypeLiteral<OSGIServiceRegistration<PaymentControlPluginApi>>() {}).toProvider(DefaultPaymentControlProviderPluginRegistryProvider.class).asEagerSingleton();
 
-        bind(PaymentInternalApi.class).to(DefaultPaymentInternalApi.class).asEagerSingleton();
         bind(PaymentApi.class).to(DefaultPaymentApi.class).asEagerSingleton();
+        bind(PaymentGatewayApi.class).to(DefaultPaymentGatewayApi.class).asEagerSingleton();
         bind(InvoiceHandler.class).asEagerSingleton();
         bind(PaymentTagHandler.class).asEagerSingleton();
         bind(PaymentService.class).to(DefaultPaymentService.class).asEagerSingleton();
         installPaymentProviderPlugins(paymentConfig);
         installPaymentDao();
         installProcessors(paymentConfig);
+        installStateMachines();
+        installAutomatonRunner();
         installRetryEngines();
+        installJanitor();
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentControlProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentControlProviderPlugin.java
new file mode 100644
index 0000000..33ddf53
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentControlProviderPlugin.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.provider;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.payment.retry.DefaultFailureCallResult;
+import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult;
+import org.killbill.billing.retry.plugin.api.FailureCallResult;
+import org.killbill.billing.retry.plugin.api.PaymentControlApiException;
+import org.killbill.billing.retry.plugin.api.PaymentControlContext;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.retry.plugin.api.PriorPaymentControlResult;
+
+public class DefaultNoOpPaymentControlProviderPlugin implements PaymentControlPluginApi {
+
+    private PaymentControlApiException paymentControlPluginApiException;
+    private boolean isRetryAborted;
+    private DateTime nextRetryDate;
+
+    @Override
+    public PriorPaymentControlResult priorCall(final PaymentControlContext retryPluginContext) throws PaymentControlApiException {
+        return new DefaultPriorPaymentControlResult(isRetryAborted, null);
+    }
+
+    @Override
+    public void onSuccessCall(final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
+    }
+
+    @Override
+    public FailureCallResult onFailureCall(final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
+        return new DefaultFailureCallResult(nextRetryDate);
+    }
+
+    public DefaultNoOpPaymentControlProviderPlugin setPaymentControlPluginApiException(final PaymentControlApiException paymentControlPluginApiException) {
+        this.paymentControlPluginApiException = paymentControlPluginApiException;
+        return this;
+    }
+
+    public DefaultNoOpPaymentControlProviderPlugin setRetryAborted(final boolean isRetryAborted) {
+        this.isRetryAborted = isRetryAborted;
+        return this;
+    }
+
+    public DefaultNoOpPaymentControlProviderPlugin setNextRetryDate(final DateTime nextRetryDate) {
+        this.nextRetryDate = nextRetryDate;
+        return this;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java
index 4e5aa69..e2b187d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentInfoPlugin.java
@@ -17,27 +17,35 @@
 package org.killbill.billing.payment.provider;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.payment.plugin.api.PaymentInfoPlugin;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+
+import com.google.common.collect.ImmutableList;
 
-public class DefaultNoOpPaymentInfoPlugin implements PaymentInfoPlugin {
+public class DefaultNoOpPaymentInfoPlugin implements PaymentTransactionInfoPlugin {
 
     private final UUID kbPaymentId;
+    private final UUID kbTransactionPaymentId;
     private final BigDecimal amount;
     private final DateTime effectiveDate;
     private final DateTime createdDate;
     private final PaymentPluginStatus status;
     private final String error;
     private final Currency currency;
+    private final TransactionType transactionType;
 
-    public DefaultNoOpPaymentInfoPlugin(final UUID kbPaymentId, final BigDecimal amount, final Currency currency, final DateTime effectiveDate,
+    public DefaultNoOpPaymentInfoPlugin(final UUID kbPaymentId, final UUID kbTransactionPaymentId, final TransactionType transactionType, final BigDecimal amount, final Currency currency, final DateTime effectiveDate,
                                         final DateTime createdDate, final PaymentPluginStatus status, final String error) {
         this.kbPaymentId = kbPaymentId;
+        this.kbTransactionPaymentId = kbTransactionPaymentId;
+        this.transactionType = transactionType;
         this.amount = amount;
         this.effectiveDate = effectiveDate;
         this.createdDate = createdDate;
@@ -52,6 +60,16 @@ public class DefaultNoOpPaymentInfoPlugin implements PaymentInfoPlugin {
     }
 
     @Override
+    public UUID getKbTransactionPaymentId() {
+        return kbTransactionPaymentId;
+    }
+
+    @Override
+    public TransactionType getTransactionType() {
+        return transactionType;
+    }
+
+    @Override
     public BigDecimal getAmount() {
         return amount;
     }
@@ -97,6 +115,11 @@ public class DefaultNoOpPaymentInfoPlugin implements PaymentInfoPlugin {
     }
 
     @Override
+    public List<PluginProperty> getProperties() {
+        return ImmutableList.<PluginProperty>of();
+    }
+
+    @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("DefaultNoOpPaymentInfoPlugin{");
         sb.append("kbPaymentId=").append(kbPaymentId);
@@ -136,9 +159,15 @@ public class DefaultNoOpPaymentInfoPlugin implements PaymentInfoPlugin {
         if (error != null ? !error.equals(that.error) : that.error != null) {
             return false;
         }
+        if (transactionType != null ? !transactionType.equals(that.transactionType) : that.transactionType != null) {
+            return false;
+        }
         if (kbPaymentId != null ? !kbPaymentId.equals(that.kbPaymentId) : that.kbPaymentId != null) {
             return false;
         }
+        if (kbTransactionPaymentId != null ? !kbTransactionPaymentId.equals(that.kbTransactionPaymentId) : that.kbTransactionPaymentId != null) {
+            return false;
+        }
         if (status != that.status) {
             return false;
         }
@@ -149,8 +178,10 @@ public class DefaultNoOpPaymentInfoPlugin implements PaymentInfoPlugin {
     @Override
     public int hashCode() {
         int result = kbPaymentId != null ? kbPaymentId.hashCode() : 0;
+        result = 31 * result + (kbTransactionPaymentId != null ? kbTransactionPaymentId.hashCode() : 0);
         result = 31 * result + (amount != null ? amount.hashCode() : 0);
         result = 31 * result + (effectiveDate != null ? effectiveDate.hashCode() : 0);
+        result = 31 * result + (transactionType != null ? transactionType.hashCode() : 0);
         result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
         result = 31 * result + (status != null ? status.hashCode() : 0);
         result = 31 * result + (error != null ? error.hashCode() : 0);
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentMethodPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentMethodPlugin.java
index f22baa9..ca5ce3a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentMethodPlugin.java
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentMethodPlugin.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -21,15 +23,17 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
-import org.killbill.billing.payment.api.PaymentMethodKVInfo;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
+import org.killbill.billing.payment.api.PluginProperty;
+
+import com.google.common.collect.ImmutableList;
 
 public class DefaultNoOpPaymentMethodPlugin implements PaymentMethodPlugin {
 
     private final UUID kbPaymentMethodId;
     private final String externalId;
     private final boolean isDefault;
-    private List<PaymentMethodKVInfo> props;
+    private List<PluginProperty> props;
 
     public DefaultNoOpPaymentMethodPlugin(final UUID kbPaymentMethodId, final PaymentMethodPlugin src) {
         this.kbPaymentMethodId = kbPaymentMethodId;
@@ -40,18 +44,18 @@ public class DefaultNoOpPaymentMethodPlugin implements PaymentMethodPlugin {
 
     public DefaultNoOpPaymentMethodPlugin(final String externalId,
                                           final boolean isDefault,
-                                          final List<PaymentMethodKVInfo> props) {
+                                          final Iterable<PluginProperty> props) {
         this(null, externalId, isDefault, props);
     }
 
     public DefaultNoOpPaymentMethodPlugin(@Nullable final UUID kbPaymentMethodId,
                                           final String externalId,
                                           final boolean isDefault,
-                                          final List<PaymentMethodKVInfo> props) {
+                                          @Nullable final Iterable<PluginProperty> props) {
         this.kbPaymentMethodId = kbPaymentMethodId;
         this.externalId = externalId;
         this.isDefault = isDefault;
-        this.props = props;
+        this.props = props == null ? ImmutableList.<PluginProperty>of() : ImmutableList.<PluginProperty>copyOf(props);
     }
 
     @Override
@@ -70,75 +74,15 @@ public class DefaultNoOpPaymentMethodPlugin implements PaymentMethodPlugin {
     }
 
     @Override
-    public List<PaymentMethodKVInfo> getProperties() {
+    public List<PluginProperty> getProperties() {
         return props;
     }
 
-    public void setProps(final List<PaymentMethodKVInfo> props) {
+    public void setProps(final List<PluginProperty> props) {
         this.props = props;
     }
 
     @Override
-    public String getType() {
-        return "noop";
-    }
-
-    @Override
-    public String getCCName() {
-        return null;
-    }
-
-    @Override
-    public String getCCType() {
-        return null;
-    }
-
-    @Override
-    public String getCCExpirationMonth() {
-        return null;
-    }
-
-    @Override
-    public String getCCExpirationYear() {
-        return null;
-    }
-
-    @Override
-    public String getCCLast4() {
-        return null;
-    }
-
-    @Override
-    public String getAddress1() {
-        return null;
-    }
-
-    @Override
-    public String getAddress2() {
-        return null;
-    }
-
-    @Override
-    public String getCity() {
-        return null;
-    }
-
-    @Override
-    public String getState() {
-        return null;
-    }
-
-    @Override
-    public String getZip() {
-        return null;
-    }
-
-    @Override
-    public String getCountry() {
-        return null;
-    }
-
-    @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
         sb.append("DefaultNoOpPaymentMethodPlugin");
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
index fd1deed..2908799 100644
--- a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultNoOpPaymentProviderPlugin.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,6 +19,8 @@
 package org.killbill.billing.payment.provider;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -25,25 +29,26 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.clock.Clock;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.plugin.api.GatewayNotification;
+import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
 import org.killbill.billing.payment.plugin.api.NoOpPaymentPluginApi;
-import org.killbill.billing.payment.plugin.api.PaymentInfoPlugin;
 import org.killbill.billing.payment.plugin.api.PaymentMethodInfoPlugin;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
-import org.killbill.billing.payment.plugin.api.RefundInfoPlugin;
-import org.killbill.billing.payment.plugin.api.RefundPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.DefaultPagination;
 import org.killbill.billing.util.entity.Pagination;
+import org.killbill.clock.Clock;
 
 import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.Multimap;
 import com.google.inject.Inject;
 
 public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
@@ -54,9 +59,7 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
     private final AtomicBoolean makeNextInvoiceFailWithException = new AtomicBoolean(false);
     private final AtomicBoolean makeAllInvoicesFailWithError = new AtomicBoolean(false);
 
-    private final Map<String, PaymentInfoPlugin> payments = new ConcurrentHashMap<String, PaymentInfoPlugin>();
-    // Note: we can't use HashMultiMap as we care about storing duplicate key/value pairs
-    private final Multimap<String, RefundInfoPlugin> refunds = LinkedListMultimap.<String, RefundInfoPlugin>create();
+    private final Map<String, List<PaymentTransactionInfoPlugin>> payments = new ConcurrentHashMap<String, List<PaymentTransactionInfoPlugin>>();
     private final Map<String, List<PaymentMethodPlugin>> paymentMethods = new ConcurrentHashMap<String, List<PaymentMethodPlugin>>();
 
     private final Clock clock;
@@ -90,51 +93,70 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
-    public PaymentInfoPlugin processPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
-        if (makeNextInvoiceFailWithException.getAndSet(false)) {
-            throw new PaymentPluginApiException("", "test error");
-        }
+    public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
+            throws PaymentPluginApiException {
+        return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency);
+    }
 
-        final PaymentPluginStatus status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED;
-        final PaymentInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, amount, currency, clock.getUTCNow(), clock.getUTCNow(), status, null);
-        payments.put(kbPaymentId.toString(), result);
-        return result;
+    @Override
+    public PaymentTransactionInfoPlugin capturePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
+            throws PaymentPluginApiException {
+        return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.CAPTURE, amount, currency);
     }
 
     @Override
-    public PaymentInfoPlugin getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) throws PaymentPluginApiException {
-        final PaymentInfoPlugin payment = payments.get(kbPaymentId.toString());
-        if (payment == null) {
-            throw new PaymentPluginApiException("", "No payment found for payment id " + kbPaymentId.toString());
-        }
-        return payment;
+    public PaymentTransactionInfoPlugin purchasePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, currency);
     }
 
     @Override
-    public Pagination<PaymentInfoPlugin> searchPayments(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
-        final ImmutableList<PaymentInfoPlugin> allResults = ImmutableList.<PaymentInfoPlugin>copyOf(Iterables.<PaymentInfoPlugin>filter(Iterables.<PaymentInfoPlugin>concat(payments.values()), new Predicate<PaymentInfoPlugin>() {
-            @Override
-            public boolean apply(final PaymentInfoPlugin input) {
-                return (input.getKbPaymentId() != null && input.getKbPaymentId().toString().equals(searchKey)) ||
-                       (input.getFirstPaymentReferenceId() != null && input.getFirstPaymentReferenceId().contains(searchKey)) ||
-                       (input.getSecondPaymentReferenceId() != null && input.getSecondPaymentReferenceId().contains(searchKey));
-            }
-        }));
+    public PaymentTransactionInfoPlugin voidPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context)
+            throws PaymentPluginApiException {
+        return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.VOID, BigDecimal.ZERO, null);
+    }
+
+    @Override
+    public PaymentTransactionInfoPlugin creditPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
+            throws PaymentPluginApiException {
+        return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.CREDIT, amount, currency);
+    }
+
+    @Override
+    public List<PaymentTransactionInfoPlugin> getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentPluginApiException {
+        return payments.get(kbPaymentId.toString());
+    }
 
-        final List<PaymentInfoPlugin> results;
+    @Override
+    public Pagination<PaymentTransactionInfoPlugin> searchPayments(final String searchKey, final Long offset, final Long limit, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentPluginApiException {
+
+        final List<PaymentTransactionInfoPlugin> flattenedTransactions = ImmutableList.copyOf(Iterables.concat(payments.values()));
+        final Collection<PaymentTransactionInfoPlugin> filteredTransactions = Collections2.<PaymentTransactionInfoPlugin>filter(flattenedTransactions,
+                                                                                                                                new Predicate<PaymentTransactionInfoPlugin>() {
+                                                                                                                                    @Override
+                                                                                                                                    public boolean apply(final PaymentTransactionInfoPlugin input) {
+                                                                                                                                        return (input.getKbPaymentId() != null && input.getKbPaymentId().toString().equals(searchKey)) ||
+                                                                                                                                               (input.getFirstPaymentReferenceId() != null && input.getFirstPaymentReferenceId().contains(searchKey)) ||
+                                                                                                                                               (input.getSecondPaymentReferenceId() != null && input.getSecondPaymentReferenceId().contains(searchKey));
+                                                                                                                                    }
+                                                                                                                                }
+                                                                                                                               );
+
+        final ImmutableList<PaymentTransactionInfoPlugin> allResults = ImmutableList.<PaymentTransactionInfoPlugin>copyOf(filteredTransactions);
+
+        final List<PaymentTransactionInfoPlugin> results;
         if (offset >= allResults.size()) {
-            results = ImmutableList.<PaymentInfoPlugin>of();
+            results = ImmutableList.<PaymentTransactionInfoPlugin>of();
         } else if (offset + limit > allResults.size()) {
             results = allResults.subList(offset.intValue(), allResults.size());
         } else {
             results = allResults.subList(offset.intValue(), offset.intValue() + limit.intValue());
         }
 
-        return new DefaultPagination<PaymentInfoPlugin>(offset, limit, (long) results.size(), (long) payments.values().size(), results.iterator());
+        return new DefaultPagination<PaymentTransactionInfoPlugin>(offset, limit, (long) results.size(), (long) payments.values().size(), results.iterator());
     }
 
     @Override
-    public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
+    public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
         final PaymentMethodPlugin realWithID = new DefaultNoOpPaymentMethodPlugin(kbPaymentMethodId, paymentMethodProps);
         List<PaymentMethodPlugin> pms = paymentMethods.get(kbPaymentMethodId.toString());
         if (pms == null) {
@@ -145,7 +167,7 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
-    public void deletePaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    public void deletePaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
         PaymentMethodPlugin toBeDeleted = null;
         final List<PaymentMethodPlugin> pms = paymentMethods.get(kbPaymentMethodId.toString());
         if (pms != null) {
@@ -163,9 +185,9 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
-    public PaymentMethodPlugin getPaymentMethodDetail(final UUID kbAccountId, final UUID kbPaymentMethodId, final TenantContext context) throws PaymentPluginApiException {
+    public PaymentMethodPlugin getPaymentMethodDetail(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentPluginApiException {
         final List<PaymentMethodPlugin> paymentMethodPlugins = paymentMethods.get(kbPaymentMethodId.toString());
-        if (paymentMethodPlugins == null || paymentMethodPlugins.size() == 0) {
+        if (paymentMethodPlugins == null || paymentMethodPlugins.isEmpty()) {
             return null;
         } else {
             return paymentMethodPlugins.get(0);
@@ -173,26 +195,20 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
-    public void setDefaultPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    public void setDefaultPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
     }
 
     @Override
-    public List<PaymentMethodInfoPlugin> getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final CallContext context) {
+    public List<PaymentMethodInfoPlugin> getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final Iterable<PluginProperty> properties, final CallContext context) {
         return ImmutableList.<PaymentMethodInfoPlugin>of();
     }
 
     @Override
-    public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+    public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentPluginApiException {
         final ImmutableList<PaymentMethodPlugin> allResults = ImmutableList.<PaymentMethodPlugin>copyOf(Iterables.<PaymentMethodPlugin>filter(Iterables.<PaymentMethodPlugin>concat(paymentMethods.values()), new Predicate<PaymentMethodPlugin>() {
             @Override
             public boolean apply(final PaymentMethodPlugin input) {
-                return (input.getAddress1() != null && input.getAddress1().contains(searchKey)) ||
-                       (input.getAddress2() != null && input.getAddress2().contains(searchKey)) ||
-                       (input.getCCLast4() != null && input.getCCLast4().contains(searchKey)) ||
-                       (input.getCCName() != null && input.getCCName().contains(searchKey)) ||
-                       (input.getCity() != null && input.getCity().contains(searchKey)) ||
-                       (input.getState() != null && input.getState().contains(searchKey)) ||
-                       (input.getCountry() != null && input.getCountry().contains(searchKey));
+                return input.getKbPaymentMethodId().toString().equals(searchKey);
             }
         }));
 
@@ -209,55 +225,60 @@ public class DefaultNoOpPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
-    public void resetPaymentMethods(final UUID kbAccountId, final List<PaymentMethodInfoPlugin> paymentMethods) {
+    public void resetPaymentMethods(final UUID kbAccountId, final List<PaymentMethodInfoPlugin> paymentMethods, final Iterable<PluginProperty> properties, final CallContext callContext) {
+    }
+
+    @Override
+    public HostedPaymentPageFormDescriptor buildFormDescriptor(final UUID kbAccountId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext) {
+        return null;
+    }
+
+    @Override
+    public GatewayNotification processNotification(final String notification, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
+        return null;
     }
 
     @Override
-    public RefundInfoPlugin processRefund(final UUID kbAccountId, final UUID kbPaymentId, final BigDecimal refundAmount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
-        final PaymentInfoPlugin paymentInfoPlugin = getPaymentInfo(kbAccountId, kbPaymentId, context);
-        if (paymentInfoPlugin == null) {
+    public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal refundAmount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        final List<PaymentTransactionInfoPlugin> transactions = getPaymentInfo(kbAccountId, kbPaymentId, properties, context);
+        if (transactions == null || transactions.size() == 0) {
             throw new PaymentPluginApiException("", String.format("No payment found for payment id %s (plugin %s)", kbPaymentId.toString(), PLUGIN_NAME));
         }
 
-        BigDecimal maxAmountRefundable = paymentInfoPlugin.getAmount();
-        for (final RefundInfoPlugin refund : refunds.get(kbPaymentId.toString())) {
-            maxAmountRefundable = maxAmountRefundable.add(refund.getAmount().negate());
+        final Iterable<PaymentTransactionInfoPlugin> refundTransactions = Iterables.filter(transactions, new Predicate<PaymentTransactionInfoPlugin>() {
+            @Override
+            public boolean apply(final PaymentTransactionInfoPlugin input) {
+                return input.getTransactionType() == TransactionType.REFUND;
+            }
+        });
+
+        BigDecimal maxAmountRefundable = BigDecimal.ZERO;
+        for (PaymentTransactionInfoPlugin cur : refundTransactions) {
+            maxAmountRefundable = maxAmountRefundable.add(cur.getAmount());
         }
+
         if (maxAmountRefundable.compareTo(refundAmount) < 0) {
             throw new PaymentPluginApiException("", String.format("Refund amount of %s for payment id %s is bigger than the payment amount %s (plugin %s)",
-                                                                  refundAmount, kbPaymentId.toString(), paymentInfoPlugin.getAmount(), PLUGIN_NAME));
+                                                                  refundAmount, kbPaymentId.toString(), maxAmountRefundable, PLUGIN_NAME));
         }
-
-        final DefaultNoOpRefundInfoPlugin refundInfoPlugin = new DefaultNoOpRefundInfoPlugin(kbPaymentId, refundAmount, currency, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
-        refunds.put(kbPaymentId.toString(), refundInfoPlugin);
-
-        return refundInfoPlugin;
+        return getInternalNoopPaymentInfoResult(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency);
     }
 
-    @Override
-    public List<RefundInfoPlugin> getRefundInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) {
-        return ImmutableList.<RefundInfoPlugin>copyOf(refunds.get(kbPaymentId.toString()));
-    }
+    private PaymentTransactionInfoPlugin getInternalNoopPaymentInfoResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType transactionType, final BigDecimal amount, final Currency currency) throws PaymentPluginApiException {
+        if (makeNextInvoiceFailWithException.getAndSet(false)) {
+            throw new PaymentPluginApiException("", "test error");
+        }
 
-    @Override
-    public Pagination<RefundInfoPlugin> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
-        final ImmutableList<RefundInfoPlugin> allResults = ImmutableList.<RefundInfoPlugin>copyOf(Iterables.<RefundInfoPlugin>filter(Iterables.<RefundInfoPlugin>concat(refunds.values()), new Predicate<RefundInfoPlugin>() {
-            @Override
-            public boolean apply(final RefundInfoPlugin input) {
-                return (input.getFirstRefundReferenceId() != null && input.getFirstRefundReferenceId().contains(searchKey)) ||
-                       (input.getSecondRefundReferenceId() != null && input.getSecondRefundReferenceId().contains(searchKey));
-            }
-        }));
+        final PaymentPluginStatus status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED;
+        BigDecimal totalAmount = amount;
 
-        final List<RefundInfoPlugin> results;
-        if (offset >= allResults.size()) {
-            results = ImmutableList.<RefundInfoPlugin>of();
-        } else if (offset + limit > allResults.size()) {
-            results = allResults.subList(offset.intValue(), allResults.size());
-        } else {
-            results = allResults.subList(offset.intValue(), offset.intValue() + limit.intValue());
+        List<PaymentTransactionInfoPlugin> paymentTransactionInfoPlugins = payments.get(kbPaymentId.toString());
+        if (paymentTransactionInfoPlugins == null) {
+            paymentTransactionInfoPlugins = new ArrayList<PaymentTransactionInfoPlugin>();
+            payments.put(kbPaymentId.toString(), paymentTransactionInfoPlugins);
         }
-
-        return new DefaultPagination<RefundInfoPlugin>(offset, limit, (long) results.size(), (long) refunds.values().size(), results.iterator());
+        final PaymentTransactionInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, transactionType, totalAmount, currency, clock.getUTCNow(), clock.getUTCNow(), status, null);
+        paymentTransactionInfoPlugins.add(result);
+        return result;
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentControlProviderPluginRegistry.java b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentControlProviderPluginRegistry.java
new file mode 100644
index 0000000..2cbe41e
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/DefaultPaymentControlProviderPluginRegistry.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.provider;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+
+public class DefaultPaymentControlProviderPluginRegistry implements OSGIServiceRegistration<PaymentControlPluginApi> {
+
+    private final static Logger log = LoggerFactory.getLogger(DefaultPaymentProviderPluginRegistry.class);
+
+    private final String defaultPlugin;
+    private final Map<String, PaymentControlPluginApi> pluginsByName = new ConcurrentHashMap<String, PaymentControlPluginApi>();
+
+    @Inject
+    public DefaultPaymentControlProviderPluginRegistry(final PaymentConfig config) {
+        this.defaultPlugin = config.getDefaultRetryProvider();
+    }
+
+    @Override
+    public void registerService(final OSGIServiceDescriptor desc, final PaymentControlPluginApi service) {
+        log.info("DefaultPaymentControlProviderPluginRegistry registering service " + desc.getRegistrationName());
+        pluginsByName.put(desc.getRegistrationName(), service);
+    }
+
+    @Override
+    public void unregisterService(final String serviceName) {
+        log.info("DefaultPaymentControlProviderPluginRegistry unregistering service " + serviceName);
+        pluginsByName.remove(serviceName);
+    }
+
+    @Override
+    public PaymentControlPluginApi getServiceForName(final String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("Null payment plugin APi name");
+        }
+        final PaymentControlPluginApi plugin = pluginsByName.get(name);
+        return plugin;
+    }
+
+    @Override
+    public Set<String> getAllServices() {
+        return pluginsByName.keySet();
+    }
+
+    @Override
+    public Class<PaymentControlPluginApi> getServiceType() {
+        return PaymentControlPluginApi.class;
+    }
+}
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java
index 6c5579a..a6167a8 100644
--- a/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/ExternalPaymentProviderPlugin.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -22,20 +24,21 @@ import java.util.List;
 import java.util.UUID;
 
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.clock.Clock;
-import org.killbill.billing.payment.api.PaymentMethodKVInfo;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
-import org.killbill.billing.payment.plugin.api.PaymentInfoPlugin;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.plugin.api.GatewayNotification;
+import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.payment.plugin.api.PaymentMethodInfoPlugin;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
-import org.killbill.billing.payment.plugin.api.RefundInfoPlugin;
-import org.killbill.billing.payment.plugin.api.RefundPluginStatus;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.DefaultPagination;
 import org.killbill.billing.util.entity.Pagination;
+import org.killbill.clock.Clock;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterators;
@@ -59,63 +62,84 @@ public class ExternalPaymentProviderPlugin implements PaymentPluginApi {
     }
 
     @Override
-    public PaymentInfoPlugin processPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
-        return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
+    public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
+    }
+
+    @Override
+    public PaymentTransactionInfoPlugin capturePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.CAPTURE, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
+    }
+
+    @Override
+    public PaymentTransactionInfoPlugin purchasePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
     }
 
     @Override
-    public PaymentInfoPlugin getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) throws PaymentPluginApiException {
-        return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, BigDecimal.ZERO, null, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
+    public PaymentTransactionInfoPlugin voidPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.VOID, BigDecimal.ZERO, null, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
     }
 
     @Override
-    public Pagination<PaymentInfoPlugin> searchPayments(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
-        return new DefaultPagination<PaymentInfoPlugin>(offset, limit, 0L, 0L, Iterators.<PaymentInfoPlugin>emptyIterator());
+    public PaymentTransactionInfoPlugin creditPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.CREDIT, amount, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
     }
 
     @Override
-    public RefundInfoPlugin processRefund(final UUID kbAccountId, final UUID kbPaymentId, final BigDecimal refundAmount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
-        return new DefaultNoOpRefundInfoPlugin(kbPaymentId, BigDecimal.ZERO, currency, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
+    public List<PaymentTransactionInfoPlugin> getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentPluginApiException {
+        // TODO broken...
+        return ImmutableList.of();
     }
 
     @Override
-    public List<RefundInfoPlugin> getRefundInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) throws PaymentPluginApiException {
-        return Collections.emptyList();
+    public Pagination<PaymentTransactionInfoPlugin> searchPayments(final String searchKey, final Long offset, final Long limit, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentPluginApiException {
+        return new DefaultPagination<PaymentTransactionInfoPlugin>(offset, limit, 0L, 0L, Iterators.<PaymentTransactionInfoPlugin>emptyIterator());
     }
 
     @Override
-    public Pagination<RefundInfoPlugin> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
-        return new DefaultPagination<RefundInfoPlugin>(offset, limit, 0L, 0L, Iterators.<RefundInfoPlugin>emptyIterator());
+    public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal refundAmount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.REFUND, BigDecimal.ZERO, currency, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
     }
 
     @Override
-    public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
+    public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
     }
 
     @Override
-    public void deletePaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    public void deletePaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
     }
 
     @Override
-    public PaymentMethodPlugin getPaymentMethodDetail(final UUID kbAccountId, final UUID kbPaymentMethodId, final TenantContext context) throws PaymentPluginApiException {
-        return new DefaultNoOpPaymentMethodPlugin(kbPaymentMethodId, "unknown", false, Collections.<PaymentMethodKVInfo>emptyList());
+    public PaymentMethodPlugin getPaymentMethodDetail(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentMethodPlugin(kbPaymentMethodId, "unknown", false, Collections.<PluginProperty>emptyList());
     }
 
     @Override
-    public void setDefaultPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    public void setDefaultPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
     }
 
     @Override
-    public List<PaymentMethodInfoPlugin> getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final CallContext context) throws PaymentPluginApiException {
+    public List<PaymentMethodInfoPlugin> getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
         return ImmutableList.<PaymentMethodInfoPlugin>of();
     }
 
     @Override
-    public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+    public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentPluginApiException {
         return new DefaultPagination<PaymentMethodPlugin>(offset, limit, 0L, 0L, Iterators.<PaymentMethodPlugin>emptyIterator());
     }
 
     @Override
-    public void resetPaymentMethods(final UUID kbAccountId, final List<PaymentMethodInfoPlugin> paymentMethods) throws PaymentPluginApiException {
+    public void resetPaymentMethods(final UUID kbAccountId, final List<PaymentMethodInfoPlugin> paymentMethods, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
+    }
+
+    @Override
+    public HostedPaymentPageFormDescriptor buildFormDescriptor(final UUID kbAccountId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext) {
+        return null;
+    }
+
+    @Override
+    public GatewayNotification processNotification(final String notification, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
+        return null;
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginModule.java b/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginModule.java
index 94b2155..9b640c0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginModule.java
+++ b/payment/src/main/java/org/killbill/billing/payment/provider/NoOpPaymentProviderPluginModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,17 @@
 
 package org.killbill.billing.payment.provider;
 
-import com.google.inject.AbstractModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
+
 import com.google.inject.name.Names;
 
-public class NoOpPaymentProviderPluginModule extends AbstractModule {
+public class NoOpPaymentProviderPluginModule extends KillBillModule {
+
     private final String instanceName;
 
-    public NoOpPaymentProviderPluginModule(final String instanceName) {
+    public NoOpPaymentProviderPluginModule(final String instanceName, final KillbillConfigSource configSource) {
+        super(configSource);
         this.instanceName = instanceName;
     }
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java b/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
index 2aa7e45..b72d4c7 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/BaseRetryService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -20,23 +22,22 @@ import java.io.IOException;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ObjectType;
-import org.killbill.notificationq.api.NotificationEvent;
-import org.killbill.notificationq.api.NotificationQueue;
-import org.killbill.notificationq.api.NotificationQueueService;
-import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
-import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
-import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueHandler;
+import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.payment.glue.DefaultPaymentService;
 import org.killbill.billing.util.callcontext.CallOrigin;
-import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.UserType;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
+import org.killbill.notificationq.api.NotificationEvent;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
+import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
+import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 
@@ -69,9 +70,10 @@ public abstract class BaseRetryService implements RetryService {
                                                                               }
                                                                               final PaymentRetryNotificationKey key = (PaymentRetryNotificationKey) notificationKey;
                                                                               final InternalCallContext callContext = internalCallContextFactory.createInternalCallContext(tenantRecordId, accountRecordId, PAYMENT_RETRY_SERVICE, CallOrigin.INTERNAL, UserType.SYSTEM, userToken);
-                                                                              retry(key.getUuidKey(), callContext);
+                                                                              retryPaymentTransaction(key.getAttemptId(), key.getPluginName(), callContext);
                                                                           }
-                                                                      });
+                                                                      }
+                                                                     );
     }
 
     @Override
@@ -102,38 +104,17 @@ public abstract class BaseRetryService implements RetryService {
             this.internalCallContextFactory = internalCallContextFactory;
         }
 
-        public boolean scheduleRetryFromTransaction(final UUID paymentId, final DateTime timeOfRetry, final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) {
-            return scheduleRetryInternal(paymentId, timeOfRetry, entitySqlDaoWrapperFactory);
-        }
-
-        public boolean scheduleRetry(final UUID paymentId, final DateTime timeOfRetry) {
-            return scheduleRetryInternal(paymentId, timeOfRetry, null);
+        public boolean scheduleRetry(final ObjectType objectType, final UUID objectId, final UUID attemptId, final String pluginName, final DateTime timeOfRetry) {
+            return scheduleRetryInternal(objectType, objectId, attemptId, pluginName, timeOfRetry, null);
         }
 
-        // STEPH TimedoutPaymentRetryServiceScheduler
-        public void cancelAllScheduleRetryForKey(final UUID paymentId) {
-            /*
-            try {
-                NotificationQueue retryQueue = notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, getQueueName());
-                NotificationKey key = new NotificationKey() {
-                    @Override
-                    public String toString() {
-                        return paymentId.toString();
-                    }
-                };
-                retryQueue.removeNotificationsByKey(key);
-            } catch (NoSuchNotificationQueue e) {
-                log.error(String.format("Failed to retrieve notification queue %s:%s", DefaultPaymentService.SERVICE_NAME, getQueueName()));
-            }
-             */
-        }
 
-        private boolean scheduleRetryInternal(final UUID paymentId, final DateTime timeOfRetry, final EntitySqlDaoWrapperFactory<EntitySqlDao> transactionalDao) {
-            final InternalCallContext context = createCallContextFromPaymentId(paymentId);
+        private boolean scheduleRetryInternal(final ObjectType objectType, final UUID objectId, final UUID attemptId, final String pluginName, final DateTime timeOfRetry, final EntitySqlDaoWrapperFactory<EntitySqlDao> transactionalDao) {
+            final InternalCallContext context = createCallContextFromPaymentId(objectType, objectId);
 
             try {
                 final NotificationQueue retryQueue = notificationQueueService.getNotificationQueue(DefaultPaymentService.SERVICE_NAME, getQueueName());
-                final NotificationEvent key = new PaymentRetryNotificationKey(paymentId);
+                final NotificationEvent key = new PaymentRetryNotificationKey(attemptId, pluginName);
                 if (retryQueue != null) {
                     if (transactionalDao == null) {
                         retryQueue.recordFutureNotification(timeOfRetry, key, context.getUserToken(), context.getAccountRecordId(), context.getTenantRecordId());
@@ -145,14 +126,14 @@ public abstract class BaseRetryService implements RetryService {
                 log.error(String.format("Failed to retrieve notification queue %s:%s", DefaultPaymentService.SERVICE_NAME, getQueueName()));
                 return false;
             } catch (IOException e) {
-                log.error(String.format("Failed to serialize notificationQueue event for paymentId %s", paymentId));
+                log.error(String.format("Failed to serialize notificationQueue event for object %s, objectId %s", objectId));
                 return false;
             }
             return true;
         }
 
-        protected InternalCallContext createCallContextFromPaymentId(final UUID paymentId) {
-            return internalCallContextFactory.createInternalCallContext(paymentId, ObjectType.PAYMENT, PAYMENT_RETRY_SERVICE, CallOrigin.INTERNAL, UserType.SYSTEM, null);
+        protected InternalCallContext createCallContextFromPaymentId(final ObjectType objectType, final UUID objectId) {
+            return internalCallContextFactory.createInternalCallContext(objectId, objectType, PAYMENT_RETRY_SERVICE, CallOrigin.INTERNAL, UserType.SYSTEM, null);
         }
 
         public abstract String getQueueName();
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java b/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java
index 00348cf..50a3155 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/PaymentRetryNotificationKey.java
@@ -13,19 +13,33 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package org.killbill.billing.payment.retry;
 
 import java.util.UUID;
 
-import org.killbill.notificationq.DefaultUUIDNotificationKey;
+import org.killbill.notificationq.api.NotificationEvent;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-public class PaymentRetryNotificationKey extends DefaultUUIDNotificationKey {
+public class PaymentRetryNotificationKey implements NotificationEvent {
+
+    private final UUID attemptId;
+    private final String pluginName;
 
     @JsonCreator
-    public PaymentRetryNotificationKey(@JsonProperty("uuidKey") UUID uuidKey) {
-        super(uuidKey);
+    public PaymentRetryNotificationKey(@JsonProperty("attemptId") UUID attemptId,
+                                       @JsonProperty("pluginName") String pluginName) {
+        this.attemptId = attemptId;
+        this.pluginName = pluginName;
+    }
+
+    public UUID getAttemptId() {
+        return attemptId;
+    }
+
+    public String getPluginName() {
+        return pluginName;
     }
 }
diff --git a/payment/src/main/java/org/killbill/billing/payment/retry/RetryService.java b/payment/src/main/java/org/killbill/billing/payment/retry/RetryService.java
index 657848a..9864e0a 100644
--- a/payment/src/main/java/org/killbill/billing/payment/retry/RetryService.java
+++ b/payment/src/main/java/org/killbill/billing/payment/retry/RetryService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -13,26 +15,25 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package org.killbill.billing.payment.retry;
 
 import java.util.UUID;
 
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
 import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
-import org.killbill.billing.callcontext.InternalCallContext;
 
 public interface RetryService {
 
-    public void initialize(final String svcName)
-            throws NotificationQueueAlreadyExists;
+    public void initialize(final String svcName) throws NotificationQueueAlreadyExists;
 
     public void start();
 
-    public void stop()
-            throws NoSuchNotificationQueue;
+    public void stop() throws NoSuchNotificationQueue;
 
     public String getQueueName();
 
-    public void retry(UUID paymentId, final InternalCallContext context);
-
+    public void retryPaymentTransaction(final UUID attemptId, String pluginName, final InternalCallContext context);
 }
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
index 87ebd96..1cc0604 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
@@ -1,13 +1,23 @@
 group PaymentAttemptSqlDao: EntitySqlDao;
 
+tableName() ::= "payment_attempts"
+
+historyTableName() ::= "payment_attempt_history"
+
+
+
 tableFields(prefix) ::= <<
-  <prefix>payment_id
+  <prefix>account_id
 , <prefix>payment_method_id
-, <prefix>gateway_error_code
-, <prefix>gateway_error_msg
-, <prefix>processing_status
-, <prefix>requested_amount
-, <prefix>requested_currency
+, <prefix>payment_external_key
+, <prefix>transaction_id
+, <prefix>transaction_external_key
+, <prefix>transaction_type
+, <prefix>state_name
+, <prefix>amount
+, <prefix>currency
+, <prefix>plugin_name
+, <prefix>plugin_properties
 , <prefix>created_by
 , <prefix>created_date
 , <prefix>updated_by
@@ -15,60 +25,69 @@ tableFields(prefix) ::= <<
 >>
 
 tableValues() ::= <<
-  :paymentId
+  :accountId
 , :paymentMethodId
-, :gatewayErrorCode
-, :gatewayErrorMsg
-, :processingStatus
-, :requestedAmount
-, :requestedCurrency
+, :paymentExternalKey
+, :transactionId
+, :transactionExternalKey
+, :transactionType
+, :stateName
+, :amount
+, :currency
+, :pluginName
+, :pluginProperties
 , :createdBy
 , :createdDate
 , :updatedBy
 , :updatedDate
 >>
 
-tableName() ::= "payment_attempts"
-
-historyTableName() ::= "payment_attempt_history"
-
+getByTransactionExternalKey() ::= <<
+select
+<allTableFields("")>
+from <tableName()>
+where transaction_external_key = :transactionExternalKey
+<andCheckSoftDeletionWithComma("")>
+<AND_CHECK_TENANT("")>
+<defaultOrderBy()>
+;
+>>
 
-getById(id) ::= <<
-select <allTableFields("pa.")>
-, pa.created_date as effective_date
-, p.account_id as account_id
-, p.invoice_id as invoice_id
-from <tableName()> pa join payments p
-where pa.id = :id
-and pa.payment_id = p.id
-<AND_CHECK_TENANT("pa.")>
-<AND_CHECK_TENANT("p.")>
+getByPaymentExternalKey() ::= <<
+select
+<allTableFields("")>
+from <tableName()>
+where payment_external_key = :paymentExternalKey
+<andCheckSoftDeletionWithComma("")>
+<AND_CHECK_TENANT()>
+<defaultOrderBy()>
 ;
 >>
 
-getByPaymentId(paymentId) ::= <<
-select <allTableFields("pa.")>
-, pa.created_date as effective_date
-, p.account_id as account_id
-, p.invoice_id as invoice_id
-from <tableName()> pa join payments p
-where pa.payment_id = :paymentId
-and p.id = :paymentId
-<AND_CHECK_TENANT("pa.")>
-<AND_CHECK_TENANT("p.")>
-<defaultOrderBy("pa.")>
+/* Does not include tenant info, global */
+getByStateName() ::= <<
+select
+<allTableFields("")>
+from <tableName()>
+where state_name = :stateName
+and created_date \< :createdBeforeDate
+<andCheckSoftDeletionWithComma("")>
+<defaultOrderBy()>
 ;
 >>
 
 
-updatePaymentAttemptStatus() ::= <<
+updateAttempt() ::= <<
 update <tableName()>
-set processing_status = :processingStatus
-, gateway_error_code = :gatewayErrorCode
-, gateway_error_msg = :gatewayErrorMsg
+set state_name = :stateName
+, transaction_id = :transactionId
 , updated_by = :updatedBy
 , updated_date = :createdDate
 where id = :id
 <AND_CHECK_TENANT()>
 ;
 >>
+
+
+
+
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
index a4135fb..aee2984 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentMethodSqlDao.sql.stg
@@ -9,7 +9,8 @@ historyTableName() ::= "payment_method_history"
 andCheckSoftDeletionWithComma(prefix) ::= "and <prefix>is_active"
 
 tableFields(prefix) ::= <<
-  <prefix>account_id
+  <prefix>external_key
+, <prefix>account_id
 , <prefix>plugin_name
 , <prefix>is_active
 , <prefix>created_by
@@ -19,7 +20,8 @@ tableFields(prefix) ::= <<
 >>
 
 tableValues() ::= <<
-  :accountId
+  :externalKey
+, :accountId
 , :pluginName
 , :isActive
 , :createdBy
@@ -28,7 +30,6 @@ tableValues() ::= <<
 , :updatedDate
 >>
 
-
 markPaymentMethodAsDeleted(id) ::= <<
 update <tableName()>
 set is_active = 0
@@ -49,6 +50,23 @@ where  id = :id
 ;
 >>
 
+getByExternalKey() ::= <<
+select <allTableFields()>
+from <tableName()>
+where external_key = :externalKey
+and is_active = 1
+<AND_CHECK_TENANT()>
+;
+>>
+
+getPaymentMethodByExternalKeyIncludedDeleted() ::= <<
+select <allTableFields()>
+from <tableName()>
+where external_key = :externalKey
+<AND_CHECK_TENANT()>
+;
+>>
+
 getPaymentMethodIncludedDelete(accountId) ::= <<
 select <allTableFields()>
 from <tableName()>
@@ -73,6 +91,13 @@ where account_id = :accountId
 ;
 >>
 
+searchQuery(prefix) ::= <<
+     <idField(prefix)> = :searchKey
+  or <prefix>external_key like :likeSearchKey
+  or <prefix>account_id = :searchKey
+  or <prefix>plugin_name like :likeSearchKey
+>>
+
 getByPluginName() ::= <<
 select
 <allTableFields("t.")>
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
index 88bb4b0..51ca239 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentSqlDao.sql.stg
@@ -1,24 +1,23 @@
 group PaymentSqlDao: EntitySqlDao;
 
+tableName() ::= "payments"
+
+historyTableName() ::= "payment_history"
 
 extraTableFieldsWithComma(prefix) ::= <<
 , <prefix>record_id as payment_number
 >>
 
 defaultOrderBy(prefix) ::= <<
-order by <prefix>effective_date ASC, <recordIdField(prefix)> ASC
+order by <prefix>created_date ASC, <recordIdField(prefix)> ASC
 >>
 
 tableFields(prefix) ::= <<
   <prefix>account_id
-, <prefix>invoice_id
 , <prefix>payment_method_id
-, <prefix>amount
-, <prefix>currency
-, <prefix>processed_amount
-, <prefix>processed_currency
-, <prefix>effective_date
-, <prefix>payment_status
+, <prefix>external_key
+, <prefix>state_name
+, <prefix>last_success_state_name
 , <prefix>created_by
 , <prefix>created_date
 , <prefix>updated_by
@@ -27,63 +26,28 @@ tableFields(prefix) ::= <<
 
 tableValues() ::= <<
   :accountId
-, :invoiceId
 , :paymentMethodId
-, :amount
-, :currency
-, :processedAmount
-, :processedCurrency
-, :effectiveDate
-, :paymentStatus
+, :externalKey
+, :stateName
+, :lastSuccessStateName
 , :createdBy
 , :createdDate
 , :updatedBy
 , :updatedDate
 >>
 
-tableName() ::= "payments"
-
-historyTableName() ::= "payment_history"
-
-
-getPaymentsForAccount() ::= <<
-select <allTableFields()>
-, record_id as payment_number
-from payments
-where account_id = :accountId
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
-;
->>
-
-getPaymentsForInvoice() ::= <<
-select <allTableFields()>
-, record_id as payment_number
-from payments
-where invoice_id = :invoiceId
-<AND_CHECK_TENANT()>
-<defaultOrderBy()>
-;
->>
-
-
-getLastPaymentForAccountAndPaymentMethod() ::= <<
-select <allTableFields()>
-, record_id as payment_number
-from payments
-where account_id = :accountId
-and payment_method_id = :paymentMethodId
+updatePaymentForNewTransaction() ::= <<
+update <tableName()>
+set updated_by = :updatedBy
+, updated_date = :createdDate
+where id = :id
 <AND_CHECK_TENANT()>
-order by effective_date desc limit 1
 ;
 >>
 
-
-updatePaymentStatus() ::= <<
-update payments
-set payment_status = :paymentStatus
-, processed_amount = :processedAmount
-, processed_currency = :processedCurrency
+updatePaymentStateName() ::= <<
+update <tableName()>
+set state_name = :stateName
 , updated_by = :updatedBy
 , updated_date = :createdDate
 where id = :id
@@ -91,12 +55,10 @@ where id = :id
 ;
 >>
 
-
-updatePaymentForNewAttempt() ::= <<
+updateLastSuccessPaymentStateName() ::= <<
 update <tableName()>
-set amount = :amount
-, effective_date = :effectiveDate
-, payment_method_id= :paymentMethodId
+set state_name = :stateName
+, last_success_state_name = :lastSuccessStateName
 , updated_by = :updatedBy
 , updated_date = :createdDate
 where id = :id
@@ -104,13 +66,29 @@ where id = :id
 ;
 >>
 
+getPaymentByExternalKey() ::= <<
+select
+<allTableFields("")>
+from <tableName()>
+where external_key = :externalKey
+;
+>>
+
+searchQuery(prefix) ::= <<
+     <idField(prefix)> = :searchKey
+  or <prefix>account_id = :searchKey
+  or <prefix>payment_method_id = :searchKey
+  or <prefix>external_key like :likeSearchKey
+  or <prefix>state_name like :likeSearchKey
+>>
+
 getByPluginName() ::= <<
 select
 <allTableFields("t.")>
 from <tableName()> t
 join payment_methods pm on pm.id = t.payment_method_id
 where pm.plugin_name = :pluginName
-order by record_id
+order by t.record_id asc
 limit :offset, :rowCount
 ;
 >>
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/RefundSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/RefundSqlDao.sql.stg
index ff82e80..f8f94a9 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/RefundSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/RefundSqlDao.sql.stg
@@ -21,7 +21,7 @@ tableFields(prefix) ::= <<
 
 tableValues() ::= <<
 :accountId
-, :paymentId
+, :paymentExternalKey
 , :amount
 , :currency
 , :processedAmount
@@ -46,10 +46,10 @@ where id = :id
 ;
 >>
 
-getRefundsForPayment(paymentId)  ::= <<
+getRefundsForPayment(paymentExternalKey)  ::= <<
 select <allTableFields()>
 from <tableName()>
-where payment_id = :paymentId
+where payment_id = :paymentExternalKey
 <AND_CHECK_TENANT()>
 <defaultOrderBy()>
 ;
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
new file mode 100644
index 0000000..932a405
--- /dev/null
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/TransactionSqlDao.sql.stg
@@ -0,0 +1,102 @@
+group TransactionSqlDao: EntitySqlDao;
+
+tableName() ::= "payment_transactions"
+
+historyTableName() ::= "payment_transaction_history"
+
+defaultOrderBy(prefix) ::= <<
+order by <prefix>effective_date ASC, <recordIdField(prefix)> ASC
+>>
+
+tableFields(prefix) ::= <<
+  <prefix>attempt_id
+, <prefix>transaction_external_key
+, <prefix>transaction_type
+, <prefix>effective_date
+, <prefix>transaction_status
+, <prefix>amount
+, <prefix>currency
+, <prefix>processed_amount
+, <prefix>processed_currency
+, <prefix>payment_id
+, <prefix>gateway_error_code
+, <prefix>gateway_error_msg
+, <prefix>created_by
+, <prefix>created_date
+, <prefix>updated_by
+, <prefix>updated_date
+>>
+
+tableValues() ::= <<
+  :attemptId
+, :transactionExternalKey
+, :transactionType
+, :effectiveDate
+, :transactionStatus
+, :amount
+, :currency
+, :processedAmount
+, :processedCurrency
+, :paymentId
+, :gatewayErrorCode
+, :gatewayErrorMsg
+, :createdBy
+, :createdDate
+, :updatedBy
+, :updatedDate
+>>
+
+getPaymentTransactionsByExternalKey() ::= <<
+select
+<allTableFields("")>
+from <tableName()>
+where transaction_external_key = :transactionExternalKey
+<AND_CHECK_TENANT()>
+<defaultOrderBy()>
+;
+>>
+
+
+updateTransactionStatus() ::= <<
+update <tableName()>
+set transaction_status = :transactionStatus
+, processed_amount = :processedAmount
+, processed_currency = :processedCurrency
+, gateway_error_code = :gatewayErrorCode
+, gateway_error_msg = :gatewayErrorMsg
+, updated_by = :updatedBy
+, updated_date = :createdDate
+where id = :id
+<AND_CHECK_TENANT()>
+;
+>>
+
+getByPaymentId() ::= <<
+select <allTableFields()>
+from <tableName()>
+where payment_id = :paymentId
+<AND_CHECK_TENANT()>
+<defaultOrderBy()>
+;
+>>
+
+
+/* Does not include AND_CHECK_TENANT() since this is a global operation */
+getByTransactionStatusPriorDate() ::= <<
+select <allTableFields()>
+from <tableName()>
+where transaction_status = :transactionStatus
+and created_date \< :beforeCreatedDate
+<defaultOrderBy()>
+;
+>>
+
+
+failOldPendingTransactions(ids) ::= <<
+update <tableName()>
+set transaction_status = :newTransactionStatus
+, updated_by = :updatedBy
+, updated_date = :createdDate
+where <idField("")> in (<ids: {id | :id_<i0>}; separator="," >)
+;
+>>
diff --git a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
index cbeda72..a97090b 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
+++ b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
@@ -1,196 +1,229 @@
 /*! SET storage_engine=INNODB */;
 
-DROP TABLE IF EXISTS payments;
-CREATE TABLE payments (
+
+DROP TABLE IF EXISTS payment_attempts;
+CREATE TABLE payment_attempts (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
     account_id char(36) NOT NULL,
-    invoice_id char(36) NOT NULL,
-    payment_method_id char(36) NOT NULL,
+    payment_method_id char(36) DEFAULT NULL,
+    payment_external_key char(128) NOT NULL,
+    transaction_id char(36),
+    transaction_external_key char(128) NOT NULL,
+    transaction_type varchar(32) NOT NULL,
+    state_name varchar(32) NOT NULL,
     amount numeric(15,9),
     currency char(3),
-    processed_amount numeric(15,9),
-    processed_currency char(3),
-    effective_date datetime,
-    payment_status varchar(50),
+    plugin_name varchar(50) NOT NULL,
+    plugin_properties blob(8194),
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    account_record_id int(11) unsigned default null,
-    tenant_record_id int(11) unsigned default null,
+    account_record_id int(11) unsigned DEFAULT NULL,
+    tenant_record_id int(11) unsigned DEFAULT NULL,
     PRIMARY KEY (record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE UNIQUE INDEX payments_id ON payments(id);
-CREATE INDEX payments_inv ON payments(invoice_id);
-CREATE INDEX payments_accnt ON payments(account_id);
-CREATE INDEX payments_tenant_account_record_id ON payments(tenant_record_id, account_record_id);
+CREATE UNIQUE INDEX payment_attempts_id ON payment_attempts(id);
+CREATE INDEX payment_attempts_payment ON payment_attempts(transaction_id);
+CREATE INDEX payment_attempts_payment_key ON payment_attempts(payment_external_key);
+CREATE INDEX payment_attempts_payment_state ON payment_attempts(state_name);
+CREATE INDEX payment_attempts_payment_transaction_key ON payment_attempts(transaction_external_key);
+CREATE INDEX payment_attempts_tenant_account_record_id ON payment_attempts(tenant_record_id, account_record_id);
 
-DROP TABLE IF EXISTS payment_history;
-CREATE TABLE payment_history (
+DROP TABLE IF EXISTS payment_attempt_history;
+CREATE TABLE payment_attempt_history (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
     target_record_id int(11) unsigned NOT NULL,
     account_id char(36) NOT NULL,
-    invoice_id char(36) NOT NULL,
-    payment_method_id char(36) NOT NULL,
+    payment_method_id char(36) DEFAULT NULL,
+    payment_external_key char(128) NOT NULL,
+    transaction_id char(36),
+    transaction_external_key char(128) NOT NULL,
+    transaction_type varchar(32) NOT NULL,
+    state_name varchar(32) NOT NULL,
     amount numeric(15,9),
     currency char(3),
-    processed_amount numeric(15,9),
-    processed_currency char(3),
-    effective_date datetime,
-    payment_status varchar(50),
-    ext_first_payment_ref_id varchar(128),
-    ext_second_payment_ref_id varchar(128),
+    plugin_name varchar(50) NOT NULL,
+    plugin_properties blob(8194),
     change_type char(6) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    account_record_id int(11) unsigned default null,
-    tenant_record_id int(11) unsigned default null,
+    account_record_id int(11) unsigned DEFAULT NULL,
+    tenant_record_id int(11) unsigned DEFAULT NULL,
     PRIMARY KEY(record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE INDEX payment_history_target_record_id ON payment_history(target_record_id);
-CREATE INDEX payment_history_tenant_account_record_id ON payment_history(tenant_record_id, account_record_id);
+CREATE INDEX payment_attempt_history_target_record_id ON payment_attempt_history(target_record_id);
+CREATE INDEX payment_attempt_history_tenant_account_record_id ON payment_attempt_history(tenant_record_id, account_record_id);
 
-DROP TABLE IF EXISTS payment_attempts;
-CREATE TABLE payment_attempts (
+DROP TABLE IF EXISTS payment_methods;
+CREATE TABLE payment_methods (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
-    payment_id char(36) NOT NULL,
-    payment_method_id char(36) NOT NULL,
-    gateway_error_code varchar(32),
-    gateway_error_msg varchar(256),
-    processing_status varchar(50),
-    requested_amount numeric(15,9),
-    requested_currency char(3),
+    external_key varchar(255) NOT NULL,
+    account_id char(36) NOT NULL,
+    plugin_name varchar(50) NOT NULL,
+    is_active bool DEFAULT true,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    account_record_id int(11) unsigned default null,
-    tenant_record_id int(11) unsigned default null,
+    account_record_id int(11) unsigned DEFAULT NULL,
+    tenant_record_id int(11) unsigned DEFAULT NULL,
     PRIMARY KEY (record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE UNIQUE INDEX payment_attempts_id ON payment_attempts(id);
-CREATE INDEX payment_attempts_payment ON payment_attempts(payment_id);
-CREATE INDEX payment_attempts_tenant_account_record_id ON payment_attempts(tenant_record_id, account_record_id);
+CREATE UNIQUE INDEX payment_methods_id ON payment_methods(id);
+CREATE INDEX payment_methods_plugin_name ON payment_methods(plugin_name);
+CREATE INDEX payment_methods_active_accnt ON payment_methods(is_active, account_id);
+CREATE INDEX payment_methods_tenant_account_record_id ON payment_methods(tenant_record_id, account_record_id);
 
-DROP TABLE IF EXISTS payment_attempt_history;
-CREATE TABLE payment_attempt_history (
+DROP TABLE IF EXISTS payment_method_history;
+CREATE TABLE payment_method_history (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
+    external_key varchar(255) NOT NULL,
     target_record_id int(11) unsigned NOT NULL,
-    payment_id char(36) NOT NULL,
-    payment_method_id char(36) NOT NULL,
-    gateway_error_code varchar(32),
-    gateway_error_msg varchar(256),
-    processing_status varchar(50),
-    requested_amount numeric(15,9),
-    requested_currency char(3),
+    account_id char(36) NOT NULL,
+    plugin_name varchar(50) NOT NULL,
+    is_active bool DEFAULT true,
     change_type char(6) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    account_record_id int(11) unsigned default null,
-    tenant_record_id int(11) unsigned default null,
+    account_record_id int(11) unsigned DEFAULT NULL,
+    tenant_record_id int(11) unsigned DEFAULT NULL,
     PRIMARY KEY(record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE INDEX payment_attempt_history_target_record_id ON payment_attempt_history(target_record_id);
-CREATE INDEX payment_attempt_history_tenant_account_record_id ON payment_attempt_history(tenant_record_id, account_record_id);
+CREATE INDEX payment_method_history_target_record_id ON payment_method_history(target_record_id);
+CREATE INDEX payment_method_history_tenant_account_record_id ON payment_method_history(tenant_record_id, account_record_id);
 
-DROP TABLE IF EXISTS payment_methods;
-CREATE TABLE payment_methods (
+
+DROP TABLE IF EXISTS payments;
+CREATE TABLE payments (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
     account_id char(36) NOT NULL,
-    plugin_name varchar(50) DEFAULT NULL,
-    is_active bool DEFAULT true,
+    payment_method_id char(36) NOT NULL,
+    external_key varchar(255) NOT NULL,
+    state_name varchar(64) DEFAULT NULL,
+    last_success_state_name varchar(64) DEFAULT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    account_record_id int(11) unsigned default null,
-    tenant_record_id int(11) unsigned default null,
+    account_record_id int(11) unsigned DEFAULT NULL,
+    tenant_record_id int(11) unsigned DEFAULT NULL,
     PRIMARY KEY (record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE UNIQUE INDEX payment_methods_id ON payment_methods(id);
-CREATE INDEX payment_methods_active_accnt ON payment_methods(is_active, account_id);
-CREATE INDEX payment_methods_tenant_account_record_id ON payment_methods(tenant_record_id, account_record_id);
+CREATE UNIQUE INDEX payments_id ON payments(id);
+CREATE UNIQUE INDEX payments_key ON payments(external_key);
+CREATE INDEX payments_accnt ON payments(account_id);
+CREATE INDEX payments_tenant_account_record_id ON payments(tenant_record_id, account_record_id);
 
-DROP TABLE IF EXISTS payment_method_history;
-CREATE TABLE payment_method_history (
+
+DROP TABLE IF EXISTS payment_history;
+CREATE TABLE payment_history (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
     target_record_id int(11) unsigned NOT NULL,
     account_id char(36) NOT NULL,
-    plugin_name varchar(50) DEFAULT NULL,
-    is_active bool DEFAULT true,
+    payment_method_id char(36) NOT NULL,
+    external_key varchar(255) NOT NULL,
+    state_name varchar(64) DEFAULT NULL,
+    last_success_state_name varchar(64) DEFAULT NULL,
     change_type char(6) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    account_record_id int(11) unsigned default null,
-    tenant_record_id int(11) unsigned default null,
+    account_record_id int(11) unsigned DEFAULT NULL,
+    tenant_record_id int(11) unsigned DEFAULT NULL,
     PRIMARY KEY(record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE INDEX payment_method_history_target_record_id ON payment_method_history(target_record_id);
-CREATE INDEX payment_method_history_tenant_account_record_id ON payment_method_history(tenant_record_id, account_record_id);
+CREATE INDEX payment_history_target_record_id ON payment_history(target_record_id);
+CREATE INDEX payment_history_tenant_account_record_id ON payment_history(tenant_record_id, account_record_id);
+
 
-DROP TABLE IF EXISTS refunds;
-CREATE TABLE refunds (
+DROP TABLE IF EXISTS payment_transactions;
+CREATE TABLE payment_transactions (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
-    account_id char(36) NOT NULL,
-    payment_id char(36) NOT NULL,
+    attempt_id char(36) DEFAULT NULL,
+    transaction_external_key varchar(255) NOT NULL,
+    transaction_type varchar(32) NOT NULL,
+    effective_date datetime NOT NULL,
+    transaction_status varchar(50) NOT NULL,
     amount numeric(15,9),
     currency char(3),
     processed_amount numeric(15,9),
     processed_currency char(3),
-    is_adjusted tinyint(1),
-    refund_status varchar(50),
+    payment_id char(36) NOT NULL,
+    gateway_error_code varchar(32),
+    gateway_error_msg varchar(256),
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    account_record_id int(11) unsigned default null,
-    tenant_record_id int(11) unsigned default null,
+    account_record_id int(11) unsigned DEFAULT NULL,
+    tenant_record_id int(11) unsigned DEFAULT NULL,
     PRIMARY KEY (record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE UNIQUE INDEX refunds_id ON refunds(id);
-CREATE INDEX refunds_pay ON refunds(payment_id);
-CREATE INDEX refunds_accnt ON refunds(account_id);
-CREATE INDEX refunds_tenant_account_record_id ON refunds(tenant_record_id, account_record_id);
+CREATE UNIQUE INDEX transactions_id ON payment_transactions(id);
+CREATE INDEX transactions_payment_id ON payment_transactions(payment_id);
+CREATE INDEX transactions_key ON payment_transactions(transaction_external_key);
+CREATE INDEX transactions_status ON payment_transactions(transaction_status);
+CREATE INDEX transactions_tenant_account_record_id ON payment_transactions(tenant_record_id, account_record_id);
 
-DROP TABLE IF EXISTS refund_history;
-CREATE TABLE refund_history (
+DROP TABLE IF EXISTS payment_transaction_history;
+CREATE TABLE payment_transaction_history (
     record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
     id char(36) NOT NULL,
+    attempt_id char(36) DEFAULT NULL,
+    transaction_external_key varchar(255) NOT NULL,
     target_record_id int(11) unsigned NOT NULL,
-    account_id char(36) NOT NULL,
-    payment_id char(36) NOT NULL,
+    transaction_type varchar(32) NOT NULL,
+    effective_date datetime NOT NULL,
+    transaction_status varchar(50) NOT NULL,
     amount numeric(15,9),
     currency char(3),
     processed_amount numeric(15,9),
     processed_currency char(3),
-    is_adjusted tinyint(1),
-    refund_status varchar(50),
+    payment_id char(36) NOT NULL,
+    gateway_error_code varchar(32),
+    gateway_error_msg varchar(256),
     change_type char(6) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     updated_by varchar(50) NOT NULL,
     updated_date datetime NOT NULL,
-    account_record_id int(11) unsigned default null,
-    tenant_record_id int(11) unsigned default null,
-    PRIMARY KEY(record_id)
+    account_record_id int(11) unsigned DEFAULT NULL,
+    tenant_record_id int(11) unsigned DEFAULT NULL,
+    PRIMARY KEY (record_id)
 ) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE INDEX refund_history_target_record_id ON refund_history(target_record_id);
-CREATE INDEX refund_history_tenant_account_record_id ON refund_history(tenant_record_id, account_record_id);
-
-
-
+CREATE INDEX transaction_history_target_record_id ON payment_transaction_history(target_record_id);
+CREATE INDEX transaction_history_tenant_account_record_id ON payment_transaction_history(tenant_record_id, account_record_id);
 
 
+/*  PaymentControlPlugin lives  here until this becomes a first class citizen plugin */
+DROP TABLE IF EXISTS _invoice_payment_control_plugin_auto_pay_off;
+CREATE TABLE _invoice_payment_control_plugin_auto_pay_off (
+    record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+    attempt_id char(36) NOT NULL,
+    payment_external_key varchar(255) NOT NULL,
+    transaction_external_key varchar(255) NOT NULL,
+    account_id char(36) NOT NULL,
+    plugin_name varchar(50) NOT NULL,
+    payment_id char(36),
+    payment_method_id char(36) NOT NULL,
+    amount numeric(15,9),
+    currency char(3),
+    is_active bool DEFAULT true,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    PRIMARY KEY (record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX _invoice_payment_control_plugin_auto_pay_off_account ON _invoice_payment_control_plugin_auto_pay_off(account_id);
diff --git a/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
new file mode 100644
index 0000000..f4aeef9
--- /dev/null
+++ b/payment/src/main/resources/org/killbill/billing/payment/PaymentStates.xml
@@ -0,0 +1,373 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2014 Groupon, Inc
+  ~
+  ~ Groupon 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.
+  -->
+
+<stateMachineConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                    xsi:noNamespaceSchemaLocation="StateMachineConfig.xsd">
+
+    <stateMachines>
+        <stateMachine name="BIG_BANG">
+        <states>
+            <state name="BIG_BANG_INIT"/>
+        </states>
+            <transitions>
+                <transition>
+                    <initialState>BIG_BANG_INIT</initialState>
+                    <operation>OP_DUMMY</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>BIG_BANG_INIT</finalState>
+                </transition>
+            </transitions>
+            <operations>
+                <operation name="OP_DUMMY"/>
+            </operations>
+        </stateMachine>
+        <stateMachine name="AUTHORIZE">
+            <states>
+                <state name="AUTH_INIT"/>
+                <state name="AUTH_PENDING"/>
+                <state name="AUTH_SUCCESS"/>
+                <state name="AUTH_FAILED"/>
+                <state name="AUTH_ERRORED"/>
+            </states>
+            <transitions>
+                <transition>
+                    <initialState>AUTH_INIT</initialState>
+                    <operation>OP_AUTHORIZE</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>AUTH_SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>AUTH_INIT</initialState>
+                    <operation>OP_AUTHORIZE</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>AUTH_FAILED</finalState>
+                </transition>
+                <transition>
+                    <initialState>AUTH_INIT</initialState>
+                    <operation>OP_AUTHORIZE</operation>
+                    <operationResult>PENDING</operationResult>
+                    <finalState>AUTH_PENDING</finalState>
+                </transition>
+                <transition>
+                    <initialState>AUTH_PENDING</initialState>
+                    <operation>OP_AUTHORIZE</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>AUTH_SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>AUTH_PENDING</initialState>
+                    <operation>OP_AUTHORIZE</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>AUTH_FAILED</finalState>
+                </transition>
+                <transition>
+                    <initialState>AUTH_PENDING</initialState>
+                    <operation>OP_AUTHORIZE</operation>
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>AUTH_ERRORED</finalState>
+                </transition>
+                <transition>
+                    <initialState>AUTH_INIT</initialState>
+                    <operation>OP_AUTHORIZE</operation>
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>AUTH_ERRORED</finalState>
+                </transition>
+            </transitions>
+            <operations>
+                <operation name="OP_AUTHORIZE"/>
+            </operations>
+        </stateMachine>
+
+        <stateMachine name="CAPTURE">
+            <states>
+                <state name="CAPTURE_INIT"/>
+                <state name="CAPTURE_SUCCESS"/>
+                <state name="CAPTURE_FAILED"/>
+                <state name="CAPTURE_ERRORED"/>
+            </states>
+            <transitions>
+                <transition>
+                    <initialState>CAPTURE_INIT</initialState>
+                    <operation>OP_CAPTURE</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>CAPTURE_SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>CAPTURE_INIT</initialState>
+                    <operation>OP_CAPTURE</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>CAPTURE_FAILED</finalState>
+                </transition>
+                <transition>
+                    <initialState>CAPTURE_INIT</initialState>
+                    <operation>OP_CAPTURE</operation>
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>CAPTURE_ERRORED</finalState>
+                </transition>
+            </transitions>
+            <operations>
+                <operation name="OP_CAPTURE"/>
+            </operations>
+        </stateMachine>
+
+        <stateMachine name="PURCHASE">
+            <states>
+                <state name="PURCHASE_INIT"/>
+                <state name="PURCHASE_SUCCESS"/>
+                <state name="PURCHASE_FAILED"/>
+                <state name="PURCHASE_ERRORED"/>
+            </states>
+            <transitions>
+                <transition>
+                    <initialState>PURCHASE_INIT</initialState>
+                    <operation>OP_PURCHASE</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>PURCHASE_SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>PURCHASE_INIT</initialState>
+                    <operation>OP_PURCHASE</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>PURCHASE_FAILED</finalState>
+                </transition>
+                <transition>
+                    <initialState>PURCHASE_INIT</initialState>
+                    <operation>OP_PURCHASE</operation>
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>PURCHASE_ERRORED</finalState>
+                </transition>
+            </transitions>
+            <operations>
+                <operation name="OP_PURCHASE"/>
+            </operations>
+        </stateMachine>
+
+        <stateMachine name="REFUND">
+            <states>
+                <state name="REFUND_INIT"/>
+                <state name="REFUND_SUCCESS"/>
+                <state name="REFUND_FAILED"/>
+                <state name="REFUND_ERRORED"/>
+            </states>
+            <transitions>
+                <transition>
+                    <initialState>REFUND_INIT</initialState>
+                    <operation>OP_REFUND</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>REFUND_SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>REFUND_INIT</initialState>
+                    <operation>OP_REFUND</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>REFUND_FAILED</finalState>
+                </transition>
+                <transition>
+                    <initialState>REFUND_INIT</initialState>
+                    <operation>OP_REFUND</operation>
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>REFUND_ERRORED</finalState>
+                </transition>
+            </transitions>
+            <operations>
+                <operation name="OP_REFUND"/>
+            </operations>
+        </stateMachine>
+
+        <stateMachine name="CREDIT">
+            <states>
+                <state name="CREDIT_INIT"/>
+                <state name="CREDIT_SUCCESS"/>
+                <state name="CREDIT_FAILED"/>
+                <state name="CREDIT_ERRORED"/>
+            </states>
+            <transitions>
+                <transition>
+                    <initialState>CREDIT_INIT</initialState>
+                    <operation>OP_CREDIT</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>CREDIT_SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>CREDIT_INIT</initialState>
+                    <operation>OP_CREDIT</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>CREDIT_FAILED</finalState>
+                </transition>
+                <transition>
+                    <initialState>CREDIT_INIT</initialState>
+                    <operation>OP_CREDIT</operation>
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>CREDIT_ERRORED</finalState>
+                </transition>
+            </transitions>
+            <operations>
+                <operation name="OP_CREDIT"/>
+            </operations>
+        </stateMachine>
+
+        <stateMachine name="VOID">
+            <states>
+                <state name="VOID_INIT"/>
+                <state name="VOID_SUCCESS"/>
+                <state name="VOID_FAILED"/>
+                <state name="VOID_ERRORED"/>
+            </states>
+            <transitions>
+                <transition>
+                    <initialState>VOID_INIT</initialState>
+                    <operation>OP_VOID</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>VOID_SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>VOID_INIT</initialState>
+                    <operation>OP_VOID</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>VOID_FAILED</finalState>
+                </transition>
+                <transition>
+                    <initialState>VOID_INIT</initialState>
+                    <operation>OP_VOID</operation>
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>VOID_ERRORED</finalState>
+                </transition>
+            </transitions>
+            <operations>
+                <operation name="OP_VOID"/>
+            </operations>
+        </stateMachine>
+        <stateMachine name="CHARGEBACK">
+        <states>
+            <state name="CHARGEBACK_INIT"/>
+            <state name="CHARGEBACK_SUCCESS"/>
+            <state name="CHARGEBACK_FAILED"/>
+            <state name="CHARGEBACK_ERRORED"/>
+        </states>
+            <transitions>
+                <transition>
+                    <initialState>CHARGEBACK_INIT</initialState>
+                    <operation>OP_CHARGEBACK</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>CHARGEBACK_SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>CHARGEBACK_INIT</initialState>
+                    <operation>OP_CHARGEBACK</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>CHARGEBACK_FAILED</finalState>
+                </transition>
+                <transition>
+                    <initialState>CHARGEBACK_INIT</initialState>
+                    <operation>OP_CHARGEBACK</operation>
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>CHARGEBACK_ERRORED</finalState>
+                </transition>
+            </transitions>
+            <operations>
+                <operation name="OP_CHARGEBACK"/>
+            </operations>
+        </stateMachine>
+    </stateMachines>
+
+    <linkStateMachines>
+        <linkStateMachine>
+            <initialStateMachine>BIG_BANG</initialStateMachine>
+            <initialState>BIG_BANG_INIT</initialState>
+            <finalStateMachine>AUTHORIZE</finalStateMachine>
+            <finalState>AUTH_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>BIG_BANG</initialStateMachine>
+            <initialState>BIG_BANG_INIT</initialState>
+            <finalStateMachine>PURCHASE</finalStateMachine>
+            <finalState>PURCHASE_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>BIG_BANG</initialStateMachine>
+            <initialState>BIG_BANG_INIT</initialState>
+            <finalStateMachine>CREDIT</finalStateMachine>
+            <finalState>CREDIT_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>AUTHORIZE</initialStateMachine>
+            <initialState>AUTH_SUCCESS</initialState>
+            <finalStateMachine>AUTHORIZE</finalStateMachine>
+            <finalState>AUTH_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>AUTHORIZE</initialStateMachine>
+            <initialState>AUTH_SUCCESS</initialState>
+            <finalStateMachine>CAPTURE</finalStateMachine>
+            <finalState>CAPTURE_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>AUTHORIZE</initialStateMachine>
+            <initialState>AUTH_SUCCESS</initialState>
+            <finalStateMachine>VOID</finalStateMachine>
+            <finalState>VOID_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>CAPTURE</initialStateMachine>
+            <initialState>CAPTURE_SUCCESS</initialState>
+            <finalStateMachine>REFUND</finalStateMachine>
+            <finalState>REFUND_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>CAPTURE</initialStateMachine>
+            <initialState>CAPTURE_SUCCESS</initialState>
+            <finalStateMachine>CAPTURE</finalStateMachine>
+            <finalState>CAPTURE_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>CAPTURE</initialStateMachine>
+            <initialState>CAPTURE_SUCCESS</initialState>
+            <finalStateMachine>CHARGEBACK</finalStateMachine>
+            <finalState>CHARGEBACK_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>REFUND</initialStateMachine>
+            <initialState>REFUND_SUCCESS</initialState>
+            <finalStateMachine>REFUND</finalStateMachine>
+            <finalState>REFUND_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>REFUND</initialStateMachine>
+            <initialState>REFUND_SUCCESS</initialState>
+            <finalStateMachine>CHARGEBACK</finalStateMachine>
+            <finalState>CHARGEBACK_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>PURCHASE</initialStateMachine>
+            <initialState>PURCHASE_SUCCESS</initialState>
+            <finalStateMachine>REFUND</finalStateMachine>
+            <finalState>REFUND_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>PURCHASE</initialStateMachine>
+            <initialState>PURCHASE_SUCCESS</initialState>
+            <finalStateMachine>CHARGEBACK</finalStateMachine>
+            <finalState>CHARGEBACK_INIT</finalState>
+        </linkStateMachine>
+        <linkStateMachine>
+            <initialStateMachine>CHARGEBACK</initialStateMachine>
+            <initialState>CHARGEBACK_SUCCESS</initialState>
+            <finalStateMachine>CHARGEBACK</finalStateMachine>
+            <finalState>CHARGEBACK_INIT</finalState>
+        </linkStateMachine>
+    </linkStateMachines>
+</stateMachineConfig>
diff --git a/payment/src/main/resources/org/killbill/billing/payment/retry/RetryStates.xml b/payment/src/main/resources/org/killbill/billing/payment/retry/RetryStates.xml
new file mode 100644
index 0000000..e832f35
--- /dev/null
+++ b/payment/src/main/resources/org/killbill/billing/payment/retry/RetryStates.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2014 Groupon, Inc
+  ~
+  ~ Groupon 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.
+  -->
+
+<stateMachineConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                    xsi:noNamespaceSchemaLocation="StateMachineConfig.xsd">
+
+    <stateMachines>
+        <stateMachine name="PAYMENT_RETRY">
+            <states>
+                <state name="INIT"/>
+                <state name="SUCCESS"/>
+                <state name="RETRIED"/>
+                <state name="ABORTED"/>
+            </states>
+            <transitions>
+                <transition>
+                    <initialState>INIT</initialState>
+                    <operation>OP_RETRY</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>INIT</initialState>
+                    <operation>OP_RETRY</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>RETRIED</finalState>
+                </transition>
+                <transition>
+                    <initialState>INIT</initialState>
+                    <operation>OP_RETRY</operation>
+                    <!-- We are using EXCEPTION operation result to get out of the RETRIED state and transition to  ABORTED -->
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>ABORTED</finalState>
+                </transition>
+                <transition>
+                    <initialState>RETRIED</initialState>
+                    <operation>OP_RETRY</operation>
+                    <operationResult>SUCCESS</operationResult>
+                    <finalState>SUCCESS</finalState>
+                </transition>
+                <transition>
+                    <initialState>RETRIED</initialState>
+                    <operation>OP_RETRY</operation>
+                    <operationResult>FAILURE</operationResult>
+                    <finalState>RETRIED</finalState>
+                </transition>
+                <transition>
+                    <initialState>RETRIED</initialState>
+                    <operation>OP_RETRY</operation>
+                    <!-- We are using EXCEPTION operation result to get out of the RETRIED state and transition to  ABORTED -->
+                    <operationResult>EXCEPTION</operationResult>
+                    <finalState>ABORTED</finalState>
+                </transition>
+            </transitions>
+            <operations>
+                <operation name="OP_RETRY"/>
+            </operations>
+        </stateMachine>
+    </stateMachines>
+
+    <linkStateMachines>
+        <linkStateMachine>
+            <initialStateMachine>PAYMENT_RETRY</initialStateMachine>
+            <initialState>ABORTED</initialState>
+            <finalStateMachine>PAYMENT_RETRY</finalStateMachine>
+            <finalState>INIT</finalState>
+        </linkStateMachine>
+    </linkStateMachines>
+
+</stateMachineConfig>
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestEventJson.java b/payment/src/test/java/org/killbill/billing/payment/api/TestEventJson.java
index 64703fe..809199f 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestEventJson.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestEventJson.java
@@ -20,6 +20,7 @@ import java.math.BigDecimal;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.Currency;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -33,7 +34,7 @@ public class TestEventJson extends PaymentTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testPaymentErrorEvent() throws Exception {
-        final PaymentErrorInternalEvent e = new DefaultPaymentErrorEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), "no message", 1L, 2L, UUID.randomUUID());
+        final PaymentErrorInternalEvent e = new DefaultPaymentErrorEvent(UUID.randomUUID(), UUID.randomUUID(), TransactionType.PURCHASE, "no message", 1L, 2L, UUID.randomUUID());
         final String json = mapper.writeValueAsString(e);
 
         final Class<?> claz = Class.forName(DefaultPaymentErrorEvent.class.getName());
@@ -43,8 +44,8 @@ public class TestEventJson extends PaymentTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testPaymentInfoEvent() throws Exception {
-        final PaymentInfoInternalEvent e = new DefaultPaymentInfoEvent(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), new BigDecimal(12.9), new Integer(13), PaymentStatus.SUCCESS,
-                                                                       new DateTime(), 1L, 2L, UUID.randomUUID());
+        final PaymentInfoInternalEvent e = new DefaultPaymentInfoEvent(UUID.randomUUID(), UUID.randomUUID(), new BigDecimal(12.9), Currency.EUR, TransactionStatus.SUCCESS,
+                                                                       TransactionType.PURCHASE, new DateTime(), 1L, 2L, UUID.randomUUID());
         final String json = mapper.writeValueAsString(e);
 
         final Class<?> clazz = Class.forName(DefaultPaymentInfoEvent.class.getName());
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index 25bd6dd..ce075e3 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,45 +19,215 @@
 package org.killbill.billing.payment.api;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
-import org.testng.Assert;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
-import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.payment.MockRecurringInvoiceItem;
 import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.dao.PaymentSqlDao;
+import org.killbill.billing.retry.plugin.api.PaymentControlApiException;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
 
 public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
+    final PaymentOptions INVOICE_PAYMENT = new PaymentOptions() {
+        @Override
+        public boolean isExternalPayment() {
+            return false;
+        }
+
+        @Override
+        public String getPaymentControlPluginName() {
+            return InvoicePaymentControlPluginApi.PLUGIN_NAME;
+        }
+    };
 
     private Account account;
 
     @BeforeClass(groups = "slow")
-    public void beforeClass() throws Exception {
-        super.beforeClass();
-        account = testHelper.createTestAccount("bobo@gmail.com", false);
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        account = testHelper.createTestAccount("bobo@gmail.com", true);
+    }
+
+    @Test(groups = "slow")
+    public void testCreateSuccessPurchase() throws PaymentApiException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+
+        final String paymentExternalKey = "bwwrr";
+        final String transactionExternalKey = "krapaut";
+
+        final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+                                                          ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment.getExternalKey(), paymentExternalKey);
+        assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment.getAccountId(), account.getId());
+        assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getPurchasedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
+        assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.AED);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+        assertNull(payment.getTransactions().get(0).getGatewayErrorMsg());
+        assertNull(payment.getTransactions().get(0).getGatewayErrorCode());
     }
 
+    @Test(groups = "slow")
+    public void testCreateSuccessAuthCapture() throws PaymentApiException {
+
+        final BigDecimal authAmount = BigDecimal.TEN;
+        final BigDecimal captureAmount = BigDecimal.ONE;
+
+        final String paymentExternalKey = "bouzou";
+        final String transactionExternalKey = "kaput";
+        final String transactionExternalKey2 = "kapu2t";
+
+        final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+                                                               ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment.getExternalKey(), paymentExternalKey);
+        assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment.getAccountId(), account.getId());
+        assertEquals(payment.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
+        assertEquals(payment.getTransactions().get(0).getAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.AED);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.AUTHORIZE);
+        assertNull(payment.getTransactions().get(0).getGatewayErrorMsg());
+        assertNull(payment.getTransactions().get(0).getGatewayErrorCode());
+
+        final Payment payment2 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.AED, transactionExternalKey2,
+                                                          ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment2.getExternalKey(), paymentExternalKey);
+        assertEquals(payment2.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment2.getAccountId(), account.getId());
+        assertEquals(payment2.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment2.getCapturedAmount().compareTo(captureAmount), 0);
+        assertEquals(payment2.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getCurrency(), Currency.AED);
+
+        assertEquals(payment2.getTransactions().size(), 2);
+        assertEquals(payment2.getTransactions().get(1).getExternalKey(), transactionExternalKey2);
+        assertEquals(payment2.getTransactions().get(1).getPaymentId(), payment.getId());
+        assertEquals(payment2.getTransactions().get(1).getAmount().compareTo(captureAmount), 0);
+        assertEquals(payment2.getTransactions().get(1).getCurrency(), Currency.AED);
+        assertEquals(payment2.getTransactions().get(1).getProcessedAmount().compareTo(captureAmount), 0);
+        assertEquals(payment2.getTransactions().get(1).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment2.getTransactions().get(1).getTransactionType(), TransactionType.CAPTURE);
+        assertNull(payment2.getTransactions().get(1).getGatewayErrorMsg());
+        assertNull(payment2.getTransactions().get(1).getGatewayErrorCode());
+    }
 
     @Test(groups = "slow")
-    public void testCreatePaymentWithNoDefaultPaymentMethod() throws InvoiceApiException, EventBusException, PaymentApiException {
+    public void testCreateSuccessAuthMultipleCaptureAndRefund() throws PaymentApiException {
 
+        final BigDecimal authAmount = BigDecimal.TEN;
+        final BigDecimal captureAmount = BigDecimal.ONE;
 
-        final LocalDate now = clock.getUTCToday();
-        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD, callContext);
+        final String paymentExternalKey = "courou";
+        final String transactionExternalKey = "sioux";
+        final String transactionExternalKey2 = "sioux2";
+        final String transactionExternalKey3 = "sioux3";
+        final String transactionExternalKey4 = "sioux4";
+
+        final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                                     ImmutableList.<PluginProperty>of(), callContext);
+
+        paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.USD, transactionExternalKey2,
+                                 ImmutableList.<PluginProperty>of(), callContext);
+
+        final Payment payment3 = paymentApi.createCapture(account, payment.getId(), captureAmount, Currency.USD, transactionExternalKey3,
+                                                                ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment3.getExternalKey(), paymentExternalKey);
+        assertEquals(payment3.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment3.getAccountId(), account.getId());
+        assertEquals(payment3.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment3.getCapturedAmount().compareTo(captureAmount.add(captureAmount)), 0);
+        assertEquals(payment3.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment3.getCurrency(), Currency.USD);
+        assertEquals(payment3.getTransactions().size(), 3);
+
+        final Payment payment4 = paymentApi.createRefund(account, payment3.getId(), payment3.getCapturedAmount(), Currency.USD, transactionExternalKey4, ImmutableList.<PluginProperty>of(), callContext);
+        assertEquals(payment4.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment4.getCapturedAmount().compareTo(captureAmount.add(captureAmount)), 0);
+        assertEquals(payment4.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment4.getRefundedAmount().compareTo(payment3.getCapturedAmount()), 0);
+        assertEquals(payment4.getTransactions().size(), 4);
+
+        assertEquals(payment4.getTransactions().get(3).getExternalKey(), transactionExternalKey4);
+        assertEquals(payment4.getTransactions().get(3).getPaymentId(), payment.getId());
+        assertEquals(payment4.getTransactions().get(3).getAmount().compareTo(payment3.getCapturedAmount()), 0);
+        assertEquals(payment4.getTransactions().get(3).getCurrency(), Currency.USD);
+        assertEquals(payment4.getTransactions().get(3).getProcessedAmount().compareTo(payment3.getCapturedAmount()), 0);
+        assertEquals(payment4.getTransactions().get(3).getProcessedCurrency(), Currency.USD);
+        assertEquals(payment4.getTransactions().get(3).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment4.getTransactions().get(3).getTransactionType(), TransactionType.REFUND);
+        assertNull(payment4.getTransactions().get(3).getGatewayErrorMsg());
+        assertNull(payment4.getTransactions().get(3).getGatewayErrorCode());
+    }
+
+
+    @Test(groups = "slow")
+    public void testCreateSuccessPurchaseWithPaymentControl() throws PaymentApiException, InvoiceApiException, EventBusException {
 
+        final BigDecimal requestedAmount = BigDecimal.TEN;
         final UUID subscriptionId = UUID.randomUUID();
         final UUID bundleId = UUID.randomUUID();
-        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final LocalDate now = clock.getUTCToday();
+
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+
+        final String paymentExternalKey = invoice.getId().toString();
+        final String transactionExternalKey = "brrrrrr";
 
         invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
                                                             subscriptionId,
@@ -67,16 +239,358 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
                                                             new BigDecimal("1.0"),
                                                             Currency.USD));
 
+        final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+
+        assertEquals(payment.getExternalKey(), paymentExternalKey);
+        assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment.getAccountId(), account.getId());
+        assertEquals(payment.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getPurchasedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCurrency(), Currency.USD);
+
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
+        assertEquals(payment.getTransactions().get(0).getAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.USD);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.USD);
+
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+        assertNull(payment.getTransactions().get(0).getGatewayErrorMsg());
+        assertNull(payment.getTransactions().get(0).getGatewayErrorCode());
+
+        // Not stricly an API test but interesting to verify that we indeed went through the attempt logic
+        final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(payment.getExternalKey(), internalCallContext);
+        assertEquals(attempts.size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testCreateAbortedPurchaseWithPaymentControl() throws InvoiceApiException, EventBusException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final LocalDate now = clock.getUTCToday();
+
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+
+        final String paymentExternalKey = invoice.getId().toString();
+        final String transactionExternalKey = "brrrrrr";
+
+        invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
+                                                            subscriptionId,
+                                                            bundleId,
+                                                            "test plan", "test phase", null,
+                                                            now,
+                                                            now.plusMonths(1),
+                                                            BigDecimal.ONE,
+                                                            new BigDecimal("1.0"),
+                                                            Currency.USD));
+
+        try {
+            paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                        createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+            Assert.fail("Unexpected success");
+        } catch (PaymentApiException e) {
+            assertTrue(e.getCause() instanceof PaymentControlApiException);
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testCreateSuccessRefundWithPaymentControl() throws PaymentApiException, InvoiceApiException, EventBusException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final LocalDate now = clock.getUTCToday();
+
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+
+        final String paymentExternalKey = invoice.getId().toString();
+        final String transactionExternalKey = "sacrebleu";
+        final String transactionExternalKey2 = "maisenfin";
+
+        final InvoiceItem invoiceItem = new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
+                                                                     subscriptionId,
+                                                                     bundleId,
+                                                                     "test plan", "test phase", null,
+                                                                     now,
+                                                                     now.plusMonths(1),
+                                                                     requestedAmount,
+                                                                     new BigDecimal("1.0"),
+                                                                     Currency.USD);
+        invoice.addInvoiceItem(invoiceItem);
+
+        final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+
+        final List<PluginProperty> refundProperties = ImmutableList.<PluginProperty>of();
+        final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), requestedAmount, Currency.USD, transactionExternalKey2,
+                                                                                 refundProperties, INVOICE_PAYMENT, callContext);
+
+        assertEquals(payment2.getTransactions().size(), 2);
+        assertEquals(payment2.getExternalKey(), paymentExternalKey);
+        assertEquals(payment2.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment2.getAccountId(), account.getId());
+        assertEquals(payment2.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getPurchasedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment2.getRefundedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment2.getCurrency(), Currency.USD);
+    }
+
+    @Test(groups = "slow")
+    public void testCreateAbortedRefundWithPaymentControl() throws PaymentApiException, InvoiceApiException, EventBusException {
+
+        final BigDecimal requestedAmount = BigDecimal.ONE;
+        final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final LocalDate now = clock.getUTCToday();
+
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+
+        final String paymentExternalKey = invoice.getId().toString();
+        final String transactionExternalKey = "payment";
+        final String transactionExternalKey2 = "refund";
+
+        final InvoiceItem invoiceItem = new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
+                                                                     subscriptionId,
+                                                                     bundleId,
+                                                                     "test plan", "test phase", null,
+                                                                     now,
+                                                                     now.plusMonths(1),
+                                                                     requestedAmount,
+                                                                     new BigDecimal("1.0"),
+                                                                     Currency.USD);
+        invoice.addInvoiceItem(invoiceItem);
+
+        final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+
+        final List<PluginProperty> refundProperties = ImmutableList.<PluginProperty>of();
+
+        try {
+            paymentApi.createRefundWithPaymentControl(account, payment.getId(), BigDecimal.TEN, Currency.USD, transactionExternalKey2,
+                                                      refundProperties, INVOICE_PAYMENT, callContext);
+        } catch (PaymentApiException e) {
+            assertTrue(e.getCause() instanceof PaymentControlApiException);
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testCreateSuccessRefundPaymentControlWithItemAdjustments() throws PaymentApiException, InvoiceApiException, EventBusException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final LocalDate now = clock.getUTCToday();
+
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+
+        final String paymentExternalKey = invoice.getId().toString();
+        final String transactionExternalKey = "hopla";
+        final String transactionExternalKey2 = "chouette";
+
+        final InvoiceItem invoiceItem = new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
+                                                                     subscriptionId,
+                                                                     bundleId,
+                                                                     "test plan", "test phase", null,
+                                                                     now,
+                                                                     now.plusMonths(1),
+                                                                     requestedAmount,
+                                                                     new BigDecimal("1.0"),
+                                                                     Currency.USD);
+        invoice.addInvoiceItem(invoiceItem);
+
+        final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+
+        final List<PluginProperty> refundProperties = new ArrayList<PluginProperty>();
+        final HashMap<UUID, BigDecimal> uuidBigDecimalHashMap = new HashMap<UUID, BigDecimal>();
+        uuidBigDecimalHashMap.put(invoiceItem.getId(), null);
+        final PluginProperty refundIdsProp = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY, uuidBigDecimalHashMap, false);
+        refundProperties.add(refundIdsProp);
+
+        final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), null, Currency.USD, transactionExternalKey2,
+                                                                                 refundProperties, INVOICE_PAYMENT, callContext);
+
+        assertEquals(payment2.getTransactions().size(), 2);
+        assertEquals(payment2.getExternalKey(), paymentExternalKey);
+        assertEquals(payment2.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment2.getAccountId(), account.getId());
+        assertEquals(payment2.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getPurchasedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment2.getRefundedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment2.getCurrency(), Currency.USD);
+    }
+
+    @Test(groups = "slow")
+    public void testCreateChargeback() throws PaymentApiException {
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+
+        final String paymentExternalKey = "couic";
+        final String transactionExternalKey = "couac";
+        final String transactionExternalKey2 = "couyc";
+
+        final Payment payment = paymentApi.createPurchase(account, account.getPaymentMethodId(), null, requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+                                                                ImmutableList.<PluginProperty>of(), callContext);
+
+        paymentApi.createChargeback(account, payment.getId(), requestedAmount, Currency.AED, transactionExternalKey2,  callContext);
+        final Payment payment2 = paymentApi.getPayment(payment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+
+        assertEquals(payment2.getExternalKey(), paymentExternalKey);
+        assertEquals(payment2.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment2.getAccountId(), account.getId());
+        assertEquals(payment2.getAuthAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getPurchasedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment2.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment2.getCurrency(), Currency.AED);
+
+        assertEquals(payment2.getTransactions().size(), 2);
+        assertEquals(payment2.getTransactions().get(1).getExternalKey(), transactionExternalKey2);
+        assertEquals(payment2.getTransactions().get(1).getPaymentId(), payment.getId());
+        assertEquals(payment2.getTransactions().get(1).getAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment2.getTransactions().get(1).getCurrency(), Currency.AED);
+
+        assertEquals(payment2.getTransactions().get(1).getProcessedAmount().compareTo(requestedAmount), 0);
+        assertEquals(payment2.getTransactions().get(1).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment2.getTransactions().get(1).getTransactionType(), TransactionType.CHARGEBACK);
+        assertNull(payment2.getTransactions().get(1).getGatewayErrorMsg());
+        assertNull(payment2.getTransactions().get(1).getGatewayErrorCode());
+
+        // Attempt to any other operation afterwards, that should fail
+        try {
+            paymentApi.createPurchase(account, account.getPaymentMethodId(), payment.getId(), requestedAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+                                      ImmutableList.<PluginProperty>of(), callContext);
+            Assert.fail("Purchase not succeed after a chargeback");
+        } catch (PaymentApiException e) {
+            Assert.assertTrue(true);
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testNotifyPendingTransactionOfStateChanged() throws PaymentApiException {
+
+        final BigDecimal authAmount = BigDecimal.TEN;
+
+        final String paymentExternalKey = "rouge";
+        final String transactionExternalKey = "vert";
+
+        final Payment initialPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, authAmount, Currency.AED, paymentExternalKey, transactionExternalKey,
+                                                               ImmutableList.<PluginProperty>of(), callContext);
+
+        // Update the payment/transaction by hand to simulate a PENDING state.
+        final PaymentTransaction paymentTransaction = initialPayment.getTransactions().get(0);
+        paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), initialPayment.getId(), TransactionType.AUTHORIZE, "AUTH_PENDING", "AUTH_PENDING",
+                                                           paymentTransaction.getId(), TransactionStatus.PENDING, paymentTransaction.getProcessedAmount(), paymentTransaction.getProcessedCurrency(),
+                                                           null, null, internalCallContext);
+
+
+        final Payment payment = paymentApi.notifyPendingTransactionOfStateChanged(account, paymentTransaction.getId(), true, callContext);
+
+        assertEquals(payment.getExternalKey(), paymentExternalKey);
+        assertEquals(payment.getPaymentMethodId(), account.getPaymentMethodId());
+        assertEquals(payment.getAccountId(), account.getId());
+        assertEquals(payment.getAuthAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getCapturedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getPurchasedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getRefundedAmount().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(payment.getCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
+        assertEquals(payment.getTransactions().get(0).getPaymentId(), payment.getId());
+        assertEquals(payment.getTransactions().get(0).getAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getCurrency(), Currency.AED);
+        assertEquals(payment.getTransactions().get(0).getProcessedAmount().compareTo(authAmount), 0);
+        assertEquals(payment.getTransactions().get(0).getProcessedCurrency(), Currency.AED);
+
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.AUTHORIZE);
+        assertNull(payment.getTransactions().get(0).getGatewayErrorMsg());
+        assertNull(payment.getTransactions().get(0).getGatewayErrorCode());
+    }
+
+
+    @Test(groups = "slow")
+    public void testSimpleAuthCaptureWithInvalidPaymentId() throws Exception {
+        final BigDecimal requestedAmount = new BigDecimal("80.0091");
+
+        final Payment initialPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(),
+                                                                            UUID.randomUUID().toString(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+
+
+        try {
+            paymentApi.createCapture(account, UUID.randomUUID(), requestedAmount, account.getCurrency(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+            Assert.fail("Expected capture to fail...");
+        } catch (PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_NO_SUCH_PAYMENT.getCode());
+
+            final Payment latestPayment = paymentApi.getPayment(initialPayment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+            assertEquals(latestPayment, initialPayment);
+        }
+    }
+
+
+    @Test(groups = "slow")
+    public void testSimpleAuthCaptureWithInvalidCurrency() throws Exception {
+        final BigDecimal requestedAmount = new BigDecimal("80.0091");
+
+        final Payment initialPayment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(),
+                                                                            UUID.randomUUID().toString(), UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+
+
         try {
-            paymentApi.createPayment(account, invoice.getId(), requestedAmount, callContext);
+            paymentApi.createCapture(account, initialPayment.getId(), requestedAmount, Currency.AMD, UUID.randomUUID().toString(), ImmutableList.<PluginProperty>of(), callContext);
+            Assert.fail("Expected capture to fail...");
         } catch (PaymentApiException e) {
-            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD.getCode());
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
+
+            final Payment latestPayment = paymentApi.getPayment(initialPayment.getId(), false, ImmutableList.<PluginProperty>of(), callContext);
+            assertEquals(latestPayment, initialPayment);
         }
+    }
+
+    @Test(groups = "slow")
+    public void testInvalidTransitionAfterFailure() throws PaymentApiException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+
+        final String paymentExternalKey = "krapo";
+        final String transactionExternalKey = "grenouye";
+
+        final Payment payment = paymentApi.createAuthorization(account, account.getPaymentMethodId(), null, requestedAmount, Currency.EUR, paymentExternalKey, transactionExternalKey,
+                                                          ImmutableList.<PluginProperty>of(), callContext);
+
+        // Hack the Database to make it look like it was a failure
+        paymentDao.updatePaymentAndTransactionOnCompletion(account.getId(), payment.getId(), TransactionType.AUTHORIZE, "AUTH_ERRORED", null,
+                                                           payment.getTransactions().get(0).getId(), TransactionStatus.PLUGIN_FAILURE, null, null, null, null, internalCallContext);
+        PaymentSqlDao paymentSqlDao = dbi.onDemand(PaymentSqlDao.class);
+        paymentSqlDao.updateLastSuccessPaymentStateName(payment.getId().toString(), "AUTH_ERRORED", null, internalCallContext);
+
+        try {
+            paymentApi.createCapture(account, payment.getId(), requestedAmount, Currency.EUR, "tetard", ImmutableList.<PluginProperty>of(), callContext);
+            Assert.fail("Unexpected success");
+        } catch (PaymentApiException e){
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+        }
+    }
+
+
 
-        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), callContext);
-        Assert.assertEquals(payments.size(), 1);
 
-        final Payment payment = payments.get(0);
-        Assert.assertEquals(payment.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE_ABORTED);
+    private List<PluginProperty> createPropertiesForInvoice(final Invoice invoice) {
+        final List<PluginProperty> result = new ArrayList<PluginProperty>();
+        result.add(new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false));
+        return result;
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java
index 5d85e82..22454ae 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApiNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,26 +19,27 @@
 package org.killbill.billing.payment.api;
 
 import java.math.BigDecimal;
-import java.math.RoundingMode;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
-import org.mockito.Mockito;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.payment.MockRecurringInvoiceItem;
 import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
 import org.killbill.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
@@ -47,6 +50,18 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
 
     private static final Logger log = LoggerFactory.getLogger(TestPaymentApiNoDB.class);
 
+    private final Iterable<PluginProperty> PLUGIN_PROPERTIES = ImmutableList.<PluginProperty>of();
+    private final static PaymentOptions PAYMENT_OPTIONS = new PaymentOptions() {
+        @Override
+        public boolean isExternalPayment() {
+            return false;
+        }
+        @Override
+        public String getPaymentControlPluginName() {
+            return InvoicePaymentControlPluginApi.PLUGIN_NAME;
+        }
+    };
+
     private Account account;
 
     @BeforeClass(groups = "fast")
@@ -63,16 +78,16 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testSimplePaymentWithNoAmount() throws Exception {
+    public void testSimpleInvoicePaymentWithNoAmount() throws Exception {
         final BigDecimal invoiceAmount = new BigDecimal("10.0011");
         final BigDecimal requestedAmount = null;
-        final BigDecimal expectedAmount = invoiceAmount;
+        final BigDecimal expectedAmount = null;
 
         testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
     }
 
     @Test(groups = "fast")
-    public void testSimplePaymentWithInvoiceAmount() throws Exception {
+    public void testSimpleInvoicePaymentWithInvoiceAmount() throws Exception {
         final BigDecimal invoiceAmount = new BigDecimal("10.0011");
         final BigDecimal requestedAmount = invoiceAmount;
         final BigDecimal expectedAmount = invoiceAmount;
@@ -81,7 +96,7 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testSimplePaymentWithLowerAmount() throws Exception {
+    public void testSimpleInvoicePaymentWithLowerAmount() throws Exception {
         final BigDecimal invoiceAmount = new BigDecimal("10.0011");
         final BigDecimal requestedAmount = new BigDecimal("8.0091");
         final BigDecimal expectedAmount = requestedAmount;
@@ -90,7 +105,7 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testSimplePaymentWithInvalidAmount() throws Exception {
+    public void testSimpleInvoicePaymentWithInvalidAmount() throws Exception {
         final BigDecimal invoiceAmount = new BigDecimal("10.0011");
         final BigDecimal requestedAmount = new BigDecimal("80.0091");
         final BigDecimal expectedAmount = null;
@@ -100,7 +115,7 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
 
     private void testSimplePayment(final BigDecimal invoiceAmount, final BigDecimal requestedAmount, final BigDecimal expectedAmount) throws Exception {
         final LocalDate now = clock.getUTCToday();
-        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD, callContext);
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
 
         final UUID subscriptionId = UUID.randomUUID();
         final UUID bundleId = UUID.randomUUID();
@@ -116,64 +131,69 @@ public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
                                                             Currency.USD));
 
         try {
-            final Payment paymentInfo = paymentApi.createPayment(account, invoice.getId(), requestedAmount, callContext);
+
+            final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+            final PluginProperty prop1 = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false);
+            properties.add(prop1);
+
+            final Payment paymentInfo = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, account.getCurrency(),
+                                                                                          invoice.getId().toString(), UUID.randomUUID().toString(), properties, PAYMENT_OPTIONS, callContext);
             if (expectedAmount == null) {
                 fail("Expected to fail because requested amount > invoice amount");
             }
             assertNotNull(paymentInfo.getId());
-            assertTrue(paymentInfo.getAmount().compareTo(expectedAmount) == 0);
+            assertTrue(paymentInfo.getPurchasedAmount().compareTo(expectedAmount) == 0);
             assertNotNull(paymentInfo.getPaymentNumber());
-            assertEquals(paymentInfo.getPaymentStatus(), PaymentStatus.SUCCESS);
-            assertEquals(paymentInfo.getAttempts().size(), 1);
-            assertEquals(paymentInfo.getInvoiceId(), invoice.getId());
+            assertEquals(paymentInfo.getExternalKey(), invoice.getId().toString());
             assertEquals(paymentInfo.getCurrency(), Currency.USD);
+            assertTrue(paymentInfo.getTransactions().get(0).getAmount().compareTo(expectedAmount) == 0);
+            assertEquals(paymentInfo.getTransactions().get(0).getCurrency(), Currency.USD);
+            assertEquals(paymentInfo.getTransactions().get(0).getPaymentId(), paymentInfo.getId());
+            assertEquals(paymentInfo.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+            assertEquals(paymentInfo.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
 
-            final PaymentAttempt paymentAttempt = paymentInfo.getAttempts().get(0);
-            assertNotNull(paymentAttempt);
-            assertNotNull(paymentAttempt.getId());
-        } catch (PaymentApiException e) {
+        } catch (final PaymentApiException e) {
             if (expectedAmount != null) {
                 fail("Failed to create payment", e);
             } else {
                 log.info(e.getMessage());
-                assertEquals(e.getCode(), ErrorCode.PAYMENT_AMOUNT_DENIED.getCode());
             }
         }
     }
 
     @Test(groups = "fast")
     public void testPaymentMethods() throws Exception {
-        List<PaymentMethod> methods = paymentApi.getPaymentMethods(account, false, callContext);
+        List<PaymentMethod> methods = paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext);
         assertEquals(methods.size(), 1);
 
         final PaymentMethod initDefaultMethod = methods.get(0);
         assertEquals(initDefaultMethod.getId(), account.getPaymentMethodId());
 
         final PaymentMethodPlugin newPaymenrMethod = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), true, null);
-        final UUID newPaymentMethodId = paymentApi.addPaymentMethod(MockPaymentProviderPlugin.PLUGIN_NAME, account, true, newPaymenrMethod, callContext);
+        final UUID newPaymentMethodId = paymentApi.addPaymentMethod(account, UUID.randomUUID().toString(), MockPaymentProviderPlugin.PLUGIN_NAME, true, newPaymenrMethod, PLUGIN_PROPERTIES, callContext);
         Mockito.when(account.getPaymentMethodId()).thenReturn(newPaymentMethodId);
 
-        methods = paymentApi.getPaymentMethods(account, false, callContext);
+        methods = paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext);
         assertEquals(methods.size(), 2);
 
         assertEquals(newPaymentMethodId, account.getPaymentMethodId());
 
         boolean failed = false;
         try {
-            paymentApi.deletedPaymentMethod(account, newPaymentMethodId, false, callContext);
-        } catch (PaymentApiException e) {
+            paymentApi.deletePaymentMethod(account, newPaymentMethodId, false, PLUGIN_PROPERTIES, callContext);
+        } catch (final PaymentApiException e) {
             failed = true;
         }
         assertTrue(failed);
 
-        paymentApi.deletedPaymentMethod(account, initDefaultMethod.getId(), true,  callContext);
-        methods = paymentApi.getPaymentMethods(account, false, callContext);
+        paymentApi.deletePaymentMethod(account, initDefaultMethod.getId(), true, PLUGIN_PROPERTIES, callContext);
+        methods = paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext);
         assertEquals(methods.size(), 1);
 
         // NOW retry with default payment method with special flag
-        paymentApi.deletedPaymentMethod(account, newPaymentMethodId, true, callContext);
+        paymentApi.deletePaymentMethod(account, newPaymentMethodId, true, PLUGIN_PROPERTIES, callContext);
 
-        methods = paymentApi.getPaymentMethods(account, false, callContext);
+        methods = paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext);
         assertEquals(methods.size(), 0);
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentMethodPlugin.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentMethodPlugin.java
index b393341..1280407 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentMethodPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentMethodPlugin.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -24,7 +26,7 @@ public class TestPaymentMethodPlugin extends TestPaymentMethodPluginBase impleme
     private final UUID kbPaymentMethodId;
     private final String externalPaymentMethodId;
     private final boolean isDefaultPaymentMethod;
-    private final List<PaymentMethodKVInfo> properties;
+    private final List<PluginProperty> properties;
 
     public TestPaymentMethodPlugin(final UUID kbPaymentMethodId, final PaymentMethodPlugin src, final String externalPaymentId) {
         this.kbPaymentMethodId = kbPaymentMethodId;
@@ -49,7 +51,7 @@ public class TestPaymentMethodPlugin extends TestPaymentMethodPluginBase impleme
     }
 
     @Override
-    public List<PaymentMethodKVInfo> getProperties() {
+    public List<PluginProperty> getProperties() {
         return properties;
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/control/dao/TestInvoicePaymentControlDao.java b/payment/src/test/java/org/killbill/billing/payment/control/dao/TestInvoicePaymentControlDao.java
new file mode 100644
index 0000000..27429cc
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/control/dao/TestInvoicePaymentControlDao.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.control.dao;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestInvoicePaymentControlDao extends PaymentTestSuiteWithEmbeddedDB {
+
+    private InvoicePaymentControlDao dao;
+
+    @BeforeClass(groups = "slow")
+    protected void beforeClass() throws Exception {
+        super.beforeClass();
+        dao = new InvoicePaymentControlDao(dbi);
+    }
+
+    @Test(groups = "slow")
+    public void testPluginAutoPayOffSimple() {
+
+        UUID accountId = UUID.randomUUID();
+        UUID attemptId = UUID.randomUUID();
+        UUID paymentId = UUID.randomUUID();
+        UUID methodId = UUID.randomUUID();
+        BigDecimal amount = new BigDecimal("13.33");
+        DateTime utcNow = clock.getUTCNow();
+        final PluginAutoPayOffModelDao entry1 = new PluginAutoPayOffModelDao(attemptId, "key1", "tkey1", accountId, "XXX", paymentId, methodId, amount, Currency.USD, "lulu", utcNow);
+        dao.insertAutoPayOff(entry1);
+
+        final List<PluginAutoPayOffModelDao> entries = dao.getAutoPayOffEntry(accountId);
+        assertEquals(entries.size(), 1);
+        assertEquals(entries.get(0).getPaymentExternalKey(), "key1");
+        assertEquals(entries.get(0).getTransactionExternalKey(), "tkey1");
+        assertEquals(entries.get(0).getAccountId(), accountId);
+        assertEquals(entries.get(0).getPluginName(), "XXX");
+        assertEquals(entries.get(0).getPaymentId(), paymentId);
+        assertEquals(entries.get(0).getPaymentMethodId(), methodId);
+        assertEquals(entries.get(0).getAmount().compareTo(amount), 0);
+        assertEquals(entries.get(0).getCurrency(), Currency.USD);
+        assertEquals(entries.get(0).getCreatedBy(), "lulu");
+        assertEquals(entries.get(0).getCreatedDate(), utcNow);
+    }
+
+    @Test(groups = "slow")
+    public void testPluginAutoPayOffMutlitpleEntries() {
+
+        UUID accountId = UUID.randomUUID();
+        UUID attemptId = UUID.randomUUID();
+        UUID paymentId1 = UUID.randomUUID();
+        UUID methodId = UUID.randomUUID();
+        BigDecimal amount = new BigDecimal("13.33");
+        DateTime utcNow = clock.getUTCNow();
+        final PluginAutoPayOffModelDao entry1 = new PluginAutoPayOffModelDao(attemptId, "key1", "tkey1", accountId, "XXX", paymentId1, methodId, amount, Currency.USD, "lulu", utcNow);
+        dao.insertAutoPayOff(entry1);
+
+        UUID paymentId2 = UUID.randomUUID();
+        final PluginAutoPayOffModelDao entry2 = new PluginAutoPayOffModelDao(attemptId, "key2", "tkey2", accountId, "XXX", paymentId2, methodId, amount, Currency.USD, "lulu", utcNow);
+        dao.insertAutoPayOff(entry2);
+
+        final List<PluginAutoPayOffModelDao> entries = dao.getAutoPayOffEntry(accountId);
+        assertEquals(entries.size(), 2);
+    }
+
+    @Test(groups = "slow")
+    public void testPluginAutoPayOffNoEntries() {
+
+        UUID accountId = UUID.randomUUID();
+        UUID paymentId1 = UUID.randomUUID();
+        UUID attemptId = UUID.randomUUID();
+        UUID methodId = UUID.randomUUID();
+        BigDecimal amount = new BigDecimal("13.33");
+        DateTime utcNow = clock.getUTCNow();
+        final PluginAutoPayOffModelDao entry1 = new PluginAutoPayOffModelDao(attemptId, "key1", "tkey1", accountId, "XXX", paymentId1, methodId, amount, Currency.USD, "lulu", utcNow);
+        dao.insertAutoPayOff(entry1);
+
+        final List<PluginAutoPayOffModelDao> entries = dao.getAutoPayOffEntry(UUID.randomUUID());
+        assertEquals(entries.size(), 0);
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
new file mode 100644
index 0000000..992e210
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryablePaymentAutomatonRunner.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.killbill.automaton.Operation.OperationCallback;
+import org.killbill.automaton.OperationResult;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.callcontext.CallContext;
+import org.killbill.billing.util.config.PaymentConfig;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+
+import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED;
+
+public class MockRetryablePaymentAutomatonRunner extends PluginControlledPaymentAutomatonRunner {
+
+    private OperationCallback operationCallback;
+    private RetryablePaymentStateContext context;
+
+    @Inject
+    public MockRetryablePaymentAutomatonRunner(@Named(PaymentModule.STATE_MACHINE_PAYMENT) final StateMachineConfig stateMachineConfig, @Named(PaymentModule.STATE_MACHINE_RETRY) final StateMachineConfig retryStateMachine, final PaymentDao paymentDao, final GlobalLocker locker, final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry, final Clock clock, final TagInternalApi tagApi, final PaymentProcessor paymentProcessor,
+                                               @Named(RETRYABLE_NAMED) final RetryServiceScheduler retryServiceScheduler, final PaymentConfig paymentConfig, @com.google.inject.name.Named(PLUGIN_EXECUTOR_NAMED) final ExecutorService executor,
+                                               final PaymentStateMachineHelper paymentSMHelper, final RetryStateMachineHelper retrySMHelper, final PersistentBus eventBus) {
+        super(stateMachineConfig, paymentDao, locker, pluginRegistry, retryPluginRegistry, clock, paymentProcessor, retryServiceScheduler, paymentConfig, executor, paymentSMHelper, retrySMHelper, eventBus);
+    }
+
+    @Override
+    OperationCallback createOperationCallback(final TransactionType transactionType, final RetryablePaymentStateContext paymentStateContext) {
+        if (operationCallback == null) {
+            return super.createOperationCallback(transactionType, paymentStateContext);
+        } else {
+            return operationCallback;
+        }
+    }
+
+    @Override
+    RetryablePaymentStateContext createContext(final boolean isApiPayment, final TransactionType transactionType, final Account account, @Nullable final UUID paymentMethodId,
+                                                     @Nullable final UUID paymentId, @Nullable final String paymentExternalKey, final String paymentTransactionExternalKey,
+                                                     @Nullable final BigDecimal amount, @Nullable final Currency currency,
+                                                     final Iterable<PluginProperty> properties,
+                                                     final String pluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
+        if (context == null) {
+            return super.createContext(isApiPayment, transactionType, account, paymentMethodId, paymentId, paymentExternalKey, paymentTransactionExternalKey,
+                                       amount, currency, properties, pluginName, callContext, internalCallContext);
+        } else {
+            return context;
+        }
+    }
+
+    public MockRetryablePaymentAutomatonRunner setOperationCallback(final OperationCallback operationCallback) {
+        this.operationCallback = operationCallback;
+        return this;
+    }
+
+    public MockRetryablePaymentAutomatonRunner setContext(final RetryablePaymentStateContext context) {
+        this.context = context;
+        return this;
+    }
+
+    public PluginDispatcher<OperationResult> getPaymentPluginDispatcher() {
+        return paymentPluginDispatcher;
+    }
+
+    public OSGIServiceRegistration<PaymentControlPluginApi> getRetryPluginRegistry() {
+        return paymentControlPluginRegistry;
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
new file mode 100644
index 0000000..efd2963
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/MockRetryAuthorizeOperationCallback.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.util.Collections;
+
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.api.DefaultPayment;
+import org.killbill.billing.payment.api.DefaultPaymentTransaction;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.clock.Clock;
+import org.killbill.commons.locker.GlobalLocker;
+
+public class MockRetryAuthorizeOperationCallback extends RetryAuthorizeOperationCallback {
+
+    private final PaymentDao paymentDao;
+    private final Clock clock;
+
+    private Exception exception;
+    private OperationResult result;
+
+    public MockRetryAuthorizeOperationCallback(final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final RetryablePaymentStateContext paymentStateContext, final PaymentProcessor paymentProcessor, final OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry, final PaymentDao paymentDao, final Clock clock) {
+        super(locker, paymentPluginDispatcher, paymentStateContext, paymentProcessor, retryPluginRegistry);
+        this.paymentDao = paymentDao;
+        this.clock = clock;
+    }
+
+    @Override
+    protected Payment doCallSpecificOperationCallback() throws PaymentApiException {
+        if (exception != null) {
+            if (exception instanceof PaymentApiException) {
+                throw (PaymentApiException) exception;
+            } else {
+                throw new RuntimeException(exception);
+            }
+        }
+        final PaymentModelDao payment = new PaymentModelDao(clock.getUTCNow(),
+                                                            clock.getUTCNow(),
+                                                            paymentStateContext.account.getId(),
+                                                            paymentStateContext.paymentMethodId,
+                                                            paymentStateContext.paymentExternalKey);
+
+        final PaymentTransactionModelDao transaction = new PaymentTransactionModelDao(clock.getUTCNow(),
+                                                                                      clock.getUTCNow(),
+                                                                                      paymentStateContext.getAttemptId(),
+                                                                                      paymentStateContext.paymentTransactionExternalKey,
+                                                                                      paymentStateContext.paymentId,
+                                                                                      paymentStateContext.transactionType,
+                                                                                      clock.getUTCNow(),
+                                                                                      TransactionStatus.SUCCESS,
+                                                                                      paymentStateContext.amount,
+                                                                                      paymentStateContext.currency,
+                                                                                      "",
+                                                                                      "");
+        final PaymentModelDao paymentModelDao = paymentDao.insertPaymentWithFirstTransaction(payment, transaction, paymentStateContext.internalCallContext);
+        final PaymentTransaction convertedTransaction = new DefaultPaymentTransaction(transaction.getId(),
+                                                                                                  paymentStateContext.getAttemptId(),
+                                                                                                  transaction.getTransactionExternalKey(),
+                                                                                                  transaction.getCreatedDate(),
+                                                                                                  transaction.getUpdatedDate(),
+                                                                                                  transaction.getPaymentId(),
+                                                                                                  transaction.getTransactionType(),
+                                                                                                  transaction.getEffectiveDate(),
+                                                                                                  transaction.getTransactionStatus(),
+                                                                                                  transaction.getAmount(),
+                                                                                                  transaction.getCurrency(),
+                                                                                                  transaction.getProcessedAmount(),
+                                                                                                  transaction.getProcessedCurrency(),
+                                                                                                  transaction.getGatewayErrorCode(),
+                                                                                                  transaction.getGatewayErrorMsg(),
+                                                                                                  null);
+
+        return new DefaultPayment(paymentModelDao.getId(), paymentModelDao.getCreatedDate(), paymentModelDao.getUpdatedDate(), paymentModelDao.getAccountId(),
+                                        paymentModelDao.getPaymentMethodId(), paymentModelDao.getPaymentNumber(), paymentModelDao.getExternalKey(), Collections.singletonList(convertedTransaction));
+    }
+
+    public MockRetryAuthorizeOperationCallback setException(final Exception exception) {
+        this.exception = exception;
+        return this;
+    }
+
+    public MockRetryAuthorizeOperationCallback setResult(final OperationResult result) {
+        this.result = result;
+        return this;
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentAutomatonDAOHelper.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentAutomatonDAOHelper.java
new file mode 100644
index 0000000..f6dcd0a
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentAutomatonDAOHelper.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestPaymentAutomatonDAOHelper extends PaymentTestSuiteWithEmbeddedDB {
+
+    private final String paymentExternalKey = UUID.randomUUID().toString();
+    private final String paymentTransactionExternalKey = UUID.randomUUID().toString();
+    private final BigDecimal amount = new BigDecimal("9320.19200001");
+    private final Currency currency = Currency.CAD;
+
+    private PaymentStateContext paymentStateContext;
+
+    @Test(groups = "slow")
+    public void testFailToRetrievePayment() throws Exception {
+        // Verify a dummy payment doesn't exist
+        final PaymentAutomatonDAOHelper daoHelper = createDAOHelper(UUID.randomUUID(), paymentExternalKey, paymentTransactionExternalKey, amount, currency);
+        try {
+            daoHelper.getPayment();
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_NO_SUCH_PAYMENT.getCode());
+        }
+    }
+
+    @Test(groups = "slow")
+    public void testCreateNewPaymentTransaction() throws Exception {
+        // Create a payment and transaction based on the context
+        final PaymentAutomatonDAOHelper daoHelper = createDAOHelper(null, paymentExternalKey, paymentTransactionExternalKey, amount, currency);
+        daoHelper.createNewPaymentTransaction();
+
+        final PaymentModelDao payment1 = daoHelper.getPayment();
+        Assert.assertEquals(payment1.getExternalKey(), paymentExternalKey);
+        Assert.assertNull(payment1.getStateName());
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getTransactionExternalKey(), paymentTransactionExternalKey);
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getAmount().compareTo(amount), 0);
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getCurrency(), currency);
+
+        // Verify we can update them
+        final PaymentTransactionInfoPlugin paymentInfoPlugin = Mockito.mock(PaymentTransactionInfoPlugin.class);
+        Mockito.when(paymentInfoPlugin.getAmount()).thenReturn(new BigDecimal("82010.222"));
+        Mockito.when(paymentInfoPlugin.getCurrency()).thenReturn(Currency.CAD);
+        Mockito.when(paymentInfoPlugin.getStatus()).thenReturn(PaymentPluginStatus.PROCESSED);
+        Mockito.when(paymentInfoPlugin.getGatewayErrorCode()).thenReturn(UUID.randomUUID().toString().substring(0, 5));
+        Mockito.when(paymentInfoPlugin.getGatewayError()).thenReturn(UUID.randomUUID().toString());
+        daoHelper.processPaymentInfoPlugin(TransactionStatus.SUCCESS, paymentInfoPlugin, "SOME_STATE");
+
+        final PaymentModelDao payment2 = daoHelper.getPayment();
+        Assert.assertEquals(payment2.getExternalKey(), paymentExternalKey);
+        Assert.assertEquals(payment2.getStateName(), "SOME_STATE");
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getPaymentId(), payment2.getId());
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getTransactionExternalKey(), paymentTransactionExternalKey);
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getTransactionStatus(), TransactionStatus.SUCCESS);
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getAmount().compareTo(amount), 0);
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getCurrency(), currency);
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getProcessedAmount().compareTo(paymentInfoPlugin.getAmount()), 0);
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getProcessedCurrency(), paymentInfoPlugin.getCurrency());
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getGatewayErrorCode(), paymentInfoPlugin.getGatewayErrorCode());
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getGatewayErrorMsg(), paymentInfoPlugin.getGatewayError());
+    }
+
+    @Test(groups = "slow")
+    public void testNoPaymentMethod() throws Exception {
+        final PaymentAutomatonDAOHelper daoHelper = createDAOHelper(UUID.randomUUID(), paymentExternalKey, paymentTransactionExternalKey, amount, currency);
+        try {
+            daoHelper.getPaymentProviderPlugin();
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_NO_SUCH_PAYMENT_METHOD.getCode());
+        }
+    }
+
+    private PaymentAutomatonDAOHelper createDAOHelper(@Nullable final UUID paymentId, final String paymentExternalKey,
+                                                      final String paymentTransactionExternalKey,
+                                                      final BigDecimal amount, final Currency currency) throws Exception {
+        final Account account = Mockito.mock(Account.class);
+        Mockito.when(account.getId()).thenReturn(UUID.randomUUID());
+        // No default payment method
+
+        paymentStateContext = new PaymentStateContext(true, paymentId,
+                                                      null, null,
+                                                      paymentExternalKey,
+                                                      paymentTransactionExternalKey,
+                                                      TransactionType.CAPTURE,
+                                                      account,
+                                                      UUID.randomUUID(),
+                                                      amount,
+                                                      currency,
+                                                      false,
+                                                      null, ImmutableList.<PluginProperty>of(),
+                                                      internalCallContext,
+                                                      callContext);
+
+        return new PaymentAutomatonDAOHelper(paymentStateContext, clock.getUTCNow(), paymentDao, registry, internalCallContext, eventBus, paymentSMHelper);
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentEnteringStateCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentEnteringStateCallback.java
new file mode 100644
index 0000000..7cc35b5
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentEnteringStateCallback.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.killbill.automaton.Operation.OperationCallback;
+import org.killbill.automaton.OperationResult;
+import org.killbill.automaton.State;
+import org.killbill.automaton.State.LeavingStateCallback;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestPaymentEnteringStateCallback extends PaymentTestSuiteWithEmbeddedDB {
+
+    private final State state = Mockito.mock(State.class);
+    private final OperationCallback operationCallback = Mockito.mock(OperationCallback.class);
+    private final LeavingStateCallback leavingStateCallback = Mockito.mock(LeavingStateCallback.class);
+
+    private PaymentStateContext paymentStateContext;
+    private PaymentAutomatonDAOHelper daoHelper;
+    private PaymentEnteringStateTestCallback callback;
+    private OperationResult operationResult;
+
+    @BeforeMethod(groups = "slow")
+    public void setUp() throws Exception {
+        final Account account = Mockito.mock(Account.class);
+        Mockito.when(account.getId()).thenReturn(UUID.randomUUID());
+        paymentStateContext = new PaymentStateContext(true,
+                                                      null,
+                                                      UUID.randomUUID().toString(),
+                                                      TransactionType.CAPTURE,
+                                                      account,
+                                                      UUID.randomUUID(),
+                                                      new BigDecimal("192.3920111"),
+                                                      Currency.BRL,
+                                                      false,
+                                                      ImmutableList.<PluginProperty>of(),
+                                                      internalCallContext,
+                                                      callContext);
+        daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, clock.getUTCNow(), paymentDao, registry, internalCallContext, eventBus, paymentSMHelper);
+        callback = new PaymentEnteringStateTestCallback(daoHelper, paymentStateContext);
+
+        Mockito.when(state.getName()).thenReturn("NEW_STATE");
+        operationResult = OperationResult.SUCCESS;
+    }
+
+    @Test(groups = "slow")
+    public void testEnterStateAndProcessPaymentTransactionInfoPlugin() throws Exception {
+        // Create the payment and first transaction (would be done by PaymentLeavingStateCallback)
+        daoHelper.createNewPaymentTransaction();
+        Assert.assertEquals(paymentDao.getPaymentTransaction(paymentStateContext.getPaymentTransactionModelDao().getId(), internalCallContext).getTransactionStatus(), TransactionStatus.UNKNOWN);
+
+        // Mock the plugin result
+        final PaymentTransactionInfoPlugin paymentInfoPlugin = Mockito.mock(PaymentTransactionInfoPlugin.class);
+        Mockito.when(paymentInfoPlugin.getAmount()).thenReturn(new BigDecimal("82010.222"));
+        Mockito.when(paymentInfoPlugin.getCurrency()).thenReturn(Currency.CAD);
+        Mockito.when(paymentInfoPlugin.getStatus()).thenReturn(PaymentPluginStatus.PENDING);
+        Mockito.when(paymentInfoPlugin.getGatewayErrorCode()).thenReturn(UUID.randomUUID().toString().substring(0, 5));
+        Mockito.when(paymentInfoPlugin.getGatewayError()).thenReturn(UUID.randomUUID().toString());
+        paymentStateContext.setPaymentInfoPlugin(paymentInfoPlugin);
+
+        // Process the plugin result
+        callback.enteringState(state, operationCallback, operationResult, leavingStateCallback);
+
+        // Verify the updated transaction
+        final PaymentTransactionModelDao paymentTransaction = paymentDao.getPaymentTransaction(paymentStateContext.getPaymentTransactionModelDao().getId(), internalCallContext);
+        Assert.assertEquals(paymentTransaction.getAmount().compareTo(paymentStateContext.getAmount()), 0);
+        Assert.assertEquals(paymentTransaction.getCurrency(), paymentStateContext.getCurrency());
+        Assert.assertEquals(paymentTransaction.getProcessedAmount().compareTo(paymentInfoPlugin.getAmount()), 0);
+        Assert.assertEquals(paymentTransaction.getProcessedCurrency(), paymentInfoPlugin.getCurrency());
+        Assert.assertEquals(paymentTransaction.getTransactionStatus(), TransactionStatus.PENDING);
+        Assert.assertEquals(paymentTransaction.getGatewayErrorCode(), paymentInfoPlugin.getGatewayErrorCode());
+        Assert.assertEquals(paymentTransaction.getGatewayErrorMsg(), paymentInfoPlugin.getGatewayError());
+    }
+
+    @Test(groups = "slow")
+    public void testEnterStateWithOperationException() throws Exception {
+        daoHelper.createNewPaymentTransaction();
+        // Simulate a bug in the plugin - i.e. nothing was returned
+        paymentStateContext.setPaymentInfoPlugin(null);
+        operationResult = OperationResult.EXCEPTION;
+
+        callback.enteringState(state, operationCallback, operationResult, leavingStateCallback);
+
+        Assert.assertEquals(paymentDao.getPaymentTransaction(paymentStateContext.getPaymentTransactionModelDao().getId(), internalCallContext).getTransactionStatus(), TransactionStatus.PLUGIN_FAILURE);
+    }
+
+    private static final class PaymentEnteringStateTestCallback extends PaymentEnteringStateCallback {
+
+        private PaymentEnteringStateTestCallback(final PaymentAutomatonDAOHelper daoHelper, final PaymentStateContext paymentStateContext) throws PaymentApiException {
+            super(daoHelper, paymentStateContext);
+        }
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java
new file mode 100644
index 0000000..c37f577
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentLeavingStateCallback.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.automaton.State;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestPaymentLeavingStateCallback extends PaymentTestSuiteWithEmbeddedDB {
+
+    private final State state = Mockito.mock(State.class);
+
+    private PaymentStateContext paymentStateContext;
+    private PaymentLeavingStateTestCallback callback;
+
+    @Test(groups = "slow")
+    public void testLeaveStateForNewPayment() throws Exception {
+        setUp(null);
+
+        callback.leavingState(state);
+
+        // Verify a new transaction was created
+        verifyPaymentTransaction();
+
+        // Verify a new payment was created
+        final PaymentModelDao payment = paymentDao.getPayment(paymentStateContext.getPaymentTransactionModelDao().getPaymentId(), internalCallContext);
+        Assert.assertEquals(payment.getExternalKey(), paymentStateContext.getPaymentExternalKey());
+        Assert.assertNull(payment.getStateName());
+
+        // Verify the payment has only one transaction
+        Assert.assertEquals(paymentDao.getTransactionsForPayment(payment.getId(), internalCallContext).size(), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testLeaveStateForExistingPayment() throws Exception {
+        final UUID paymentId = UUID.randomUUID();
+        setUp(paymentId);
+
+        // Verify the payment has only one transaction
+        Assert.assertEquals(paymentDao.getTransactionsForPayment(paymentId, internalCallContext).size(), 1);
+
+        callback.leavingState(state);
+
+        // Verify a new transaction was created
+        verifyPaymentTransaction();
+
+        // Verify the payment has now two transactions
+        Assert.assertEquals(paymentDao.getTransactionsForPayment(paymentId, internalCallContext).size(), 2);
+    }
+
+    private void verifyPaymentTransaction() {
+        Assert.assertNotNull(paymentStateContext.getPaymentTransactionModelDao().getPaymentId());
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getTransactionExternalKey(), paymentStateContext.getPaymentTransactionExternalKey());
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getTransactionStatus(), TransactionStatus.UNKNOWN);
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getAmount().compareTo(paymentStateContext.getAmount()), 0);
+        Assert.assertEquals(paymentStateContext.getPaymentTransactionModelDao().getCurrency(), paymentStateContext.getCurrency());
+        Assert.assertNull(paymentStateContext.getPaymentTransactionModelDao().getProcessedAmount());
+        Assert.assertNull(paymentStateContext.getPaymentTransactionModelDao().getProcessedCurrency());
+        Assert.assertNull(paymentStateContext.getPaymentTransactionModelDao().getGatewayErrorCode());
+        Assert.assertNull(paymentStateContext.getPaymentTransactionModelDao().getGatewayErrorMsg());
+    }
+
+    private void setUp(@Nullable final UUID paymentId) throws Exception {
+        final Account account = Mockito.mock(Account.class);
+        Mockito.when(account.getId()).thenReturn(UUID.randomUUID());
+        paymentStateContext = new PaymentStateContext(true,
+                                                      paymentId,
+                                                      null, null,
+                                                      UUID.randomUUID().toString(),
+                                                      UUID.randomUUID().toString(),
+                                                      TransactionType.CAPTURE,
+                                                      account,
+                                                      UUID.randomUUID(),
+                                                      new BigDecimal("192.3920111"),
+                                                      Currency.BRL,
+                                                      false,
+                                                      null, ImmutableList.<PluginProperty>of(),
+                                                      internalCallContext,
+                                                      callContext);
+
+        if (paymentId != null) {
+            // Create the first payment manually
+            final PaymentModelDao newPaymentModelDao = new PaymentModelDao(paymentId,
+                                                                           clock.getUTCNow(),
+                                                                           clock.getUTCNow(),
+                                                                           paymentStateContext.getAccount().getId(),
+                                                                           paymentStateContext.getPaymentMethodId(),
+                                                                           1,
+                                                                           paymentStateContext.getPaymentExternalKey()
+            );
+            final PaymentTransactionModelDao newPaymentTransactionModelDao = new PaymentTransactionModelDao(clock.getUTCNow(),
+                                                                                                            clock.getUTCNow(),
+                                                                                                            null,
+                                                                                                            paymentStateContext.getPaymentTransactionExternalKey(),
+                                                                                                            paymentId,
+                                                                                                            paymentStateContext.getTransactionType(),
+                                                                                                            clock.getUTCNow(),
+                                                                                                            TransactionStatus.UNKNOWN,
+                                                                                                            paymentStateContext.getAmount(),
+                                                                                                            paymentStateContext.getCurrency(),
+                                                                                                            null,
+                                                                                                            null);
+            paymentDao.insertPaymentWithFirstTransaction(newPaymentModelDao, newPaymentTransactionModelDao, internalCallContext);
+        }
+
+        final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, clock.getUTCNow(), paymentDao, registry, internalCallContext, eventBus, paymentSMHelper);
+        callback = new PaymentLeavingStateTestCallback(daoHelper, paymentStateContext);
+
+        Mockito.when(state.getName()).thenReturn("NEW_STATE");
+    }
+
+    private static final class PaymentLeavingStateTestCallback extends PaymentLeavingStateCallback {
+
+        private PaymentLeavingStateTestCallback(final PaymentAutomatonDAOHelper daoHelper, final PaymentStateContext paymentStateContext) throws PaymentApiException {
+            super(daoHelper, paymentStateContext);
+        }
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
new file mode 100644
index 0000000..4d11b09
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPaymentOperation.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.Executors;
+
+import javax.annotation.Nullable;
+
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentMethodModelDao;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.memory.MemoryGlobalLocker;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestPaymentOperation extends PaymentTestSuiteNoDB {
+
+    private PaymentStateContext paymentStateContext;
+    private PaymentOperationTest paymentOperation;
+
+    @Test(groups = "fast")
+    public void testPaymentFailure() throws Exception {
+        setUp(PaymentPluginStatus.ERROR);
+
+        Assert.assertNull(paymentStateContext.getPaymentInfoPlugin());
+
+        Assert.assertEquals(paymentOperation.doOperationCallback(), OperationResult.FAILURE);
+
+        Assert.assertNotNull(paymentStateContext.getPaymentInfoPlugin());
+    }
+
+    @Test(groups = "fast")
+    public void testPluginFailure() throws Exception {
+        setUp(null);
+
+        Assert.assertNull(paymentStateContext.getPaymentInfoPlugin());
+
+        try {
+            paymentOperation.doOperationCallback();
+            Assert.fail();
+        } catch (final OperationException e) {
+            Assert.assertEquals(e.getOperationResult(), OperationResult.EXCEPTION);
+        }
+
+        Assert.assertNull(paymentStateContext.getPaymentInfoPlugin());
+    }
+
+    @Test(groups = "fast")
+    public void testPaymentPending() throws Exception {
+        setUp(PaymentPluginStatus.PENDING);
+
+        Assert.assertNull(paymentStateContext.getPaymentInfoPlugin());
+
+        Assert.assertEquals(paymentOperation.doOperationCallback(), OperationResult.PENDING);
+
+        Assert.assertNotNull(paymentStateContext.getPaymentInfoPlugin());
+    }
+
+    @Test(groups = "fast")
+    public void testPaymentSuccess() throws Exception {
+        setUp(PaymentPluginStatus.PROCESSED);
+
+        Assert.assertNull(paymentStateContext.getPaymentInfoPlugin());
+
+        Assert.assertEquals(paymentOperation.doOperationCallback(), OperationResult.SUCCESS);
+
+        Assert.assertNotNull(paymentStateContext.getPaymentInfoPlugin());
+    }
+
+    private void setUp(final PaymentPluginStatus paymentPluginStatus) throws Exception {
+        final GlobalLocker locker = new MemoryGlobalLocker();
+        final PluginDispatcher<OperationResult> paymentPluginDispatcher = new PluginDispatcher<OperationResult>(1, Executors.newCachedThreadPool());
+        paymentStateContext = new PaymentStateContext(true,
+                                                      UUID.randomUUID(),
+                                                      null, null,
+                                                      UUID.randomUUID().toString(),
+                                                      UUID.randomUUID().toString(),
+                                                      TransactionType.CAPTURE,
+                                                      Mockito.mock(Account.class),
+                                                      UUID.randomUUID(),
+                                                      new BigDecimal("192.3920111"),
+                                                      Currency.BRL,
+                                                      false,
+                                                      null, ImmutableList.<PluginProperty>of(),
+                                                      internalCallContext,
+                                                      callContext);
+
+        final PaymentMethodModelDao paymentMethodModelDao = new PaymentMethodModelDao(paymentStateContext.getPaymentMethodId(), UUID.randomUUID().toString(), clock.getUTCNow(), clock.getUTCNow(),
+                                                                                      paymentStateContext.getAccount().getId(), MockPaymentProviderPlugin.PLUGIN_NAME, true);
+        final PaymentDao paymentDao = Mockito.mock(PaymentDao.class);
+        Mockito.when(paymentDao.getPaymentMethodIncludedDeleted(paymentStateContext.getPaymentMethodId(), internalCallContext)).thenReturn(paymentMethodModelDao);
+
+        final PaymentAutomatonDAOHelper daoHelper = new PaymentAutomatonDAOHelper(paymentStateContext, clock.getUTCNow(), paymentDao, registry, internalCallContext, eventBus, paymentSMHelper);
+        paymentOperation = new PaymentOperationTest(paymentPluginStatus, daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+    }
+
+    private static final class PaymentOperationTest extends PaymentOperation {
+
+        private final PaymentTransactionInfoPlugin paymentInfoPlugin;
+
+        public PaymentOperationTest(@Nullable final PaymentPluginStatus paymentPluginStatus,
+                                    final PaymentAutomatonDAOHelper daoHelper, final GlobalLocker locker,
+                                    final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateContext paymentStateContext) throws PaymentApiException {
+            super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+            this.paymentInfoPlugin = (paymentPluginStatus == null ? null : getPaymentInfoPlugin(paymentPluginStatus));
+        }
+
+        @Override
+        protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+            if (paymentInfoPlugin == null) {
+                throw new RuntimeException("Exception expected by test");
+            } else {
+                return paymentInfoPlugin;
+            }
+        }
+
+        private PaymentTransactionInfoPlugin getPaymentInfoPlugin(final PaymentPluginStatus paymentPluginStatus) {
+            final PaymentTransactionInfoPlugin paymentInfoPlugin = Mockito.mock(PaymentTransactionInfoPlugin.class);
+            Mockito.when(paymentInfoPlugin.getStatus()).thenReturn(paymentPluginStatus);
+            return paymentInfoPlugin;
+        }
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
new file mode 100644
index 0000000..fbf4a8c
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.Nullable;
+
+import org.killbill.automaton.OperationException;
+import org.killbill.automaton.OperationResult;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.ProcessorBase.WithAccountLockCallback;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.memory.MemoryGlobalLocker;
+import org.mockito.Mockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.jayway.awaitility.Awaitility;
+
+public class TestPluginOperation extends PaymentTestSuiteNoDB {
+
+    private final GlobalLocker locker = new MemoryGlobalLocker();
+    private final Account account = Mockito.mock(Account.class);
+
+    private static final Logger logger = LoggerFactory.getLogger(TestPluginOperation.class);
+
+    @BeforeMethod(groups = "fast")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        Mockito.when(account.getExternalKey()).thenReturn(UUID.randomUUID().toString());
+    }
+
+    @Test(groups = "fast")
+    public void testWithAccountLock() throws Exception {
+        testLocking(true);
+    }
+
+
+    @Test(groups = "fast")
+    public void testOperationThrowsPaymentApiException() throws Exception {
+        final CallbackTest callback = new CallbackTest(new PaymentApiException(ErrorCode.__UNKNOWN_ERROR_CODE));
+        final PaymentOperation pluginOperation = getPluginOperation();
+
+        try {
+            pluginOperation.dispatchWithAccountLockAndTimeout(callback);
+            Assert.fail();
+        } catch (final OperationException e) {
+            Assert.assertEquals(e.getOperationResult(), OperationResult.FAILURE);
+            Assert.assertTrue(e.getCause() instanceof PaymentApiException);
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testOperationThrowsRuntimeException() throws Exception {
+        final CallbackTest callback = new CallbackTest(new NullPointerException("Expected for the test"));
+        final PaymentOperation pluginOperation = getPluginOperation();
+
+        try {
+            pluginOperation.dispatchWithAccountLockAndTimeout(callback);
+            Assert.fail();
+        } catch (final OperationException e) {
+            Assert.assertEquals(e.getOperationResult(), OperationResult.EXCEPTION);
+            Assert.assertTrue(e.getCause() instanceof NullPointerException);
+        }
+    }
+
+    private void testLocking(final boolean withAccountLock) throws Exception {
+        final Semaphore available = new Semaphore(1, true);
+        final CallbackTest callback = new CallbackTest(available);
+        final PaymentOperation pluginOperation = getPluginOperation(withAccountLock);
+
+        // Take the only permit
+        available.acquire();
+
+        // Start the plugin operation in the background (will block)
+        runPluginOperationInBackground(pluginOperation, callback, false);
+
+        // The operation should be blocked here because we have the semaphore
+        Assert.assertEquals(available.getQueueLength(), 1);
+        Assert.assertEquals(callback.getRunCount(), 0);
+
+        if (withAccountLock) {
+            // If the account is locked, trying to run the operation again will throw LockFailedException
+            runPluginOperationInBackground(pluginOperation, callback, true);
+        } else {
+            // If the account is not locked, it will just block
+            runPluginOperationInBackground(pluginOperation, callback, false);
+            Assert.assertEquals(available.getQueueLength(), 2);
+            Assert.assertEquals(callback.getRunCount(), 0);
+        }
+
+        // Release the semaphore
+        available.release();
+
+        // Give some time for the operation to run
+        Awaitility.await()
+                  .until(new Callable<Boolean>() {
+                      @Override
+                      public Boolean call() throws Exception {
+                          return callback.getRunCount() == (withAccountLock ? 1 : 2);
+                      }
+                  });
+
+        // Verify the final state
+        Assert.assertEquals(available.getQueueLength(), 0);
+        Assert.assertEquals(callback.getRunCount(), withAccountLock ? 1 : 2);
+    }
+
+    private void runPluginOperationInBackground(final PaymentOperation pluginOperation, final CallbackTest callback, final boolean shouldFailBecauseOfLockFailure) throws Exception {
+        final AtomicBoolean threadStarted = new AtomicBoolean(false);
+        final Thread t1 = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                threadStarted.set(true);
+
+                if (shouldFailBecauseOfLockFailure) {
+                    try {
+                        pluginOperation.dispatchWithAccountLockAndTimeout(callback);
+                        Assert.fail();
+                    } catch (final OperationException e) {
+                        Assert.assertTrue(e.getCause() instanceof PaymentApiException);
+                        // No better error code for lock failures...
+                        Assert.assertEquals(((PaymentApiException) e.getCause()).getCode(), ErrorCode.PAYMENT_INTERNAL_ERROR.getCode());
+                    }
+                } else {
+                    try {
+                        pluginOperation.dispatchWithAccountLockAndTimeout(callback);
+                    } catch (final OperationException e) {
+                        Assert.fail(e.getMessage());
+                    }
+                }
+            }
+        });
+
+        t1.start();
+
+        // Make sure the thread has started
+        Awaitility.await().untilTrue(threadStarted);
+    }
+
+    private PaymentOperation getPluginOperation() throws PaymentApiException {
+        return getPluginOperation(false);
+    }
+
+    private PaymentOperation getPluginOperation(final boolean shouldLockAccount) throws PaymentApiException {
+        return getPluginOperation(shouldLockAccount, Integer.MAX_VALUE);
+    }
+
+    private PaymentOperation getPluginOperation(final boolean shouldLockAccount, final int timeoutSeconds) throws PaymentApiException {
+        final PluginDispatcher<OperationResult> paymentPluginDispatcher = new PluginDispatcher<OperationResult>(timeoutSeconds, Executors.newCachedThreadPool());
+
+        final PaymentStateContext paymentStateContext = new PaymentStateContext(true, UUID.randomUUID(),
+                                                                                null, null,
+                                                                                UUID.randomUUID().toString(),
+                                                                                UUID.randomUUID().toString(),
+                                                                                TransactionType.CAPTURE,
+                                                                                account,
+                                                                                UUID.randomUUID(),
+                                                                                new BigDecimal("192.3920111"),
+                                                                                Currency.BRL,
+                                                                                shouldLockAccount,
+                                                                                null, ImmutableList.<PluginProperty>of(),
+                                                                                internalCallContext,
+                                                                                callContext);
+
+        final PaymentAutomatonDAOHelper daoHelper = Mockito.mock(PaymentAutomatonDAOHelper.class);
+        Mockito.when(daoHelper.getPaymentProviderPlugin()).thenReturn(null);
+        return new PluginOperationTest(daoHelper, locker, paymentPluginDispatcher, paymentStateContext);
+    }
+
+    private static final class CallbackTest implements WithAccountLockCallback<PluginDispatcherReturnType<OperationResult>, PaymentApiException> {
+
+        private final AtomicInteger runCount = new AtomicInteger(0);
+
+        private final Semaphore available;
+        private final Integer sleepTimeMillis;
+        private final PaymentApiException paymentApiException;
+        private final RuntimeException runtimeException;
+
+        public CallbackTest(final Semaphore available) {
+            this(available, null, null, null);
+        }
+
+        public CallbackTest(final Integer sleepTimeMillis) {
+            this(null, sleepTimeMillis, null, null);
+        }
+
+        public CallbackTest(final PaymentApiException paymentApiException) {
+            this(null, null, paymentApiException, null);
+        }
+
+        public CallbackTest(final RuntimeException runtimeException) {
+            this(null, null, null, runtimeException);
+        }
+
+        private CallbackTest(@Nullable final Semaphore available, @Nullable final Integer sleepTimeMillis,
+                             @Nullable final PaymentApiException paymentApiException, @Nullable final RuntimeException runtimeException) {
+            this.available = available;
+            this.sleepTimeMillis = sleepTimeMillis;
+            this.paymentApiException = paymentApiException;
+            this.runtimeException = runtimeException;
+        }
+
+        @Override
+        public PluginDispatcherReturnType<OperationResult> doOperation() throws PaymentApiException {
+            try {
+                if (available != null) {
+                    available.acquire();
+                }
+
+                if (sleepTimeMillis != null) {
+                    Thread.sleep(sleepTimeMillis);
+                }
+
+                if (paymentApiException != null) {
+                    throw paymentApiException;
+                } else if (runtimeException != null) {
+                    throw runtimeException;
+                } else {
+                    runCount.incrementAndGet();
+                }
+            } catch (final InterruptedException e) {
+                Thread.currentThread().interrupt();
+                Assert.fail(e.getMessage());
+            } finally {
+                if (available != null) {
+                    available.release();
+                }
+            }
+            return PluginDispatcher.createPluginDispatcherReturnType(null);
+        }
+
+        public int getRunCount() {
+            return runCount.get();
+        }
+    }
+
+    private static final class PluginOperationTest extends PaymentOperation {
+
+        protected PluginOperationTest(final PaymentAutomatonDAOHelper daoHelper, final GlobalLocker locker, final PluginDispatcher<OperationResult> paymentPluginDispatcher, final PaymentStateContext paymentStateContext) throws PaymentApiException {
+            super(locker, daoHelper, paymentPluginDispatcher, paymentStateContext);
+        }
+
+        @Override
+        protected PaymentTransactionInfoPlugin doCallSpecificOperationCallback() throws PaymentPluginApiException {
+            return null;
+        }
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
new file mode 100644
index 0000000..5acf9df
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryablePayment.java
@@ -0,0 +1,747 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core.sm;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+
+import javax.inject.Named;
+
+import org.joda.time.DateTime;
+import org.killbill.automaton.OperationResult;
+import org.killbill.automaton.State;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.osgi.api.OSGIServiceDescriptor;
+import org.killbill.billing.osgi.api.OSGIServiceRegistration;
+import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.core.PluginControlledPaymentProcessor;
+import org.killbill.billing.payment.dao.MockPaymentDao;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
+import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
+import org.killbill.billing.payment.provider.MockPaymentControlProviderPlugin;
+import org.killbill.billing.payment.retry.BaseRetryService.RetryServiceScheduler;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.billing.util.globallocker.LockerType;
+import org.killbill.commons.locker.GlobalLock;
+import org.killbill.commons.locker.GlobalLocker;
+import org.killbill.commons.locker.LockFailedException;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+
+import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
+import static org.killbill.billing.payment.glue.PaymentModule.RETRYABLE_NAMED;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+public class TestRetryablePayment extends PaymentTestSuiteNoDB {
+
+    @Inject
+    @Named(PaymentModule.STATE_MACHINE_PAYMENT)
+    private StateMachineConfig stateMachineConfig;
+    @Inject
+    @Named(PaymentModule.STATE_MACHINE_RETRY)
+    private StateMachineConfig retryStateMachineConfig;
+    @Inject
+    private PaymentDao paymentDao;
+    @Inject
+    private NonEntityDao nonEntityDao;
+    @Inject
+    private GlobalLocker locker;
+    @Inject
+    private OSGIServiceRegistration<PaymentPluginApi> pluginRegistry;
+    @Inject
+    private OSGIServiceRegistration<PaymentControlPluginApi> retryPluginRegistry;
+    @Inject
+    private TagInternalApi tagApi;
+    @Inject
+    private PaymentProcessor paymentProcessor;
+    @Inject
+    @Named(RETRYABLE_NAMED)
+    private RetryServiceScheduler retryServiceScheduler;
+    @Inject
+    @Named(PLUGIN_EXECUTOR_NAMED)
+    private ExecutorService executor;
+    @Inject
+    private PaymentStateMachineHelper paymentSMHelper;
+    @Inject
+    private RetryStateMachineHelper retrySMHelper;
+
+    private Account account;
+    private DateTime utcNow;
+
+    private final UUID paymentMethodId = UUID.randomUUID();
+    private final String paymentExternalKey = "foo";
+    private final String paymentTransactionExternalKey = "foobar";
+    private final BigDecimal amount = BigDecimal.ONE;
+    private final Currency currency = Currency.EUR;
+    private final ImmutableList<PluginProperty> emptyProperties = ImmutableList.of();
+    private final MockPaymentControlProviderPlugin mockRetryProviderPlugin = new MockPaymentControlProviderPlugin();
+
+    private byte [] EMPTY_PROPERTIES;
+    private MockRetryablePaymentAutomatonRunner runner;
+    private RetryablePaymentStateContext paymentStateContext;
+    private MockRetryAuthorizeOperationCallback mockRetryAuthorizeOperationCallback;
+    private PluginControlledPaymentProcessor processor;
+
+    @BeforeClass(groups = "fast")
+    public void beforeClass() throws Exception {
+        super.beforeClass();
+        account = testHelper.createTestAccount("lolo@gmail.com", false);
+        Mockito.when(accountInternalApi.getAccountById(Mockito.<UUID>any(), Mockito.<InternalTenantContext>any())).thenReturn(account);
+        //Mockito.when(nonEntityDao.retrieveIdFromObject(Mockito.<Long>any(), Mockito.<ObjectType>any())).thenReturn(uuid);
+        retryPluginRegistry.registerService(new OSGIServiceDescriptor() {
+            @Override
+            public String getPluginSymbolicName() {
+                return null;
+            }
+
+            @Override
+            public String getRegistrationName() {
+                return MockPaymentControlProviderPlugin.PLUGIN_NAME;
+            }
+        }, mockRetryProviderPlugin);
+        EMPTY_PROPERTIES = PluginPropertySerializer.serialize(ImmutableList.<PluginProperty>of());
+    }
+
+    @BeforeMethod(groups = "fast")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        ((MockPaymentDao) paymentDao).reset();
+        this.utcNow = clock.getUTCNow();
+
+        runner = new MockRetryablePaymentAutomatonRunner(
+                stateMachineConfig,
+                retryStateMachineConfig,
+                paymentDao,
+                locker,
+                pluginRegistry,
+                retryPluginRegistry,
+                clock,
+                tagApi,
+                paymentProcessor,
+                retryServiceScheduler,
+                paymentConfig,
+                executor,
+                paymentSMHelper,
+                retrySMHelper,
+                eventBus);
+
+        paymentStateContext =
+                new RetryablePaymentStateContext(MockPaymentControlProviderPlugin.PLUGIN_NAME,
+                                                       true,
+                                                       null,
+                                                       paymentExternalKey,
+                                                       paymentTransactionExternalKey,
+                                                       TransactionType.AUTHORIZE,
+                                                       account,
+                                                       paymentMethodId,
+                                                       amount,
+                                                       currency,
+                                                       emptyProperties,
+                                                       internalCallContext,
+                                                       callContext);
+
+        mockRetryAuthorizeOperationCallback =
+                new MockRetryAuthorizeOperationCallback(locker,
+                                                        runner.getPaymentPluginDispatcher(),
+                                                        paymentStateContext,
+                                                        null,
+                                                        runner.getRetryPluginRegistry(),
+                                                        paymentDao,
+                                                        clock);
+
+        processor = new PluginControlledPaymentProcessor(pluginRegistry,
+                                                         accountInternalApi,
+                                                         null,
+                                                         tagApi,
+                                                         paymentDao,
+                                                         nonEntityDao,
+                                                         locker,
+                                                         executor,
+                                                         runner,
+                                                         retrySMHelper,
+                                                         clock,
+                                                         cacheControllerDispatcher);
+
+    }
+
+    @Test(groups = "fast")
+    public void testInitToAborted() throws PaymentApiException {
+
+        mockRetryProviderPlugin
+                .setAborted(true)
+                .setNextRetryDate(null);
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(OperationResult.SUCCESS)
+                .setException(null);
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        runner.run(true,
+                   TransactionType.AUTHORIZE,
+                   account,
+                   paymentMethodId,
+                   null,
+                   paymentExternalKey,
+                   paymentTransactionExternalKey,
+                   amount,
+                   currency,
+                   emptyProperties,
+                   null,
+                   callContext,
+                   internalCallContext);
+
+        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
+        assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
+        assertEquals(pa.getStateName(), "ABORTED");
+        assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+    }
+
+    @Test(groups = "fast")
+    public void testInitToSuccessWithResSuccess() throws PaymentApiException {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(null);
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(OperationResult.SUCCESS)
+                .setException(null);
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        runner.run(true,
+                   TransactionType.AUTHORIZE,
+                   account,
+                   paymentMethodId,
+                   null,
+                   paymentExternalKey,
+                   paymentTransactionExternalKey,
+                   amount,
+                   currency,
+                   emptyProperties,
+                   null,
+                   callContext,
+                   internalCallContext);
+
+        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
+        assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
+        assertEquals(pa.getStateName(), "SUCCESS");
+        assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+    }
+
+    @Test(groups = "fast")
+    public void testInitToSuccessWithResFailure() throws PaymentApiException {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(null);
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(OperationResult.FAILURE)
+                .setException(null);
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        runner.run(true,
+                   TransactionType.AUTHORIZE,
+                   account,
+                   paymentMethodId,
+                   null,
+                   paymentExternalKey,
+                   paymentTransactionExternalKey,
+                   amount,
+                   currency,
+                   emptyProperties,
+                   null,
+                   callContext, internalCallContext);
+
+        final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
+        assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
+        assertEquals(pa.getStateName(), "SUCCESS");
+        assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+    }
+
+    @Test(groups = "fast")
+    public void testInitToSuccessWithPaymentExceptionNoRetries() {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(null);
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(null)
+                .setException(new PaymentApiException(ErrorCode.__UNKNOWN_ERROR_CODE, "bla bla"));
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        try {
+            runner.run(true,
+                       TransactionType.AUTHORIZE,
+                       account,
+                       paymentMethodId,
+                       null,
+                       paymentExternalKey,
+                       paymentTransactionExternalKey,
+                       amount,
+                       currency,
+                       emptyProperties,
+                       null,
+                       callContext, internalCallContext);
+
+            Assert.fail("Expected PaymentApiException...");
+
+        } catch (final PaymentApiException e) {
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
+            assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
+            assertEquals(pa.getStateName(), "ABORTED");
+            assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testInitToSuccessWithPaymentExceptionAndRetries() {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(new DateTime(clock.getUTCNow().plusDays(1)));
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(null)
+                .setException(new PaymentApiException(ErrorCode.__UNKNOWN_ERROR_CODE, "bla bla"));
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        try {
+            runner.run(true,
+                       TransactionType.AUTHORIZE,
+                       account,
+                       paymentMethodId,
+                       null,
+                       paymentExternalKey,
+                       paymentTransactionExternalKey,
+                       amount,
+                       currency,
+                       emptyProperties,
+                       null,
+                       callContext, internalCallContext);
+
+            Assert.fail("Expected PaymentApiException...");
+        } catch (final PaymentApiException e) {
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
+            assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
+            assertEquals(pa.getStateName(), "RETRIED");
+            assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testInitToSuccessWithRuntimeExceptionAndRetries() {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(new DateTime(clock.getUTCNow().plusDays(1)));
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(null)
+                .setException(new RuntimeException());
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        try {
+            runner.run(true,
+                       TransactionType.AUTHORIZE,
+                       account,
+                       paymentMethodId,
+                       null,
+                       paymentExternalKey,
+                       paymentTransactionExternalKey,
+                       amount,
+                       currency,
+                       emptyProperties,
+                       null,
+                       callContext, internalCallContext);
+
+            Assert.fail("Expected Exception...");
+        } catch (final PaymentApiException e) {
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
+            assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
+            assertEquals(pa.getStateName(), "RETRIED");
+            assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testInitToSuccessWithRuntimeExceptionNoRetry() {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(null);
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(null)
+                .setException(new RuntimeException());
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        try {
+            runner.run(true,
+                       TransactionType.AUTHORIZE,
+                       account,
+                       paymentMethodId,
+                       null,
+                       paymentExternalKey,
+                       paymentTransactionExternalKey,
+                       amount,
+                       currency,
+                       emptyProperties,
+                       null,
+                       callContext, internalCallContext);
+
+            Assert.fail("Expected Exception...");
+        } catch (final PaymentApiException e) {
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
+            assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
+            assertEquals(pa.getStateName(), "ABORTED");
+            assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testRetryToSuccessWithResSuccess() throws PaymentApiException {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(null);
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(OperationResult.SUCCESS)
+                .setException(null);
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        final State state = retrySMHelper.getRetriedState();
+        final UUID transactionId = UUID.randomUUID();
+        paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
+                                                                                 paymentExternalKey, transactionId, paymentTransactionExternalKey,
+                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES),
+                                                      internalCallContext
+                                                     );
+        runner.run(state,
+                   false,
+                   TransactionType.AUTHORIZE,
+                   account,
+                   paymentMethodId,
+                   null,
+                   paymentExternalKey,
+                   paymentTransactionExternalKey,
+                   amount,
+                   currency,
+                   emptyProperties,
+                   null,
+                   callContext,
+                   internalCallContext);
+
+        final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext);
+        assertEquals(pas.size(), 2);
+        final PaymentAttemptModelDao successfulAttempt = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+            @Override
+            public boolean apply(final PaymentAttemptModelDao input) {
+                return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                       input.getStateName().equals("SUCCESS");
+            }
+        }).orNull();
+        assertNotNull(successfulAttempt);
+    }
+
+    @Test(groups = "fast")
+    public void testRetryToSuccessWithPaymentApiExceptionAndRetry() {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(new DateTime().plusDays(1));
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(null)
+                .setException(new PaymentApiException(ErrorCode.__UNKNOWN_ERROR_CODE, "bla"));
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        final State state = retrySMHelper.getRetriedState();
+        final UUID transactionId = UUID.randomUUID();
+        paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
+                                                                                 paymentExternalKey, transactionId, paymentTransactionExternalKey,
+                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES),
+                                                      internalCallContext
+                                                     );
+
+        try {
+            runner.run(state,
+                       false,
+                       TransactionType.AUTHORIZE,
+                       account,
+                       paymentMethodId,
+                       null,
+                       paymentExternalKey,
+                       paymentTransactionExternalKey,
+                       amount,
+                       currency,
+                       emptyProperties,
+                       null,
+                       callContext,
+                       internalCallContext);
+
+            Assert.fail("Expecting paymentApiException...");
+        } catch (final PaymentApiException e) {
+            final PaymentAttemptModelDao pa = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext).get(0);
+            assertEquals(pa.getTransactionExternalKey(), paymentTransactionExternalKey);
+            assertEquals(pa.getStateName(), "RETRIED");
+            assertEquals(pa.getTransactionType(), TransactionType.AUTHORIZE);
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testRetryToSuccessWithPaymentApiExceptionAndNoRetry() {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(null);
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(null)
+                .setException(new PaymentApiException(ErrorCode.__UNKNOWN_ERROR_CODE, "bla"));
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        final State state = retrySMHelper.getRetriedState();
+        final UUID transactionId = UUID.randomUUID();
+        paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
+                                                                                 paymentExternalKey, transactionId, paymentTransactionExternalKey,
+                                                                                 TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES),
+                                                      internalCallContext
+                                                     );
+
+        try {
+            runner.run(state,
+                       false,
+                       TransactionType.AUTHORIZE,
+                       account,
+                       paymentMethodId,
+                       null,
+                       paymentExternalKey,
+                       paymentTransactionExternalKey,
+                       amount,
+                       currency,
+                       emptyProperties,
+                       null,
+                       callContext,
+                       internalCallContext);
+
+            Assert.fail("Expecting paymentApiException...");
+        } catch (final PaymentApiException e) {
+
+            final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext);
+            assertEquals(pas.size(), 2);
+
+            final PaymentAttemptModelDao failedAttempts = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+                @Override
+                public boolean apply(final PaymentAttemptModelDao input) {
+                    return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                           input.getStateName().equals("ABORTED");
+                }
+            }).orNull();
+            assertNotNull(failedAttempts);
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testRetryLogicFromRetriedStateWithSuccess() throws PaymentApiException {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(null);
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(OperationResult.SUCCESS)
+                .setException(null);
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        final State state = retrySMHelper.getRetriedState();
+        final UUID transactionId = UUID.randomUUID();
+        final UUID paymentId = UUID.randomUUID();
+        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
+                                                                    paymentExternalKey, transactionId, paymentTransactionExternalKey,
+                                                                    TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES);
+        paymentDao.insertPaymentAttemptWithProperties(attempt,
+                                                      internalCallContext
+                                                     );
+        paymentDao.insertPaymentWithFirstTransaction(new PaymentModelDao(paymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, paymentExternalKey),
+                                                           new PaymentTransactionModelDao(transactionId, attempt.getId(), paymentTransactionExternalKey, utcNow, utcNow, paymentId, TransactionType.AUTHORIZE, utcNow, TransactionStatus.PAYMENT_FAILURE, amount, currency, "bla", "foo"),
+                                                           internalCallContext);
+
+        processor.retryPaymentTransaction(attempt.getId(), MockPaymentControlProviderPlugin.PLUGIN_NAME, internalCallContext);
+
+        final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext);
+        assertEquals(pas.size(), 2);
+
+        final PaymentAttemptModelDao successfulAttempt = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+            @Override
+            public boolean apply(final PaymentAttemptModelDao input) {
+                return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                       input.getStateName().equals("SUCCESS");
+            }
+        }).orNull();
+        assertNotNull(successfulAttempt);
+    }
+
+    @Test(groups = "fast")
+    public void testRetryLogicFromRetriedStateWithPaymentApiException() {
+
+        mockRetryProviderPlugin
+                .setAborted(false)
+                .setNextRetryDate(null);
+
+        mockRetryAuthorizeOperationCallback
+                .setResult(null)
+                .setException(new PaymentApiException(ErrorCode.__UNKNOWN_ERROR_CODE, "foo"));
+
+        runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+              .setContext(paymentStateContext);
+
+        final State state = retrySMHelper.getRetriedState();
+        final UUID transactionId = UUID.randomUUID();
+        final UUID paymentId = UUID.randomUUID();
+        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
+                                                                    paymentExternalKey, transactionId, paymentTransactionExternalKey,
+                                                                    TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES);
+        paymentDao.insertPaymentAttemptWithProperties(attempt,
+                                                      internalCallContext
+                                                     );
+        paymentDao.insertPaymentWithFirstTransaction(new PaymentModelDao(paymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, paymentExternalKey),
+                                                           new PaymentTransactionModelDao(transactionId, attempt.getId(), paymentTransactionExternalKey, utcNow, utcNow, paymentId, TransactionType.AUTHORIZE, utcNow,
+                                                                                          TransactionStatus.PAYMENT_FAILURE, amount, currency, "bla", "foo"),
+                                                           internalCallContext
+                                                          );
+
+        processor.retryPaymentTransaction(attempt.getId(), MockPaymentControlProviderPlugin.PLUGIN_NAME, internalCallContext);
+
+        final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext);
+        assertEquals(pas.size(), 2);
+        final PaymentAttemptModelDao failedAttempt = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+            @Override
+            public boolean apply(final PaymentAttemptModelDao input) {
+                return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                       input.getStateName().equals("ABORTED");
+            }
+        }).orNull();
+        assertNotNull(failedAttempt);
+    }
+
+    @Test(groups = "fast")
+    public void testRetryLogicFromRetriedStateWithLockFailure() throws LockFailedException {
+
+        GlobalLock lock = null;
+        try {
+            // Grab lock so that operation later will fail...
+            lock = locker.lockWithNumberOfTries(LockerType.ACCOUNT_FOR_INVOICE_PAYMENTS.toString(), account.getExternalKey(), 1);
+
+            mockRetryProviderPlugin
+                    .setAborted(false)
+                    .setNextRetryDate(null);
+
+            mockRetryAuthorizeOperationCallback
+                    .setResult(OperationResult.SUCCESS)
+                    .setException(null);
+
+            runner.setOperationCallback(mockRetryAuthorizeOperationCallback)
+                  .setContext(paymentStateContext);
+
+            final State state = retrySMHelper.getRetriedState();
+            final UUID transactionId = UUID.randomUUID();
+            final UUID paymentId = UUID.randomUUID();
+            final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
+                                                                        paymentExternalKey, transactionId, paymentTransactionExternalKey,
+                                                                        TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES);
+            paymentDao.insertPaymentAttemptWithProperties(attempt,
+                                                          internalCallContext
+                                                         );
+            paymentDao.insertPaymentWithFirstTransaction(new PaymentModelDao(paymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, paymentExternalKey),
+                                                               new PaymentTransactionModelDao(transactionId, attempt.getId(), paymentTransactionExternalKey, utcNow, utcNow, paymentId, TransactionType.AUTHORIZE, utcNow,
+                                                                                              TransactionStatus.PAYMENT_FAILURE, amount, currency, "bla", "foo"),
+                                                               internalCallContext
+                                                              );
+
+            processor.retryPaymentTransaction(attempt.getId(), MockPaymentControlProviderPlugin.PLUGIN_NAME, internalCallContext);
+
+            final List<PaymentAttemptModelDao> pas = paymentDao.getPaymentAttemptByTransactionExternalKey(paymentTransactionExternalKey, internalCallContext);
+            assertEquals(pas.size(), 2);
+            final PaymentAttemptModelDao failedAttempt = Iterables.tryFind(pas, new Predicate<PaymentAttemptModelDao>() {
+                @Override
+                public boolean apply(final PaymentAttemptModelDao input) {
+                    return input.getTransactionType() == TransactionType.AUTHORIZE &&
+                           input.getStateName().equals("ABORTED");
+                }
+            }).orNull();
+            assertNotNull(failedAttempt);
+        } finally {
+            if (lock != null) {
+                lock.release();
+            }
+        }
+
+    }
+
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java
index 1277751..522f3c4 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,29 +21,32 @@ package org.killbill.billing.payment.core;
 import java.util.List;
 import java.util.UUID;
 
-import org.mockito.Mockito;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.payment.PaymentTestSuiteNoDB;
 import org.killbill.billing.payment.api.PaymentMethod;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.provider.ExternalPaymentProviderPlugin;
+import org.mockito.Mockito;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
 
 public class TestPaymentMethodProcessorNoDB extends PaymentTestSuiteNoDB {
 
     @Test(groups = "fast")
     public void testGetExternalPaymentProviderPlugin() throws Exception {
+        final Iterable<PluginProperty> properties = ImmutableList.<PluginProperty>of();
         final UUID accountId = UUID.randomUUID();
         final Account account = Mockito.mock(Account.class);
         Mockito.when(account.getId()).thenReturn(accountId);
         Mockito.when(account.getExternalKey()).thenReturn(accountId.toString());
 
-        Assert.assertEquals(paymentMethodProcessor.getPaymentMethods(account, false, internalCallContext).size(), 0);
+        Assert.assertEquals(paymentMethodProcessor.getPaymentMethods(account.getId(), false, properties, internalCallContext).size(), 0);
 
         // The first call should create the payment method
-        final ExternalPaymentProviderPlugin providerPlugin = paymentMethodProcessor.getExternalPaymentProviderPlugin(account, internalCallContext);
-        final List<PaymentMethod> paymentMethods = paymentMethodProcessor.getPaymentMethods(account, false, internalCallContext);
+        final ExternalPaymentProviderPlugin providerPlugin = paymentMethodProcessor.createPaymentMethodAndGetExternalPaymentProviderPlugin(UUID.randomUUID().toString(), account, properties, callContext, internalCallContext);
+        final List<PaymentMethod> paymentMethods = paymentMethodProcessor.getPaymentMethods(account.getId(), false, properties, internalCallContext);
         Assert.assertEquals(paymentMethods.size(), 1);
         Assert.assertEquals(paymentMethods.get(0).getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
         Assert.assertEquals(paymentMethods.get(0).getAccountId(), account.getId());
@@ -49,10 +54,10 @@ public class TestPaymentMethodProcessorNoDB extends PaymentTestSuiteNoDB {
         // The succeeding calls should not create any other payment method
         final UUID externalPaymentMethodId = paymentMethods.get(0).getId();
         for (int i = 0; i < 50; i++) {
-            final ExternalPaymentProviderPlugin foundProviderPlugin = paymentMethodProcessor.getExternalPaymentProviderPlugin(account, internalCallContext);
-            Assert.assertNotNull (foundProviderPlugin);
+            final ExternalPaymentProviderPlugin foundProviderPlugin = paymentMethodProcessor.createPaymentMethodAndGetExternalPaymentProviderPlugin(UUID.randomUUID().toString(), account, properties, callContext, internalCallContext);
+            Assert.assertNotNull(foundProviderPlugin);
 
-            final List<PaymentMethod> foundPaymentMethods = paymentMethodProcessor.getPaymentMethods(account, false, internalCallContext);
+            final List<PaymentMethod> foundPaymentMethods = paymentMethodProcessor.getPaymentMethods(account.getId(), false, properties, internalCallContext);
             Assert.assertEquals(foundPaymentMethods.size(), 1);
             Assert.assertEquals(foundPaymentMethods.get(0).getPluginName(), ExternalPaymentProviderPlugin.PLUGIN_NAME);
             Assert.assertEquals(foundPaymentMethods.get(0).getAccountId(), account.getId());
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorRefreshWithDB.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorRefreshWithDB.java
index b4b4bd9..8a61a5e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorRefreshWithDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentMethodProcessorRefreshWithDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,79 +21,76 @@ package org.killbill.billing.payment.core;
 import java.util.List;
 import java.util.UUID;
 
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
 import org.killbill.billing.payment.api.PaymentMethod;
-import org.killbill.billing.payment.api.PaymentMethodKVInfo;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.dao.PaymentMethodModelDao;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
 
 public class TestPaymentMethodProcessorRefreshWithDB extends PaymentTestSuiteWithEmbeddedDB {
 
+    private static final ImmutableList<PluginProperty> PLUGIN_PROPERTIES = ImmutableList.<PluginProperty>of();
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
-        getPluginApi().resetPaymentMethods(null, null);
+        getPluginApi().resetPaymentMethods(null, null, PLUGIN_PROPERTIES, callContext);
     }
 
     @Test(groups = "slow")
     public void testRefreshWithNewPaymentMethod() throws Exception {
-
         final Account account = testHelper.createTestAccount("foo@bar.com", true);
-        Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, callContext).size(), 1);
+        Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, PLUGIN_PROPERTIES, callContext).size(), 1);
         final UUID existingPMId = account.getPaymentMethodId();
 
         // Add new payment in plugin directly
         final UUID newPmId = UUID.randomUUID();
-        getPluginApi().addPaymentMethod(account.getId(), newPmId, new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), false, ImmutableList.<PaymentMethodKVInfo>of()), false, callContext);
+        getPluginApi().addPaymentMethod(account.getId(), newPmId, new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), false, ImmutableList.<PluginProperty>of()), false, PLUGIN_PROPERTIES, callContext);
 
         // Verify that the refresh does indeed show 2 PMs
-        final List<PaymentMethod> methods = paymentMethodProcessor.refreshPaymentMethods(MockPaymentProviderPlugin.PLUGIN_NAME, account, internalCallContext);
+        final List<PaymentMethod> methods = paymentMethodProcessor.refreshPaymentMethods(MockPaymentProviderPlugin.PLUGIN_NAME, account, PLUGIN_PROPERTIES, callContext, internalCallContext);
         Assert.assertEquals(methods.size(), 2);
         checkPaymentMethodExistsWithStatus(methods, existingPMId, true);
         checkPaymentMethodExistsWithStatus(methods, newPmId, true);
     }
 
-
     @Test(groups = "slow")
     public void testRefreshWithDeletedPaymentMethod() throws Exception {
-
         final Account account = testHelper.createTestAccount("super@bar.com", true);
-        Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, callContext).size(), 1);
+        Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, PLUGIN_PROPERTIES, callContext).size(), 1);
         final UUID firstPmId = account.getPaymentMethodId();
 
-        final UUID secondPmId = paymentApi.addPaymentMethod(MockPaymentProviderPlugin.PLUGIN_NAME, account, true, new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), false, null), callContext);
-        Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, callContext).size(), 2);
-        Assert.assertEquals(paymentApi.getPaymentMethods(account, false, callContext).size(), 2);
+        String secondPaymentMethodExternalKey = UUID.randomUUID().toString();
+        final UUID secondPmId = paymentApi.addPaymentMethod(account, secondPaymentMethodExternalKey, MockPaymentProviderPlugin.PLUGIN_NAME, true, new DefaultNoOpPaymentMethodPlugin(secondPaymentMethodExternalKey, false, null), PLUGIN_PROPERTIES, callContext);
+        Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, PLUGIN_PROPERTIES, callContext).size(), 2);
+        Assert.assertEquals(paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext).size(), 2);
 
         // Remove second PM from plugin
-        getPluginApi().deletePaymentMethod(account.getId(), secondPmId, callContext);
-        Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, callContext).size(), 1);
-        Assert.assertEquals(paymentApi.getPaymentMethods(account, false, callContext).size(), 2);
+        getPluginApi().deletePaymentMethod(account.getId(), secondPmId, PLUGIN_PROPERTIES, callContext);
+        Assert.assertEquals(getPluginApi().getPaymentMethods(account.getId(), true, PLUGIN_PROPERTIES, callContext).size(), 1);
+        Assert.assertEquals(paymentApi.getAccountPaymentMethods(account.getId(), false, PLUGIN_PROPERTIES, callContext).size(), 2);
 
         // Verify that the refresh sees that PM as being deleted now
-        final List<PaymentMethod> methods = paymentMethodProcessor.refreshPaymentMethods(MockPaymentProviderPlugin.PLUGIN_NAME, account, internalCallContext);
+        final List<PaymentMethod> methods = paymentMethodProcessor.refreshPaymentMethods(MockPaymentProviderPlugin.PLUGIN_NAME, account, PLUGIN_PROPERTIES, callContext, internalCallContext);
         Assert.assertEquals(methods.size(), 1);
         checkPaymentMethodExistsWithStatus(methods, firstPmId, true);
 
-        PaymentMethodModelDao deletedPMModel =  paymentDao.getPaymentMethodIncludedDeleted(secondPmId, internalCallContext);
+        final PaymentMethodModelDao deletedPMModel = paymentDao.getPaymentMethodIncludedDeleted(secondPmId, internalCallContext);
         Assert.assertNotNull(deletedPMModel);
         Assert.assertFalse(deletedPMModel.isActive());
     }
 
-
-    private void checkPaymentMethodExistsWithStatus(final List<PaymentMethod> methods, UUID expectedPaymentMethodId, boolean expectedActive) {
+    private void checkPaymentMethodExistsWithStatus(final List<PaymentMethod> methods, final UUID expectedPaymentMethodId, final boolean expectedActive) {
         PaymentMethod foundPM = null;
-        for (PaymentMethod cur : methods) {
+        for (final PaymentMethod cur : methods) {
             if (cur.getId().equals(expectedPaymentMethodId)) {
                 foundPM = cur;
                 break;
@@ -101,7 +100,6 @@ public class TestPaymentMethodProcessorRefreshWithDB extends PaymentTestSuiteWit
         Assert.assertEquals(foundPM.isActive().booleanValue(), expectedActive);
     }
 
-
     private PaymentPluginApi getPluginApi() {
         final PaymentPluginApi pluginApi = registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME);
         Assert.assertNotNull(pluginApi);
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
new file mode 100644
index 0000000..caa98f1
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/core/TestPaymentProcessor.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.core;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.events.BusInternalEvent;
+import org.killbill.billing.events.PaymentErrorInternalEvent;
+import org.killbill.billing.events.PaymentInfoInternalEvent;
+import org.killbill.billing.events.PaymentPluginErrorInternalEvent;
+import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.eventbus.Subscribe;
+import com.jayway.awaitility.Awaitility;
+
+import static java.math.BigDecimal.ZERO;
+
+public class TestPaymentProcessor extends PaymentTestSuiteWithEmbeddedDB {
+
+    private static final boolean SHOULD_LOCK_ACCOUNT = true;
+    private static final ImmutableList<PluginProperty> PLUGIN_PROPERTIES = ImmutableList.<PluginProperty>of();
+    private static final BigDecimal FIVE = new BigDecimal("5");
+    private static final BigDecimal TEN = new BigDecimal("10");
+    private static final Currency CURRENCY = Currency.BTC;
+
+    private PaymentBusListener paymentBusListener;
+    private Account account;
+
+    @BeforeMethod(groups = "slow")
+    public void setUp() throws Exception {
+        account = testHelper.createTestAccount(UUID.randomUUID().toString(), true);
+        internalCallContext = new InternalCallContext(internalCallContext, 1L);
+
+        paymentBusListener = new PaymentBusListener();
+        eventBus.register(paymentBusListener);
+    }
+
+    @Test(groups = "slow")
+    public void testClassicFlow() throws Exception {
+        final String paymentExternalKey = UUID.randomUUID().toString();
+
+        // AUTH pre-3DS
+        final String authorizationKey = UUID.randomUUID().toString();
+        final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, authorizationKey,
+                                                                           SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(authorization, paymentExternalKey, TEN, ZERO, ZERO, 1);
+        final UUID paymentId = authorization.getId();
+        verifyPaymentTransaction(authorization.getTransactions().get(0), authorizationKey, TransactionType.AUTHORIZE, TEN, paymentId);
+        paymentBusListener.verify(1, account.getId(), paymentId, TEN);
+
+        // AUTH post-3DS
+        final String authorizationPost3DSKey = UUID.randomUUID().toString();
+        final Payment authorizationPost3DS = paymentProcessor.createAuthorization(true, null, account, null, paymentId, TEN, CURRENCY, paymentExternalKey, authorizationPost3DSKey,
+                                                                                  SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(authorizationPost3DS, paymentExternalKey, TEN, ZERO, ZERO, 2);
+        verifyPaymentTransaction(authorizationPost3DS.getTransactions().get(1), authorizationPost3DSKey, TransactionType.AUTHORIZE, TEN, paymentId);
+        paymentBusListener.verify(2, account.getId(), paymentId, TEN);
+
+        // CAPTURE
+        final String capture1Key = UUID.randomUUID().toString();
+        final Payment partialCapture1 = paymentProcessor.createCapture(true, null, account, paymentId, FIVE, CURRENCY, capture1Key,
+                                                                       SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(partialCapture1, paymentExternalKey, TEN, FIVE, ZERO, 3);
+        verifyPaymentTransaction(partialCapture1.getTransactions().get(2), capture1Key, TransactionType.CAPTURE, FIVE, paymentId);
+        paymentBusListener.verify(3, account.getId(), paymentId, FIVE);
+
+        // CAPTURE
+        final String capture2Key = UUID.randomUUID().toString();
+        final Payment partialCapture2 = paymentProcessor.createCapture(true, null, account, paymentId, FIVE, CURRENCY, capture2Key,
+                                                                       SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(partialCapture2, paymentExternalKey, TEN, TEN, ZERO, 4);
+        verifyPaymentTransaction(partialCapture2.getTransactions().get(3), capture2Key, TransactionType.CAPTURE, FIVE, paymentId);
+        paymentBusListener.verify(4, account.getId(), paymentId, FIVE);
+
+        // REFUND
+        final String refund1Key = UUID.randomUUID().toString();
+        final Payment partialRefund1 = paymentProcessor.createRefund(true, null, account, paymentId, FIVE, CURRENCY, refund1Key,
+                                                                     SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(partialRefund1, paymentExternalKey, TEN, TEN, FIVE, 5);
+        verifyPaymentTransaction(partialRefund1.getTransactions().get(4), refund1Key, TransactionType.REFUND, FIVE, paymentId);
+        paymentBusListener.verify(5, account.getId(), paymentId, FIVE);
+
+        // REFUND
+        final String refund2Key = UUID.randomUUID().toString();
+        final Payment partialRefund2 = paymentProcessor.createRefund(true, null, account, paymentId, FIVE, CURRENCY, refund2Key,
+                                                                     SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(partialRefund2, paymentExternalKey, TEN, TEN, TEN, 6);
+        verifyPaymentTransaction(partialRefund2.getTransactions().get(5), refund2Key, TransactionType.REFUND, FIVE, paymentId);
+        paymentBusListener.verify(6, account.getId(), paymentId, FIVE);
+    }
+
+    @Test(groups = "slow")
+    public void testVoid() throws Exception {
+        final String paymentExternalKey = UUID.randomUUID().toString();
+
+        // AUTH
+        final String authorizationKey = UUID.randomUUID().toString();
+        final Payment authorization = paymentProcessor.createAuthorization(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, authorizationKey,
+                                                                           SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(authorization, paymentExternalKey, TEN, ZERO, ZERO, 1);
+        final UUID paymentId = authorization.getId();
+        verifyPaymentTransaction(authorization.getTransactions().get(0), authorizationKey, TransactionType.AUTHORIZE, TEN, paymentId);
+        paymentBusListener.verify(1, account.getId(), paymentId, TEN);
+
+        // VOID
+        final String voidKey = UUID.randomUUID().toString();
+        final Payment voidTransaction = paymentProcessor.createVoid(true, null, account, paymentId, voidKey,
+                                                                    SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(voidTransaction, paymentExternalKey, TEN, ZERO, ZERO, 2);
+        verifyPaymentTransaction(voidTransaction.getTransactions().get(1), voidKey, TransactionType.VOID, null, paymentId);
+        paymentBusListener.verify(2, account.getId(), paymentId, null);
+    }
+
+    @Test(groups = "slow")
+    public void testPurchase() throws Exception {
+        final String paymentExternalKey = UUID.randomUUID().toString();
+
+        // PURCHASE
+        final String purchaseKey = UUID.randomUUID().toString();
+        final Payment purchase = paymentProcessor.createPurchase(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, purchaseKey,
+                                                                 SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(purchase, paymentExternalKey, ZERO, ZERO, ZERO, 1);
+        final UUID paymentId = purchase.getId();
+        verifyPaymentTransaction(purchase.getTransactions().get(0), purchaseKey, TransactionType.PURCHASE, TEN, paymentId);
+        paymentBusListener.verify(1, account.getId(), paymentId, TEN);
+    }
+
+    @Test(groups = "slow")
+    public void testCredit() throws Exception {
+        final String paymentExternalKey = UUID.randomUUID().toString();
+
+        // CREDIT
+        final String creditKey = UUID.randomUUID().toString();
+        final Payment purchase = paymentProcessor.createCredit(true, null, account, null, null, TEN, CURRENCY, paymentExternalKey, creditKey,
+                                                               SHOULD_LOCK_ACCOUNT, PLUGIN_PROPERTIES, callContext, internalCallContext);
+        verifyPayment(purchase, paymentExternalKey, ZERO, ZERO, ZERO, 1);
+        final UUID paymentId = purchase.getId();
+        verifyPaymentTransaction(purchase.getTransactions().get(0), creditKey, TransactionType.CREDIT, TEN, paymentId);
+        paymentBusListener.verify(1, account.getId(), paymentId, TEN);
+    }
+
+    private void verifyPayment(final Payment payment, final String paymentExternalKey,
+                               final BigDecimal authAmount, final BigDecimal capturedAmount, final BigDecimal refundedAmount,
+                               final int transactionsSize) {
+        Assert.assertEquals(payment.getAccountId(), account.getId());
+        // We cannot assume the number to be 1 here as the auto_increment implementation
+        // depends on the database. On h2, it is implemented as a sequence, and the payment number
+        // would be 33, 34, 35, etc. depending on the test
+        // See also http://h2database.com/html/grammar.html#create_sequence
+        Assert.assertTrue(payment.getPaymentNumber() > 0);
+        Assert.assertEquals(payment.getExternalKey(), paymentExternalKey);
+        Assert.assertEquals(payment.getAuthAmount().compareTo(authAmount), 0);
+        Assert.assertEquals(payment.getCapturedAmount().compareTo(capturedAmount), 0);
+        Assert.assertEquals(payment.getRefundedAmount().compareTo(refundedAmount), 0);
+        Assert.assertEquals(payment.getCurrency(), CURRENCY);
+        Assert.assertEquals(payment.getTransactions().size(), transactionsSize);
+    }
+
+    private void verifyPaymentTransaction(final PaymentTransaction paymentTransaction, final String paymentTransactionExternalKey,
+                                          final TransactionType transactionType, @Nullable final BigDecimal amount, final UUID paymentId) {
+        Assert.assertEquals(paymentTransaction.getPaymentId(), paymentId);
+        Assert.assertEquals(paymentTransaction.getExternalKey(), paymentTransactionExternalKey);
+        Assert.assertEquals(paymentTransaction.getTransactionType(), transactionType);
+        if (amount == null) {
+            Assert.assertNull(paymentTransaction.getAmount());
+            Assert.assertNull(paymentTransaction.getCurrency());
+        } else {
+            Assert.assertEquals(paymentTransaction.getAmount().compareTo(amount), 0);
+            Assert.assertEquals(paymentTransaction.getCurrency(), CURRENCY);
+        }
+    }
+
+    private static final class PaymentBusListener {
+
+        private final List<PaymentInfoInternalEvent> paymentInfoEvents = new LinkedList<PaymentInfoInternalEvent>();
+        private final Collection<BusInternalEvent> paymentErrorEvents = new LinkedList<BusInternalEvent>();
+        private final Collection<BusInternalEvent> paymentPluginErrorEvents = new LinkedList<BusInternalEvent>();
+
+        @Subscribe
+        public void paymentInfo(final PaymentInfoInternalEvent event) {
+            paymentInfoEvents.add(event);
+        }
+
+        @Subscribe
+        public void paymentError(final PaymentErrorInternalEvent event) {
+            paymentErrorEvents.add(event);
+        }
+
+        @Subscribe
+        public void paymentPluginError(final PaymentPluginErrorInternalEvent event) {
+            paymentPluginErrorEvents.add(event);
+        }
+
+        public void verify(final int eventNb, final UUID accountId, final UUID paymentId, final BigDecimal amount) throws Exception {
+            Awaitility.await()
+                      .until(new Callable<Boolean>() {
+                          @Override
+                          public Boolean call() throws Exception {
+                              return paymentInfoEvents.size() == eventNb;
+                          }
+                      });
+            Assert.assertEquals(paymentErrorEvents.size(), 0);
+            Assert.assertEquals(paymentPluginErrorEvents.size(), 0);
+
+            verify(paymentInfoEvents.get(eventNb - 1), accountId, paymentId, amount);
+        }
+
+        private void verify(final PaymentInfoInternalEvent event, final UUID accountId, final UUID paymentId, @Nullable final BigDecimal amount) {
+            Assert.assertEquals(event.getPaymentId(), paymentId);
+            Assert.assertEquals(event.getAccountId(), accountId);
+            if (amount == null) {
+                Assert.assertEquals(event.getAmount().compareTo(BigDecimal.ZERO), 0);
+            } else {
+                Assert.assertEquals(event.getAmount().compareTo(amount), 0);
+            }
+            Assert.assertEquals(event.getStatus(), TransactionStatus.SUCCESS);
+        }
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
index 892bc51..2207741 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
@@ -18,7 +18,6 @@ package org.killbill.billing.payment.dao;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -26,80 +25,107 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.payment.api.PaymentStatus;
-import org.killbill.billing.payment.api.RefundStatus;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.util.entity.Pagination;
 
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 public class MockPaymentDao implements PaymentDao {
 
     private final Map<UUID, PaymentModelDao> payments = new HashMap<UUID, PaymentModelDao>();
+    private final Map<UUID, PaymentTransactionModelDao> transactions = new HashMap<UUID, PaymentTransactionModelDao>();
     private final Map<UUID, PaymentAttemptModelDao> attempts = new HashMap<UUID, PaymentAttemptModelDao>();
 
-    @Override
-    public PaymentModelDao insertPaymentWithFirstAttempt(final PaymentModelDao paymentInfo, final PaymentAttemptModelDao attempt,
-                                                         final InternalCallContext context) {
+    public void reset() {
         synchronized (this) {
-            payments.put(paymentInfo.getId(), paymentInfo);
-            attempts.put(attempt.getId(), attempt);
+            payments.clear();
+            transactions.clear();
+            attempts.clear();
+        }
+    }
+
+    @Override
+    public int failOldPendingTransactions(final TransactionStatus newTransactionStatus, final DateTime createdBeforeDate, final InternalCallContext context) {
+        int result = 0;
+        synchronized (transactions) {
+            for (PaymentTransactionModelDao cur : transactions.values()) {
+                cur.setTransactionStatus(newTransactionStatus);
+                result++;
+            }
         }
-        return paymentInfo;
+        return result;
     }
 
     @Override
-    public PaymentAttemptModelDao updatePaymentWithNewAttempt(final UUID paymentId, final PaymentAttemptModelDao attempt, final InternalCallContext context) {
+    public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final InternalCallContext context) {
         synchronized (this) {
             attempts.put(attempt.getId(), attempt);
+            return attempt;
         }
-        return attempt;
     }
 
     @Override
-    public void updatePaymentAndAttemptOnCompletion(final UUID paymentId, final PaymentStatus paymentStatus,
-                                                    BigDecimal processedAmount, Currency processedCurrency,
-                                                    final UUID attemptId, final String gatewayErrorCode,
-                                                    final String gatewayErrorMsg,
-                                                    final InternalCallContext context) {
+    public void updatePaymentAttempt(final UUID paymentAttemptId, final UUID transactionId, final String state, final InternalCallContext context) {
+        boolean success = false;
         synchronized (this) {
-            final PaymentModelDao entry = payments.remove(paymentId);
-            if (entry != null) {
-                payments.put(paymentId, new PaymentModelDao(entry, paymentStatus));
-            }
-            final PaymentAttemptModelDao tmp = attempts.remove(attemptId);
-            if (tmp != null) {
-                attempts.put(attemptId, new PaymentAttemptModelDao(tmp, paymentStatus, gatewayErrorCode, gatewayErrorMsg));
+            for (PaymentAttemptModelDao cur : attempts.values()) {
+                if (cur.getId().equals(paymentAttemptId)) {
+                    cur.setStateName(state);
+                    cur.setTransactionId(transactionId);
+                    success = true;
+                    break;
+                }
             }
         }
+        if (!success) {
+            throw new RuntimeException("Could not find attempt " + paymentAttemptId);
+        }
     }
 
     @Override
-    public PaymentAttemptModelDao getPaymentAttempt(final UUID attemptId, final InternalTenantContext context) {
-        return attempts.get(attemptId);
+    public List<PaymentAttemptModelDao> getPaymentAttemptsByState(final String stateName, final DateTime createdBeforeDate, final InternalTenantContext context) {
+        return null;
     }
 
     @Override
-    public List<PaymentModelDao> getPaymentsForInvoice(final UUID invoiceId, final InternalTenantContext context) {
-        final List<PaymentModelDao> result = new ArrayList<PaymentModelDao>();
+    public List<PaymentAttemptModelDao> getPaymentAttempts(final String paymentExternalKey, final InternalTenantContext context) {
         synchronized (this) {
-            for (final PaymentModelDao cur : payments.values()) {
-                if (cur.getInvoiceId().equals(invoiceId)) {
+            final List<PaymentAttemptModelDao> result = new ArrayList<PaymentAttemptModelDao>();
+            for (PaymentAttemptModelDao cur : attempts.values()) {
+                if (cur.getPaymentExternalKey().equals(paymentExternalKey)) {
                     result.add(cur);
                 }
             }
+            return result;
         }
-        return result;
     }
 
     @Override
-    public List<PaymentModelDao> getPaymentsForAccount(final UUID accountId, final InternalTenantContext context) {
-        final List<PaymentModelDao> result = new ArrayList<PaymentModelDao>();
+    public List<PaymentAttemptModelDao> getPaymentAttemptByTransactionExternalKey(final String transactionExternalKey, final InternalTenantContext context) {
         synchronized (this) {
-            for (final PaymentModelDao cur : payments.values()) {
-                if (cur.getAccountId().equals(accountId)) {
+            final List<PaymentAttemptModelDao> result = new ArrayList<PaymentAttemptModelDao>();
+            for (PaymentAttemptModelDao cur : attempts.values()) {
+                if (cur.getTransactionExternalKey().equals(transactionExternalKey)) {
+                    result.add(cur);
+                }
+            }
+            return result;
+        }
+    }
+
+    @Override
+    public List<PaymentTransactionModelDao> getPaymentTransactionsByExternalKey(final String transactionExternalKey, final InternalTenantContext context) {
+        final List<PaymentTransactionModelDao> result = new ArrayList<PaymentTransactionModelDao>();
+        synchronized (this) {
+            for (PaymentTransactionModelDao cur : transactions.values()) {
+                if (cur.getTransactionExternalKey().equals(transactionExternalKey)) {
                     result.add(cur);
                 }
             }
@@ -108,121 +134,215 @@ public class MockPaymentDao implements PaymentDao {
     }
 
     @Override
+    public PaymentModelDao getPaymentByExternalKey(final String externalKey, final InternalTenantContext context) {
+        synchronized (this) {
+            for (PaymentModelDao cur : payments.values()) {
+                if (cur.getExternalKey().equals(externalKey)) {
+                    return cur;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
     public Pagination<PaymentModelDao> getPayments(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
+        return null;
+    }
+
+    @Override
+    public Pagination<PaymentModelDao> searchPayments(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public PaymentModelDao getPayment(final UUID paymentId, final InternalTenantContext context) {
-        return payments.get(paymentId);
+    public PaymentModelDao insertPaymentWithFirstTransaction(final PaymentModelDao payment, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {
+        synchronized (this) {
+            payments.put(payment.getId(), payment);
+            transactions.put(paymentTransaction.getId(), paymentTransaction);
+        }
+        return payment;
     }
 
     @Override
-    public List<PaymentAttemptModelDao> getAttemptsForPayment(final UUID paymentId, final InternalTenantContext context) {
-        final List<PaymentAttemptModelDao> result = new ArrayList<PaymentAttemptModelDao>();
+    public PaymentTransactionModelDao updatePaymentWithNewTransaction(final UUID paymentId, final PaymentTransactionModelDao paymentTransaction, final InternalCallContext context) {
         synchronized (this) {
-            for (final PaymentAttemptModelDao cur : attempts.values()) {
-                if (cur.getPaymentId().equals(paymentId)) {
-                    result.add(cur);
-                }
-            }
+            transactions.put(paymentTransaction.getId(), paymentTransaction);
         }
-        return result;
+        return paymentTransaction;
     }
 
-    private final List<PaymentMethodModelDao> paymentMethods = new LinkedList<PaymentMethodModelDao>();
+    @Override
+    public void updatePaymentAndTransactionOnCompletion(final UUID accountId, final UUID paymentId, final TransactionType transactionType,
+                                                        final String currentPaymentStateName, final String lastSuccessPaymentStateName, final UUID transactionId,
+                                                        final TransactionStatus paymentStatus, final BigDecimal processedAmount, final Currency processedCurrency,
+                                                        final String gatewayErrorCode, final String gatewayErrorMsg, final InternalCallContext context) {
+        synchronized (this) {
+            final PaymentModelDao payment = payments.get(paymentId);
+            if (payment != null) {
+                payment.setStateName(currentPaymentStateName);
+            }
+            final PaymentTransactionModelDao transaction = transactions.get(transactionId);
+            if (transaction != null) {
+                transaction.setTransactionStatus(paymentStatus);
+                transaction.setProcessedAmount(processedAmount);
+                transaction.setProcessedCurrency(processedCurrency);
+                transaction.setGatewayErrorCode(gatewayErrorCode);
+                transaction.setGatewayErrorMsg(gatewayErrorMsg);
+            }
+        }
+    }
 
     @Override
-    public PaymentMethodModelDao insertPaymentMethod(final PaymentMethodModelDao paymentMethod, final InternalCallContext context) {
-        paymentMethods.add(paymentMethod);
-        return paymentMethod;
+    public PaymentModelDao getPayment(final UUID paymentId, final InternalTenantContext context) {
+        synchronized (this) {
+            return payments.get(paymentId);
+        }
     }
 
     @Override
-    public PaymentMethodModelDao getPaymentMethod(final UUID paymentMethodId, final InternalTenantContext context) {
-        for (final PaymentMethodModelDao cur : paymentMethods) {
-            if (cur.getId().equals(paymentMethodId)) {
-                return cur;
-            }
+    public PaymentTransactionModelDao getPaymentTransaction(final UUID transactionId, final InternalTenantContext context) {
+        synchronized (this) {
+            return transactions.get(transactionId);
         }
-        return null;
     }
 
     @Override
-    public List<PaymentMethodModelDao> getPaymentMethods(final UUID accountId, final InternalTenantContext context) {
-        final List<PaymentMethodModelDao> result = new ArrayList<PaymentMethodModelDao>();
-        for (final PaymentMethodModelDao cur : paymentMethods) {
-            if (cur.getAccountId().equals(accountId)) {
-                result.add(cur);
-            }
+    public List<PaymentModelDao> getPaymentsForAccount(final UUID accountId, final InternalTenantContext context) {
+        synchronized (this) {
+            return ImmutableList.copyOf(Iterables.filter(payments.values(), new Predicate<PaymentModelDao>() {
+                @Override
+                public boolean apply(final PaymentModelDao input) {
+                    return input.getAccountId().equals(accountId);
+                }
+            }));
         }
-        return result;
     }
 
     @Override
-    public Pagination<PaymentMethodModelDao> getPaymentMethods(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
-        throw new UnsupportedOperationException();
+    public List<PaymentTransactionModelDao> getTransactionsForAccount(final UUID accountId, final InternalTenantContext context) {
+        synchronized (this) {
+            return ImmutableList.copyOf(Iterables.filter(transactions.values(), new Predicate<PaymentTransactionModelDao>() {
+                @Override
+                public boolean apply(final PaymentTransactionModelDao input) {
+                    final PaymentModelDao payment = payments.get(input.getPaymentId());
+                    if (payment != null) {
+                        return payment.getAccountId().equals(accountId);
+                    } else {
+                        return false;
+                    }
+                }
+            }));
+        }
     }
 
     @Override
-    public void deletedPaymentMethod(final UUID paymentMethodId, final InternalCallContext context) {
-        final Iterator<PaymentMethodModelDao> it = paymentMethods.iterator();
-        while (it.hasNext()) {
-            final PaymentMethodModelDao cur = it.next();
-            if (cur.getId().equals(paymentMethodId)) {
-                it.remove();
-                break;
-            }
+    public List<PaymentTransactionModelDao> getTransactionsForPayment(final UUID paymentId, final InternalTenantContext context) {
+        synchronized (this) {
+            return ImmutableList.copyOf(Iterables.filter(transactions.values(), new Predicate<PaymentTransactionModelDao>() {
+                @Override
+                public boolean apply(final PaymentTransactionModelDao input) {
+                    return input.getPaymentId().equals(paymentId);
+                }
+            }));
         }
     }
 
     @Override
-    public List<PaymentMethodModelDao> refreshPaymentMethods(final UUID accountId, final String pluginName, final List<PaymentMethodModelDao> paymentMethods, final InternalCallContext context) {
-        return ImmutableList.<PaymentMethodModelDao>of();
+    public PaymentAttemptModelDao getPaymentAttempt(final UUID attemptId, final InternalTenantContext context) {
+        synchronized (this) {
+            return Iterables.tryFind(attempts.values(), new Predicate<PaymentAttemptModelDao>() {
+                @Override
+                public boolean apply(final PaymentAttemptModelDao input) {
+                    return input.getId().equals(attemptId);
+                }
+            }).orNull();
+        }
     }
 
+    private final List<PaymentMethodModelDao> paymentMethods = new LinkedList<PaymentMethodModelDao>();
+
     @Override
-    public void undeletedPaymentMethod(final UUID paymentMethodId, final InternalCallContext context) {
-        throw new UnsupportedOperationException();
+    public PaymentMethodModelDao insertPaymentMethod(final PaymentMethodModelDao paymentMethod, final InternalCallContext context) {
+        synchronized (this) {
+            paymentMethods.add(paymentMethod);
+            return paymentMethod;
+        }
     }
 
     @Override
-    public RefundModelDao insertRefund(final RefundModelDao refundInfo, final InternalCallContext context) {
-        return null;
+    public PaymentMethodModelDao getPaymentMethod(final UUID paymentMethodId, final InternalTenantContext context) {
+        synchronized (this) {
+            for (final PaymentMethodModelDao cur : paymentMethods) {
+                if (cur.getId().equals(paymentMethodId)) {
+                    return cur;
+                }
+            }
+            return null;
+        }
     }
 
     @Override
-    public void updateRefundStatus(final UUID refundId, final RefundStatus status, final BigDecimal processedAmount, final Currency processedCurrency, final InternalCallContext context) {
-        return;
+    public PaymentMethodModelDao getPaymentMethodByExternalKey(final String paymentMethodExternalKey, final InternalTenantContext context) {
+        synchronized (this) {
+            for (final PaymentMethodModelDao cur : paymentMethods) {
+                if (cur.getExternalKey().equals(paymentMethodExternalKey)) {
+                    return cur;
+                }
+            }
+            return null;
+        }
     }
 
     @Override
-    public Pagination<RefundModelDao> getRefunds(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
-        throw new UnsupportedOperationException();
+    public List<PaymentMethodModelDao> getPaymentMethods(final UUID accountId, final InternalTenantContext context) {
+        synchronized (this) {
+            final List<PaymentMethodModelDao> result = new ArrayList<PaymentMethodModelDao>();
+            for (final PaymentMethodModelDao cur : paymentMethods) {
+                if (cur.getAccountId().equals(accountId)) {
+                    result.add(cur);
+                }
+            }
+            return result;
+        }
     }
 
     @Override
-    public RefundModelDao getRefund(final UUID refundId, final InternalTenantContext context) {
-        return null;
+    public Pagination<PaymentMethodModelDao> getPaymentMethods(final String pluginName, final Long offset, final Long limit, final InternalTenantContext context) {
+        throw new UnsupportedOperationException();
     }
 
     @Override
-    public List<RefundModelDao> getRefundsForPayment(final UUID paymentId, final InternalTenantContext context) {
-        return Collections.emptyList();
+    public Pagination<PaymentMethodModelDao> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final InternalTenantContext context) {
+        throw new UnsupportedOperationException();
     }
 
     @Override
-    public List<RefundModelDao> getRefundsForAccount(final UUID accountId, final InternalTenantContext context) {
-        return Collections.emptyList();
+    public void deletedPaymentMethod(final UUID paymentMethodId, final InternalCallContext context) {
+        synchronized (this) {
+            final Iterator<PaymentMethodModelDao> it = paymentMethods.iterator();
+            while (it.hasNext()) {
+                final PaymentMethodModelDao cur = it.next();
+                if (cur.getId().equals(paymentMethodId)) {
+                    it.remove();
+                    break;
+                }
+            }
+        }
     }
 
     @Override
-    public PaymentModelDao getLastPaymentForPaymentMethod(final UUID accountId, final UUID paymentMethodId, final InternalTenantContext context) {
-        return null;
+    public List<PaymentMethodModelDao> refreshPaymentMethods(final UUID accountId, final String pluginName, final List<PaymentMethodModelDao> paymentMethods, final InternalCallContext context) {
+        return ImmutableList.<PaymentMethodModelDao>of();
     }
 
     @Override
     public PaymentMethodModelDao getPaymentMethodIncludedDeleted(final UUID paymentMethodId, final InternalTenantContext context) {
         return getPaymentMethod(paymentMethodId, context);
     }
+
+    @Override
+    public PaymentMethodModelDao getPaymentMethodByExternalKeyIncludedDeleted(final String paymentMethodExternalKey, final InternalTenantContext context) {
+        return getPaymentMethodByExternalKey(paymentMethodExternalKey, context);
+    }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java
new file mode 100644
index 0000000..13e8891
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestDefaultPaymentDao.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.dao;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.UUID;
+
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class TestDefaultPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
+
+    @Test(groups = "slow")
+    public void testPaymentCRUD() throws Exception {
+        for (int i = 0; i < 3; i++) {
+            testPaymentCRUDForAccount(UUID.randomUUID(), i + 1);
+        }
+    }
+
+    public void testPaymentCRUDForAccount(final UUID accountId, final int accountNb) {
+        // We need to create specific call contexts to make the account_record_id magic work
+        final InternalCallContext accountCallContext = new InternalCallContext(internalCallContext, (long) accountNb);
+
+        final PaymentModelDao specifiedFirstPaymentModelDao = generatePaymentModelDao(accountId);
+        final PaymentTransactionModelDao specifiedFirstPaymentTransactionModelDao = generatePaymentTransactionModelDao(specifiedFirstPaymentModelDao.getId());
+
+        // Create and verify the payment and transaction
+        final PaymentModelDao firstPaymentModelDao = paymentDao.insertPaymentWithFirstTransaction(specifiedFirstPaymentModelDao, specifiedFirstPaymentTransactionModelDao, accountCallContext);
+        verifyPayment(firstPaymentModelDao, specifiedFirstPaymentModelDao);
+        verifyPaymentAndTransactions(accountCallContext, specifiedFirstPaymentModelDao, specifiedFirstPaymentTransactionModelDao);
+
+        // Create a second transaction for the same payment
+        final PaymentTransactionModelDao specifiedSecondPaymentTransactionModelDao = generatePaymentTransactionModelDao(specifiedFirstPaymentModelDao.getId());
+        final PaymentTransactionModelDao secondTransactionModelDao = paymentDao.updatePaymentWithNewTransaction(specifiedFirstPaymentTransactionModelDao.getPaymentId(), specifiedSecondPaymentTransactionModelDao, accountCallContext);
+        verifyPaymentTransaction(secondTransactionModelDao, specifiedSecondPaymentTransactionModelDao);
+        verifyPaymentAndTransactions(accountCallContext, specifiedFirstPaymentModelDao, specifiedFirstPaymentTransactionModelDao, specifiedSecondPaymentTransactionModelDao);
+
+        // Update the latest transaction
+        final BigDecimal processedAmount = new BigDecimal("902341.23232");
+        final Currency processedCurrency = Currency.USD;
+        final String gatewayErrorCode = UUID.randomUUID().toString().substring(0, 5);
+        final String gatewayErrorMsg = UUID.randomUUID().toString();
+        paymentDao.updatePaymentAndTransactionOnCompletion(accountId,
+                                                           specifiedSecondPaymentTransactionModelDao.getPaymentId(),
+                                                           specifiedFirstPaymentTransactionModelDao.getTransactionType(),
+                                                           "SOME_ERRORED_STATE",
+                                                           "SOME_ERRORED_STATE",
+                                                           specifiedSecondPaymentTransactionModelDao.getId(),
+                                                           TransactionStatus.PAYMENT_FAILURE,
+                                                           processedAmount,
+                                                           processedCurrency,
+                                                           gatewayErrorCode,
+                                                           gatewayErrorMsg,
+                                                           accountCallContext);
+
+        final PaymentTransactionModelDao updatedSecondPaymentTransactionModelDao = paymentDao.getPaymentTransaction(specifiedSecondPaymentTransactionModelDao.getId(), accountCallContext);
+        Assert.assertEquals(updatedSecondPaymentTransactionModelDao.getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertEquals(updatedSecondPaymentTransactionModelDao.getGatewayErrorMsg(), gatewayErrorMsg);
+        Assert.assertEquals(updatedSecondPaymentTransactionModelDao.getGatewayErrorMsg(), gatewayErrorMsg);
+
+        // Create multiple payments for that account
+        for (int i = 0; i < 3; i++) {
+            final PaymentModelDao paymentModelDao = generatePaymentModelDao(accountId);
+            final PaymentTransactionModelDao paymentTransactionModelDao = generatePaymentTransactionModelDao(paymentModelDao.getId());
+
+            final PaymentModelDao insertedPaymentModelDao = paymentDao.insertPaymentWithFirstTransaction(paymentModelDao, paymentTransactionModelDao, accountCallContext);
+            verifyPayment(insertedPaymentModelDao, paymentModelDao);
+
+            // Verify search APIs
+            Assert.assertEquals(ImmutableList.<PaymentModelDao>copyOf(paymentDao.searchPayments(paymentModelDao.getPaymentMethodId().toString(), 0L, 100L, internalCallContext).iterator()).size(), 1);
+            Assert.assertEquals(ImmutableList.<PaymentModelDao>copyOf(paymentDao.searchPayments(paymentModelDao.getExternalKey(), 0L, 100L, internalCallContext).iterator()).size(), 1);
+        }
+        Assert.assertEquals(paymentDao.getPaymentsForAccount(specifiedFirstPaymentModelDao.getAccountId(), accountCallContext).size(), 4);
+
+        // Verify search APIs
+        Assert.assertEquals(ImmutableList.<PaymentModelDao>copyOf(paymentDao.searchPayments(accountId.toString(), 0L, 100L, internalCallContext).iterator()).size(), 4);
+    }
+
+    private void verifyPaymentAndTransactions(final InternalCallContext accountCallContext, final PaymentModelDao specifiedFirstPaymentModelDao, final PaymentTransactionModelDao... specifiedFirstPaymentTransactionModelDaos) {
+        for (final PaymentTransactionModelDao specifiedFirstPaymentTransactionModelDao : specifiedFirstPaymentTransactionModelDaos) {
+            final PaymentTransactionModelDao firstTransactionModelDao = paymentDao.getPaymentTransaction(specifiedFirstPaymentTransactionModelDao.getId(), accountCallContext);
+            verifyPaymentTransaction(firstTransactionModelDao, specifiedFirstPaymentTransactionModelDao);
+        }
+
+        // Retrieve the payment directly
+        final PaymentModelDao secondPaymentModelDao = paymentDao.getPayment(specifiedFirstPaymentModelDao.getId(), accountCallContext);
+        verifyPayment(secondPaymentModelDao, specifiedFirstPaymentModelDao);
+
+        // Retrieve the payments for the account
+        final List<PaymentModelDao> paymentsForAccount = paymentDao.getPaymentsForAccount(specifiedFirstPaymentModelDao.getAccountId(), accountCallContext);
+        Assert.assertEquals(paymentsForAccount.size(), 1);
+        verifyPayment(paymentsForAccount.get(0), specifiedFirstPaymentModelDao);
+
+        // Retrieve the transactions for the account
+        final List<PaymentTransactionModelDao> transactionsForAccount = paymentDao.getTransactionsForAccount(specifiedFirstPaymentModelDao.getAccountId(), accountCallContext);
+        Assert.assertEquals(transactionsForAccount.size(), specifiedFirstPaymentTransactionModelDaos.length);
+        for (int i = 0; i < specifiedFirstPaymentTransactionModelDaos.length; i++) {
+            verifyPaymentTransaction(transactionsForAccount.get(i), specifiedFirstPaymentTransactionModelDaos[i]);
+        }
+
+        // Retrieve the transactions for the payment
+        final List<PaymentTransactionModelDao> transactionsForPayment = paymentDao.getTransactionsForPayment(specifiedFirstPaymentModelDao.getId(), accountCallContext);
+        Assert.assertEquals(transactionsForPayment.size(), specifiedFirstPaymentTransactionModelDaos.length);
+        for (int i = 0; i < specifiedFirstPaymentTransactionModelDaos.length; i++) {
+            verifyPaymentTransaction(transactionsForPayment.get(i), specifiedFirstPaymentTransactionModelDaos[i]);
+        }
+    }
+
+    private PaymentTransactionModelDao generatePaymentTransactionModelDao(final UUID paymentId) {
+        return new PaymentTransactionModelDao(UUID.randomUUID(),
+                                              null,
+                                              UUID.randomUUID().toString(),
+                                              clock.getUTCNow(),
+                                              clock.getUTCNow(),
+                                              paymentId,
+                                              TransactionType.CAPTURE,
+                                              clock.getUTCNow(),
+                                              TransactionStatus.SUCCESS,
+                                              new BigDecimal("192.32910002"),
+                                              Currency.EUR,
+                                              UUID.randomUUID().toString().substring(0, 5),
+                                              UUID.randomUUID().toString()
+        );
+    }
+
+    private PaymentModelDao generatePaymentModelDao(final UUID accountId) {
+        return new PaymentModelDao(UUID.randomUUID(),
+                                   clock.getUTCNow(),
+                                   clock.getUTCNow(),
+                                   accountId,
+                                   UUID.randomUUID(),
+                                   -1,
+                                   UUID.randomUUID().toString()
+        );
+    }
+
+    private void verifyPayment(final PaymentModelDao loadedPaymentModelDao, final PaymentModelDao specifiedPaymentModelDao) {
+        Assert.assertEquals(loadedPaymentModelDao.getAccountId(), specifiedPaymentModelDao.getAccountId());
+        Assert.assertTrue(loadedPaymentModelDao.getPaymentNumber() > 0);
+        Assert.assertEquals(loadedPaymentModelDao.getPaymentMethodId(), specifiedPaymentModelDao.getPaymentMethodId());
+        Assert.assertEquals(loadedPaymentModelDao.getExternalKey(), specifiedPaymentModelDao.getExternalKey());
+
+        // Verify search APIs
+        Assert.assertEquals(ImmutableList.<PaymentModelDao>copyOf(paymentDao.searchPayments(specifiedPaymentModelDao.getPaymentMethodId().toString(), 0L, 100L, internalCallContext).iterator()).size(), 1);
+        Assert.assertEquals(ImmutableList.<PaymentModelDao>copyOf(paymentDao.searchPayments(specifiedPaymentModelDao.getExternalKey(), 0L, 100L, internalCallContext).iterator()).size(), 1);
+    }
+
+    private void verifyPaymentTransaction(final PaymentTransactionModelDao loadedPaymentTransactionModelDao, final PaymentTransactionModelDao specifiedPaymentTransactionModelDao) {
+        Assert.assertEquals(loadedPaymentTransactionModelDao.getPaymentId(), specifiedPaymentTransactionModelDao.getPaymentId());
+        Assert.assertEquals(loadedPaymentTransactionModelDao.getTransactionExternalKey(), specifiedPaymentTransactionModelDao.getTransactionExternalKey());
+        Assert.assertEquals(loadedPaymentTransactionModelDao.getTransactionType(), specifiedPaymentTransactionModelDao.getTransactionType());
+        Assert.assertEquals(loadedPaymentTransactionModelDao.getEffectiveDate().compareTo(specifiedPaymentTransactionModelDao.getEffectiveDate()), 0);
+        Assert.assertEquals(loadedPaymentTransactionModelDao.getTransactionStatus(), specifiedPaymentTransactionModelDao.getTransactionStatus());
+        Assert.assertEquals(loadedPaymentTransactionModelDao.getAmount().compareTo(specifiedPaymentTransactionModelDao.getAmount()), 0);
+        Assert.assertEquals(loadedPaymentTransactionModelDao.getCurrency(), specifiedPaymentTransactionModelDao.getCurrency());
+        Assert.assertEquals(loadedPaymentTransactionModelDao.getGatewayErrorCode(), specifiedPaymentTransactionModelDao.getGatewayErrorCode());
+        Assert.assertEquals(loadedPaymentTransactionModelDao.getGatewayErrorMsg(), specifiedPaymentTransactionModelDao.getGatewayErrorMsg());
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index d12107b..d8e3b15 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -17,233 +17,197 @@
 package org.killbill.billing.payment.dao;
 
 import java.math.BigDecimal;
-import java.math.RoundingMode;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.testng.annotations.Test;
-
+import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
-import org.killbill.billing.payment.api.PaymentStatus;
-import org.killbill.billing.payment.api.RefundStatus;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.killbill.billing.util.callcontext.CallOrigin;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.UserType;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
-import static org.testng.Assert.fail;
 
 public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
-    public void testRefund() {
-        final UUID accountId = UUID.randomUUID();
-        final UUID paymentId1 = UUID.randomUUID();
-        final BigDecimal amount1 = new BigDecimal(13);
-        final Currency currency = Currency.USD;
-
-        final RefundModelDao refund1 = new RefundModelDao(accountId, paymentId1, amount1, currency, amount1, currency, true);
-
-        paymentDao.insertRefund(refund1, internalCallContext);
-        final RefundModelDao refundCheck = paymentDao.getRefund(refund1.getId(), internalCallContext);
-        assertNotNull(refundCheck);
-        assertEquals(refundCheck.getAccountId(), accountId);
-        assertEquals(refundCheck.getPaymentId(), paymentId1);
-        assertEquals(refundCheck.getAmount().compareTo(amount1), 0);
-        assertEquals(refundCheck.getCurrency(), currency);
-        assertEquals(refundCheck.isAdjusted(), true);
-        assertEquals(refundCheck.getRefundStatus(), RefundStatus.CREATED);
-
-        final BigDecimal amount2 = new BigDecimal(7.00);
-        final UUID paymentId2 = UUID.randomUUID();
-
-        RefundModelDao refund2 = new RefundModelDao(accountId, paymentId2, amount2, currency, amount2, currency, true);
-        paymentDao.insertRefund(refund2, internalCallContext);
-        paymentDao.updateRefundStatus(refund2.getId(), RefundStatus.COMPLETED, amount2, currency, internalCallContext);
-
-        List<RefundModelDao> refundChecks = paymentDao.getRefundsForPayment(paymentId1, internalCallContext);
-        assertEquals(refundChecks.size(), 1);
-
-        refundChecks = paymentDao.getRefundsForPayment(paymentId2, internalCallContext);
-        assertEquals(refundChecks.size(), 1);
-
-        refundChecks = paymentDao.getRefundsForAccount(accountId, internalCallContext);
-        assertEquals(refundChecks.size(), 2);
-        for (RefundModelDao cur : refundChecks) {
-            if (cur.getPaymentId().equals(paymentId1)) {
-                assertEquals(cur.getAmount().compareTo(amount1), 0);
-                assertEquals(cur.getRefundStatus(), RefundStatus.CREATED);
-            } else if (cur.getPaymentId().equals(paymentId2)) {
-                assertEquals(cur.getAmount().compareTo(amount2), 0);
-                assertEquals(cur.getRefundStatus(), RefundStatus.COMPLETED);
-            } else {
-                fail("Unexpected refund");
-            }
-        }
-    }
+    public void testPaymentAttempt() throws PluginPropertySerializerException {
+        final UUID transactionId = UUID.randomUUID();
+        final String paymentExternalKey = "vraiment?";
+        final String transactionExternalKey = "tduteuqweq";
+        final String stateName = "INIT";
+        final TransactionType transactionType = TransactionType.AUTHORIZE;
+        final String pluginName = "superPlugin";
 
-    @Test(groups = "slow")
-    public void testUpdateStatus() {
         final UUID accountId = UUID.randomUUID();
-        final UUID invoiceId = UUID.randomUUID();
-        final UUID paymentMethodId = UUID.randomUUID();
-        final BigDecimal amount = new BigDecimal(13);
-        final Currency currency = Currency.USD;
-        final DateTime effectiveDate = clock.getUTCNow();
-
-        final PaymentModelDao payment = new PaymentModelDao(accountId, invoiceId, paymentMethodId, amount, currency, effectiveDate);
-        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(accountId, invoiceId, payment.getId(), paymentMethodId, effectiveDate, amount, currency);
-        PaymentModelDao savedPayment = paymentDao.insertPaymentWithFirstAttempt(payment, attempt, internalCallContext);
-        assertEquals(savedPayment.getEffectiveDate().compareTo(effectiveDate), 0);
 
-        final PaymentStatus paymentStatus = PaymentStatus.SUCCESS;
-        final String gatewayErrorCode = "OK";
-
-        clock.addDays(1);
-        paymentDao.updatePaymentAndAttemptOnCompletion(payment.getId(), paymentStatus, amount, currency, attempt.getId(), gatewayErrorCode, null, internalCallContext);
+        final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+        properties.add(new PluginProperty("key1", "value1", false));
+        properties.add(new PluginProperty("key2", "value2", false));
+
+        final byte[] serialized = PluginPropertySerializer.serialize(properties);
+        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(UUID.randomUUID(), UUID.randomUUID(), clock.getUTCNow(), clock.getUTCNow(),
+                                                                          paymentExternalKey, transactionId, transactionExternalKey, transactionType, stateName,
+                                                                          BigDecimal.ZERO, Currency.ALL, pluginName, serialized);
+
+        PaymentAttemptModelDao savedAttempt = paymentDao.insertPaymentAttemptWithProperties(attempt, internalCallContext);
+        assertEquals(savedAttempt.getTransactionExternalKey(), transactionExternalKey);
+        assertEquals(savedAttempt.getTransactionType(), transactionType);
+        assertEquals(savedAttempt.getStateName(), stateName);
+        assertEquals(savedAttempt.getPluginName(), pluginName);
+
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(savedAttempt.getPluginProperties());
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            Assert.assertEquals(cur, properties.get(i++));
+        }
 
-        final List<PaymentModelDao> payments = paymentDao.getPaymentsForInvoice(invoiceId, internalCallContext);
-        assertEquals(payments.size(), 1);
-        savedPayment = payments.get(0);
-        assertEquals(savedPayment.getId(), payment.getId());
-        assertEquals(savedPayment.getAccountId(), accountId);
-        assertEquals(savedPayment.getInvoiceId(), invoiceId);
-        assertEquals(savedPayment.getPaymentMethodId(), paymentMethodId);
-        assertEquals(savedPayment.getAmount().compareTo(amount), 0);
-        assertEquals(savedPayment.getCurrency(), currency);
-        assertEquals(savedPayment.getEffectiveDate().compareTo(effectiveDate), 0);
-        assertEquals(savedPayment.getPaymentStatus(), PaymentStatus.SUCCESS);
-
-        final List<PaymentAttemptModelDao> attempts = paymentDao.getAttemptsForPayment(payment.getId(), internalCallContext);
-        assertEquals(attempts.size(), 1);
-        final PaymentAttemptModelDao savedAttempt = attempts.get(0);
-        assertEquals(savedAttempt.getId(), attempt.getId());
-        assertEquals(savedAttempt.getPaymentId(), payment.getId());
-        assertEquals(savedAttempt.getAccountId(), accountId);
-        assertEquals(savedAttempt.getInvoiceId(), invoiceId);
-        assertEquals(savedAttempt.getProcessingStatus(), PaymentStatus.SUCCESS);
-        assertEquals(savedAttempt.getGatewayErrorCode(), gatewayErrorCode);
-        assertEquals(savedAttempt.getRequestedAmount().compareTo(amount), 0);
+        final PaymentAttemptModelDao retrievedAttempt1 = paymentDao.getPaymentAttempt(attempt.getId(), internalCallContext);
+        assertEquals(retrievedAttempt1.getTransactionExternalKey(), transactionExternalKey);
+        assertEquals(retrievedAttempt1.getTransactionType(), transactionType);
+        assertEquals(retrievedAttempt1.getStateName(), stateName);
+        assertEquals(retrievedAttempt1.getPluginName(), pluginName);
+
+        final List<PaymentAttemptModelDao> retrievedAttempts = paymentDao.getPaymentAttemptByTransactionExternalKey(transactionExternalKey, internalCallContext);
+        assertEquals(retrievedAttempts.size(), 1);
+        assertEquals(retrievedAttempts.get(0).getTransactionExternalKey(), transactionExternalKey);
+        assertEquals(retrievedAttempts.get(0).getTransactionType(), transactionType);
+        assertEquals(retrievedAttempts.get(0).getStateName(), stateName);
+        assertEquals(retrievedAttempts.get(0).getPluginName(), pluginName);
     }
 
     @Test(groups = "slow")
-    public void testPaymentWithAttempt() {
-        final UUID accountId = UUID.randomUUID();
-        final UUID invoiceId = UUID.randomUUID();
-        final UUID paymentMethodId = UUID.randomUUID();
-        final BigDecimal amount = new BigDecimal(13);
-        final Currency currency = Currency.USD;
-        final DateTime effectiveDate = clock.getUTCNow();
-
-        final PaymentModelDao payment = new PaymentModelDao(accountId, invoiceId, paymentMethodId, amount, currency, effectiveDate);
-        final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(accountId, invoiceId, payment.getId(), paymentMethodId, clock.getUTCNow(), amount, currency);
-
-        PaymentModelDao savedPayment = paymentDao.insertPaymentWithFirstAttempt(payment, attempt, internalCallContext);
-        assertEquals(savedPayment.getId(), payment.getId());
-        assertEquals(savedPayment.getAccountId(), accountId);
-        assertEquals(savedPayment.getInvoiceId(), invoiceId);
-        assertEquals(savedPayment.getPaymentMethodId(), paymentMethodId);
-        assertEquals(savedPayment.getAmount().compareTo(amount), 0);
-        assertEquals(savedPayment.getCurrency(), currency);
-        assertEquals(savedPayment.getEffectiveDate().compareTo(effectiveDate), 0);
-        assertEquals(savedPayment.getPaymentStatus(), PaymentStatus.UNKNOWN);
-
-        PaymentAttemptModelDao savedAttempt = paymentDao.getPaymentAttempt(attempt.getId(), internalCallContext);
-        assertEquals(savedAttempt.getId(), attempt.getId());
-        assertEquals(savedAttempt.getPaymentId(), payment.getId());
-        assertEquals(savedAttempt.getAccountId(), accountId);
-        assertEquals(savedAttempt.getInvoiceId(), invoiceId);
-        assertEquals(savedAttempt.getProcessingStatus(), PaymentStatus.UNKNOWN);
-
-        final List<PaymentModelDao> payments = paymentDao.getPaymentsForInvoice(invoiceId, internalCallContext);
-        assertEquals(payments.size(), 1);
-        savedPayment = payments.get(0);
-        assertEquals(savedPayment.getId(), payment.getId());
-        assertEquals(savedPayment.getAccountId(), accountId);
-        assertEquals(savedPayment.getInvoiceId(), invoiceId);
-        assertEquals(savedPayment.getPaymentMethodId(), paymentMethodId);
-        assertEquals(savedPayment.getAmount().compareTo(amount), 0);
-        assertEquals(savedPayment.getCurrency(), currency);
-        assertEquals(savedPayment.getEffectiveDate().compareTo(effectiveDate), 0);
-        assertEquals(savedPayment.getPaymentStatus(), PaymentStatus.UNKNOWN);
-
-        final List<PaymentAttemptModelDao> attempts = paymentDao.getAttemptsForPayment(payment.getId(), internalCallContext);
-        assertEquals(attempts.size(), 1);
-        savedAttempt = attempts.get(0);
-        assertEquals(savedAttempt.getId(), attempt.getId());
-        assertEquals(savedAttempt.getPaymentId(), payment.getId());
-        assertEquals(savedAttempt.getAccountId(), accountId);
-        assertEquals(savedAttempt.getInvoiceId(), invoiceId);
-        assertEquals(savedAttempt.getProcessingStatus(), PaymentStatus.UNKNOWN);
+    public void testPaymentAndTransactions() {
 
-    }
-
-    @Test(groups = "slow")
-    public void testNewAttempt() {
-        final UUID accountId = UUID.randomUUID();
-        final UUID invoiceId = UUID.randomUUID();
         final UUID paymentMethodId = UUID.randomUUID();
-        final BigDecimal amount = new BigDecimal(13);
-        final Currency currency = Currency.USD;
-        final DateTime effectiveDate = clock.getUTCNow();
-
-        final PaymentModelDao payment = new PaymentModelDao(accountId, invoiceId, paymentMethodId, amount, currency, effectiveDate);
-        final PaymentAttemptModelDao firstAttempt = new PaymentAttemptModelDao(accountId, invoiceId, payment.getId(), paymentMethodId, effectiveDate, amount, currency);
-        PaymentModelDao savedPayment = paymentDao.insertPaymentWithFirstAttempt(payment, firstAttempt, internalCallContext);
-
-        final PaymentModelDao lastPayment = paymentDao.getLastPaymentForPaymentMethod(accountId, paymentMethodId, internalCallContext);
-        assertNotNull(lastPayment);
-        assertEquals(lastPayment.getId(), payment.getId());
-        assertEquals(lastPayment.getAccountId(), accountId);
-        assertEquals(lastPayment.getInvoiceId(), invoiceId);
-        assertEquals(lastPayment.getPaymentMethodId(), paymentMethodId);
-        assertEquals(lastPayment.getAmount().compareTo(amount), 0);
-        assertEquals(lastPayment.getCurrency(), currency);
-        assertEquals(lastPayment.getEffectiveDate().compareTo(effectiveDate), 0);
-        assertEquals(lastPayment.getPaymentStatus(), PaymentStatus.UNKNOWN);
-
-        clock.addDays(3);
-        final DateTime newEffectiveDate = clock.getUTCNow();
-        final UUID newPaymentMethodId = UUID.randomUUID();
-        final BigDecimal newAmount = new BigDecimal("15.23");
-        final PaymentAttemptModelDao secondAttempt = new PaymentAttemptModelDao(accountId, invoiceId, payment.getId(), newPaymentMethodId, newEffectiveDate, newAmount, currency);
-        paymentDao.updatePaymentWithNewAttempt(payment.getId(), secondAttempt, internalCallContext);
-
-        final List<PaymentModelDao> payments = paymentDao.getPaymentsForInvoice(invoiceId, internalCallContext);
+        final UUID accountId = UUID.randomUUID();
+        final String externalKey = "hhhhooo";
+        final String transactionExternalKey = "grrrrrr";
+        final String transactionExternalKey2 = "hahahaha";
+
+        final DateTime utcNow = clock.getUTCNow();
+
+        final PaymentModelDao paymentModelDao = new PaymentModelDao(utcNow, utcNow, accountId, paymentMethodId, externalKey);
+        final PaymentTransactionModelDao transactionModelDao = new PaymentTransactionModelDao(utcNow, utcNow, null, transactionExternalKey,
+                                                                                              paymentModelDao.getId(), TransactionType.AUTHORIZE, utcNow,
+                                                                                              TransactionStatus.SUCCESS, BigDecimal.TEN, Currency.AED,
+                                                                                              "success", "");
+
+        final PaymentModelDao savedPayment = paymentDao.insertPaymentWithFirstTransaction(paymentModelDao, transactionModelDao, internalCallContext);
+        assertEquals(savedPayment.getId(), paymentModelDao.getId());
+        assertEquals(savedPayment.getAccountId(), paymentModelDao.getAccountId());
+        assertEquals(savedPayment.getExternalKey(), paymentModelDao.getExternalKey());
+        assertEquals(savedPayment.getPaymentMethodId(), paymentModelDao.getPaymentMethodId());
+        assertNull(savedPayment.getStateName());
+
+        final PaymentModelDao savedPayment2 = paymentDao.getPayment(savedPayment.getId(), internalCallContext);
+        assertEquals(savedPayment2.getId(), paymentModelDao.getId());
+        assertEquals(savedPayment2.getAccountId(), paymentModelDao.getAccountId());
+        assertEquals(savedPayment2.getExternalKey(), paymentModelDao.getExternalKey());
+        assertEquals(savedPayment2.getPaymentMethodId(), paymentModelDao.getPaymentMethodId());
+        assertNull(savedPayment2.getStateName());
+
+        final PaymentModelDao savedPayment3 = paymentDao.getPaymentByExternalKey(externalKey, internalCallContext);
+        assertEquals(savedPayment3.getId(), paymentModelDao.getId());
+        assertEquals(savedPayment3.getAccountId(), paymentModelDao.getAccountId());
+        assertEquals(savedPayment3.getExternalKey(), paymentModelDao.getExternalKey());
+        assertEquals(savedPayment3.getPaymentMethodId(), paymentModelDao.getPaymentMethodId());
+        assertNull(savedPayment3.getStateName());
+
+        final PaymentTransactionModelDao savedTransaction = paymentDao.getPaymentTransaction(transactionModelDao.getId(), internalCallContext);
+        assertEquals(savedTransaction.getTransactionExternalKey(), transactionExternalKey);
+        assertEquals(savedTransaction.getPaymentId(), paymentModelDao.getId());
+        assertEquals(savedTransaction.getTransactionType(), TransactionType.AUTHORIZE);
+        assertEquals(savedTransaction.getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(savedTransaction.getAmount().compareTo(BigDecimal.TEN), 0);
+        assertEquals(savedTransaction.getCurrency(), Currency.AED);
+
+        final List<PaymentTransactionModelDao> savedTransactions = paymentDao.getPaymentTransactionsByExternalKey(transactionExternalKey, internalCallContext);
+        assertEquals(savedTransactions.size(), 1);
+        final PaymentTransactionModelDao savedTransaction2 = savedTransactions.get(0);
+        assertEquals(savedTransaction2.getTransactionExternalKey(), transactionExternalKey);
+        assertEquals(savedTransaction2.getPaymentId(), paymentModelDao.getId());
+        assertEquals(savedTransaction2.getTransactionType(), TransactionType.AUTHORIZE);
+        assertEquals(savedTransaction2.getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(savedTransaction2.getAmount().compareTo(BigDecimal.TEN), 0);
+        assertEquals(savedTransaction2.getCurrency(), Currency.AED);
+
+        final PaymentTransactionModelDao transactionModelDao2 = new PaymentTransactionModelDao(utcNow, utcNow, null, transactionExternalKey2,
+                                                                                               paymentModelDao.getId(), TransactionType.AUTHORIZE, utcNow,
+                                                                                               TransactionStatus.UNKNOWN, BigDecimal.TEN, Currency.AED,
+                                                                                               "success", "");
+
+        final PaymentTransactionModelDao savedTransactionModelDao2 = paymentDao.updatePaymentWithNewTransaction(savedPayment.getId(), transactionModelDao2, internalCallContext);
+        assertEquals(savedTransactionModelDao2.getTransactionExternalKey(), transactionExternalKey2);
+        assertEquals(savedTransactionModelDao2.getPaymentId(), paymentModelDao.getId());
+        assertEquals(savedTransactionModelDao2.getTransactionType(), TransactionType.AUTHORIZE);
+        assertEquals(savedTransactionModelDao2.getTransactionStatus(), TransactionStatus.UNKNOWN);
+        assertEquals(savedTransactionModelDao2.getAmount().compareTo(BigDecimal.TEN), 0);
+        assertEquals(savedTransactionModelDao2.getCurrency(), Currency.AED);
+
+        final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(savedPayment.getId(), internalCallContext);
+        assertEquals(transactions.size(), 2);
+
+        paymentDao.updatePaymentAndTransactionOnCompletion(accountId, savedPayment.getId(), savedTransactionModelDao2.getTransactionType(), "AUTH_ABORTED", "AUTH_SUCCESS", transactionModelDao2.getId(), TransactionStatus.SUCCESS,
+                                                                 BigDecimal.ONE, Currency.USD, null, "nothing", internalCallContext);
+
+        final PaymentModelDao savedPayment4 = paymentDao.getPayment(savedPayment.getId(), internalCallContext);
+        assertEquals(savedPayment4.getId(), paymentModelDao.getId());
+        assertEquals(savedPayment4.getAccountId(), paymentModelDao.getAccountId());
+        assertEquals(savedPayment4.getExternalKey(), paymentModelDao.getExternalKey());
+        assertEquals(savedPayment4.getPaymentMethodId(), paymentModelDao.getPaymentMethodId());
+        assertEquals(savedPayment4.getStateName(), "AUTH_ABORTED");
+        assertEquals(savedPayment4.getLastSuccessStateName(), "AUTH_SUCCESS");
+
+        final PaymentTransactionModelDao savedTransactionModelDao4 = paymentDao.getPaymentTransaction(savedTransactionModelDao2.getId(), internalCallContext);
+        assertEquals(savedTransactionModelDao4.getTransactionExternalKey(), transactionExternalKey2);
+        assertEquals(savedTransactionModelDao4.getPaymentId(), paymentModelDao.getId());
+        assertEquals(savedTransactionModelDao4.getTransactionType(), TransactionType.AUTHORIZE);
+        assertEquals(savedTransactionModelDao4.getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(savedTransactionModelDao4.getAmount().compareTo(BigDecimal.TEN), 0);
+        assertEquals(savedTransactionModelDao4.getCurrency(), Currency.AED);
+        assertEquals(savedTransactionModelDao4.getProcessedAmount().compareTo(BigDecimal.ONE), 0);
+        assertEquals(savedTransactionModelDao4.getProcessedCurrency(), Currency.USD);
+        assertNull(savedTransactionModelDao4.getGatewayErrorCode());
+        assertEquals(savedTransactionModelDao4.getGatewayErrorMsg(), "nothing");
+
+        paymentDao.updatePaymentAndTransactionOnCompletion(accountId, savedPayment.getId(), savedTransactionModelDao2.getTransactionType(), "AUTH_ABORTED", null, transactionModelDao2.getId(), TransactionStatus.SUCCESS,
+                                                                 BigDecimal.ONE, Currency.USD, null, "nothing", internalCallContext);
+
+        final PaymentModelDao savedPayment4Again = paymentDao.getPayment(savedPayment.getId(), internalCallContext);
+        assertEquals(savedPayment4Again.getId(), paymentModelDao.getId());
+        assertEquals(savedPayment4Again.getStateName(), "AUTH_ABORTED");
+        assertEquals(savedPayment4Again.getLastSuccessStateName(), "AUTH_SUCCESS");
+
+        paymentDao.updatePaymentAndTransactionOnCompletion(accountId, savedPayment.getId(), savedTransactionModelDao2.getTransactionType(), "AUTH_ABORTED", "AUTH_SUCCESS", transactionModelDao2.getId(), TransactionStatus.SUCCESS,
+                                                                 BigDecimal.ONE, Currency.USD, null, "nothing", internalCallContext);
+
+        final PaymentModelDao savedPayment4Final = paymentDao.getPayment(savedPayment.getId(), internalCallContext);
+        assertEquals(savedPayment4Final.getId(), paymentModelDao.getId());
+        assertEquals(savedPayment4Final.getStateName(), "AUTH_ABORTED");
+        assertEquals(savedPayment4Final.getLastSuccessStateName(), "AUTH_SUCCESS");
+
+        final List<PaymentModelDao> payments = paymentDao.getPaymentsForAccount(accountId, internalCallContext);
         assertEquals(payments.size(), 1);
-        savedPayment = payments.get(0);
-        assertEquals(savedPayment.getId(), payment.getId());
-        assertEquals(savedPayment.getAccountId(), accountId);
-        assertEquals(savedPayment.getInvoiceId(), invoiceId);
-        assertEquals(savedPayment.getPaymentMethodId(), newPaymentMethodId);
-        assertEquals(savedPayment.getAmount().compareTo(newAmount), 0);
-        assertEquals(savedPayment.getCurrency(), currency);
-        assertEquals(savedPayment.getEffectiveDate().compareTo(newEffectiveDate), 0);
-        assertEquals(savedPayment.getPaymentStatus(), PaymentStatus.UNKNOWN);
-
-        final List<PaymentAttemptModelDao> attempts = paymentDao.getAttemptsForPayment(payment.getId(), internalCallContext);
-        assertEquals(attempts.size(), 2);
-        final PaymentAttemptModelDao savedAttempt1 = attempts.get(0);
-        assertEquals(savedAttempt1.getPaymentId(), payment.getId());
-        assertEquals(savedAttempt1.getPaymentMethodId(), paymentMethodId);
-        assertEquals(savedAttempt1.getAccountId(), accountId);
-        assertEquals(savedAttempt1.getInvoiceId(), invoiceId);
-        assertEquals(savedAttempt1.getInvoiceId(), invoiceId);
-        assertEquals(savedAttempt1.getGatewayErrorCode(), null);
-        assertEquals(savedAttempt1.getGatewayErrorMsg(), null);
-        assertEquals(savedAttempt1.getRequestedAmount().compareTo(amount), 0);
-
-        final PaymentAttemptModelDao savedAttempt2 = attempts.get(1);
-        assertEquals(savedAttempt2.getPaymentId(), payment.getId());
-        assertEquals(savedAttempt2.getPaymentMethodId(), newPaymentMethodId);
-        assertEquals(savedAttempt2.getAccountId(), accountId);
-        assertEquals(savedAttempt2.getInvoiceId(), invoiceId);
-        assertEquals(savedAttempt2.getProcessingStatus(), PaymentStatus.UNKNOWN);
-        assertEquals(savedAttempt2.getGatewayErrorCode(), null);
-        assertEquals(savedAttempt2.getGatewayErrorMsg(), null);
-        assertEquals(savedAttempt2.getRequestedAmount().compareTo(newAmount), 0);
+
+        final List<PaymentTransactionModelDao> transactions2 = paymentDao.getTransactionsForAccount(accountId, internalCallContext);
+        assertEquals(transactions2.size(), 2);
     }
 
     @Test(groups = "slow")
@@ -253,9 +217,8 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
         final UUID accountId = UUID.randomUUID();
         final String pluginName = "nobody";
         final Boolean isActive = Boolean.TRUE;
-        final String externalPaymentId = UUID.randomUUID().toString();
 
-        final PaymentMethodModelDao method = new PaymentMethodModelDao(paymentMethodId, null, null,
+        final PaymentMethodModelDao method = new PaymentMethodModelDao(paymentMethodId, UUID.randomUUID().toString(), null, null,
                                                                        accountId, pluginName, isActive);
 
         PaymentMethodModelDao savedMethod = paymentDao.insertPaymentMethod(method, internalCallContext);
@@ -284,4 +247,84 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
         assertEquals(deletedPaymentMethod.getId(), paymentMethodId);
         assertEquals(deletedPaymentMethod.getPluginName(), pluginName);
     }
+
+    @Test(groups = "slow")
+    public void testPendingTransactions() {
+
+        final UUID paymentMethodId = UUID.randomUUID();
+        final UUID accountId = UUID.randomUUID();
+        final String externalKey = "hhhhooo";
+        final String transactionExternalKey1 = "transaction1";
+        final String transactionExternalKey2 = "transaction2";
+        final String transactionExternalKey3 = "transaction3";
+        final String transactionExternalKey4 = "transaction4";
+
+        final DateTime initialTime = clock.getUTCNow();
+
+        final PaymentModelDao paymentModelDao = new PaymentModelDao(initialTime, initialTime, accountId, paymentMethodId, externalKey);
+        final PaymentTransactionModelDao transaction1 = new PaymentTransactionModelDao(initialTime, initialTime, null, transactionExternalKey1,
+                                                                                       paymentModelDao.getId(), TransactionType.AUTHORIZE, initialTime,
+                                                                                       TransactionStatus.PENDING, BigDecimal.TEN, Currency.AED,
+                                                                                       "pending", "");
+
+        paymentDao.insertPaymentWithFirstTransaction(paymentModelDao, transaction1, internalCallContext);
+
+        final PaymentTransactionModelDao transaction2 = new PaymentTransactionModelDao(initialTime, initialTime, null, transactionExternalKey2,
+                                                                                       paymentModelDao.getId(), TransactionType.AUTHORIZE, initialTime,
+                                                                                       TransactionStatus.PENDING, BigDecimal.TEN, Currency.AED,
+                                                                                       "pending", "");
+        paymentDao.updatePaymentWithNewTransaction(paymentModelDao.getId(), transaction2, internalCallContext);
+
+        final PaymentTransactionModelDao transaction3 = new PaymentTransactionModelDao(initialTime, initialTime, null, transactionExternalKey3,
+                                                                                       paymentModelDao.getId(), TransactionType.AUTHORIZE, initialTime,
+                                                                                       TransactionStatus.SUCCESS, BigDecimal.TEN, Currency.AED,
+                                                                                       "success", "");
+
+        paymentDao.updatePaymentWithNewTransaction(paymentModelDao.getId(), transaction3, internalCallContext);
+
+        clock.addDays(1);
+        final DateTime newTime = clock.getUTCNow();
+
+
+        final InternalCallContext internalCallContextWithNewTime = new InternalCallContext(InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID, 1687L, UUID.randomUUID(),
+                                                                                        UUID.randomUUID().toString(), CallOrigin.TEST,
+                                                                                        UserType.TEST, "Testing", "This is a test",
+                                                                                        newTime, newTime);
+
+        final PaymentTransactionModelDao transaction4 = new PaymentTransactionModelDao(initialTime, initialTime, null, transactionExternalKey4,
+                                                                                       paymentModelDao.getId(), TransactionType.AUTHORIZE, newTime,
+                                                                                       TransactionStatus.PENDING, BigDecimal.TEN, Currency.AED,
+                                                                                       "pending", "");
+        paymentDao.updatePaymentWithNewTransaction(paymentModelDao.getId(), transaction4, internalCallContextWithNewTime);
+
+
+        final List<PaymentTransactionModelDao> result = getPendingTransactions(paymentModelDao.getId());
+        Assert.assertEquals(result.size(), 3);
+
+
+        paymentDao.failOldPendingTransactions(TransactionStatus.PAYMENT_FAILURE, newTime, internalCallContext);
+
+        final List<PaymentTransactionModelDao> result2 = getPendingTransactions(paymentModelDao.getId());
+        Assert.assertEquals(result2.size(), 1);
+
+        // Just to guarantee that next clock.getUTCNow() > newTime
+        try { Thread.sleep(1000); } catch (InterruptedException e) {};
+
+        paymentDao.failOldPendingTransactions(TransactionStatus.PAYMENT_FAILURE, clock.getUTCNow(), internalCallContextWithNewTime);
+
+        final List<PaymentTransactionModelDao> result3 = getPendingTransactions(paymentModelDao.getId());
+        Assert.assertEquals(result3.size(), 0);
+
+    }
+
+    private List<PaymentTransactionModelDao> getPendingTransactions(final UUID paymentId) {
+        final List<PaymentTransactionModelDao> total =  paymentDao.getTransactionsForPayment(paymentId, internalCallContext);
+        return ImmutableList.copyOf(Iterables.filter(total, new Predicate<PaymentTransactionModelDao>() {
+            @Override
+            public boolean apply(final PaymentTransactionModelDao input) {
+                return input.getTransactionStatus() == TransactionStatus.PENDING;
+            }
+        }));
+    }
 }
+
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPluginPropertySerializer.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPluginPropertySerializer.java
new file mode 100644
index 0000000..096936e
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPluginPropertySerializer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.dao;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestPluginPropertySerializer {
+
+    @Test(groups = "fast")
+    public void testNoPluginProperty() throws PluginPropertySerializerException {
+        final List<PluginProperty> input = new ArrayList<PluginProperty>();
+
+        final byte[] serialized = PluginPropertySerializer.serialize(input);
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            Assert.assertEquals(cur, input.get(i++));
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testSimplePluginProperty() throws PluginPropertySerializerException {
+        final List<PluginProperty> input = new ArrayList<PluginProperty>();
+        input.add(new PluginProperty("foo", "bar", false));
+
+        final byte[] serialized = PluginPropertySerializer.serialize(input);
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            Assert.assertEquals(cur, input.get(i++));
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testLotsPluginProperty() throws PluginPropertySerializerException {
+        final List<PluginProperty> input = new ArrayList<PluginProperty>();
+        for (int i = 0; i < 100; i++) {
+            input.add(new PluginProperty("foo-" + i, "bar-" + i, false));
+        }
+
+        final byte[] serialized = PluginPropertySerializer.serialize(input);
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            Assert.assertEquals(cur, input.get(i++));
+        }
+    }
+
+    @Test(groups = "fast", enabled = true)
+    public void testPluginPropertyWithComplexValue() throws PluginPropertySerializerException {
+        final HashMap<String, BigDecimal> something = new HashMap<String, BigDecimal>();
+        something.put("yoyo", new BigDecimal("0.0"));
+        something.put("what", new BigDecimal("10.0"));
+        final List<PluginProperty> input = new ArrayList<PluginProperty>();
+        input.add(new PluginProperty("prev", "simple", false));
+        input.add(new PluginProperty("foo", something, false));
+        input.add(new PluginProperty("next", "easy", false));
+
+        final byte[] serialized = PluginPropertySerializer.serialize(input);
+        final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+        int i = 0;
+        for (PluginProperty cur : deserialized) {
+            if (i == 0 || i == 2) {
+                Assert.assertEquals(cur, input.get(i));
+            } else {
+                Assert.assertEquals(cur.getKey(), "foo");
+                Assert.assertTrue(cur.getValue() instanceof Map);
+                final Map<String, BigDecimal> mappedValue = (Map<String, BigDecimal>) cur.getValue();
+                Assert.assertTrue(mappedValue.containsKey("yoyo"));
+                Assert.assertTrue(mappedValue.containsKey("what"));
+                Assert.assertTrue(mappedValue.get("yoyo").compareTo(BigDecimal.ZERO) == 0);
+                Assert.assertTrue(mappedValue.get("what").compareTo(BigDecimal.TEN) == 0);
+            }
+            i++;
+        }
+
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
index a95ddc3..d226d73 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dispatcher/TestPluginDispatcher.java
@@ -17,16 +17,17 @@
 package org.killbill.billing.payment.dispatcher;
 
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.payment.PaymentTestSuiteNoDB;
 import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.dispatcher.PluginDispatcher.PluginDispatcherReturnType;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
 
@@ -36,17 +37,19 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
     public void testDispatchWithTimeout() throws TimeoutException, PaymentApiException {
         boolean gotIt = false;
         try {
-            voidPluginDispatcher.dispatchWithAccountLockAndTimeout(new Callable<Void>() {
+            voidPluginDispatcher.dispatchWithTimeout(new Callable<PluginDispatcherReturnType<Void>>() {
                 @Override
-                public Void call() throws Exception {
+                public PluginDispatcherReturnType<Void> call() throws Exception {
                     Thread.sleep(1000);
                     return null;
                 }
             }, 100, TimeUnit.MILLISECONDS);
             Assert.fail("Failed : should have had Timeout exception");
-        } catch (TimeoutException e) {
+        } catch (final TimeoutException e) {
             gotIt = true;
-        } catch (PaymentApiException e) {
+        } catch (InterruptedException e) {
+            Assert.fail("Failed : should have had Timeout exception");
+        } catch (ExecutionException e) {
             Assert.fail("Failed : should have had Timeout exception");
         }
         Assert.assertTrue(gotIt);
@@ -56,37 +59,50 @@ public class TestPluginDispatcher extends PaymentTestSuiteNoDB {
     public void testDispatchWithPaymentApiException() throws TimeoutException, PaymentApiException {
         boolean gotIt = false;
         try {
-            voidPluginDispatcher.dispatchWithAccountLockAndTimeout(new Callable<Void>() {
+            voidPluginDispatcher.dispatchWithTimeout(new Callable<PluginDispatcherReturnType<Void>>() {
                 @Override
-                public Void call() throws Exception {
+                public PluginDispatcherReturnType<Void> call() throws Exception {
                     throw new PaymentApiException(ErrorCode.PAYMENT_ADD_PAYMENT_METHOD, "foo", "foo");
                 }
             }, 100, TimeUnit.MILLISECONDS);
             Assert.fail("Failed : should have had Timeout exception");
-        } catch (TimeoutException e) {
+        } catch (final TimeoutException e) {
             Assert.fail("Failed : should have had PaymentApiException exception");
-        } catch (PaymentApiException e) {
-            gotIt = true;
+        } catch (InterruptedException e) {
+            Assert.fail("Failed : should have had PaymentApiException exception");
+        } catch (ExecutionException e) {
+            if (e.getCause() instanceof PaymentApiException) {
+                gotIt = true;
+            } else {
+                Assert.fail("Failed : should have had PaymentApiException exception");
+            }
         }
         Assert.assertTrue(gotIt);
     }
 
     @Test(groups = "fast")
-    public void testDispatchWithRuntimeExceptionWrappedInPaymentApiException() throws TimeoutException, PaymentApiException {
+    public void testDispatchWithRuntimeException() throws TimeoutException, PaymentApiException {
         boolean gotIt = false;
         try {
-            voidPluginDispatcher.dispatchWithAccountLockAndTimeout(new Callable<Void>() {
+            voidPluginDispatcher.dispatchWithTimeout(new Callable<PluginDispatcherReturnType<Void>>() {
                 @Override
-                public Void call() throws Exception {
+                public PluginDispatcherReturnType<Void> call() throws Exception {
                     throw new RuntimeException("whatever");
                 }
             }, 100, TimeUnit.MILLISECONDS);
             Assert.fail("Failed : should have had Timeout exception");
-        } catch (TimeoutException e) {
+        } catch (final TimeoutException e) {
             Assert.fail("Failed : should have had RuntimeException exception");
-        } catch (PaymentApiException e) {
-            gotIt = true;
-        } catch (RuntimeException e) {
+        } catch (final RuntimeException e) {
+            Assert.fail("Failed : should have had RuntimeException (wrapped in an ExecutionException)");
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        } catch (ExecutionException e) {
+            if (e.getCause() instanceof RuntimeException) {
+                gotIt = true;
+            } else {
+                Assert.fail("Failed : should have had RuntimeException exception");
+            }
         }
         Assert.assertTrue(gotIt);
     }
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
index 2d8d3e7..3ea946b 100644
--- a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -18,25 +20,23 @@ package org.killbill.billing.payment.glue;
 
 import java.util.UUID;
 
-import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
-import org.mockito.Mockito;
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.ObjectType;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.mock.glue.MockAccountModule;
-import org.killbill.billing.mock.glue.MockSubscriptionModule;
 import org.killbill.billing.mock.glue.MockInvoiceModule;
-import org.killbill.billing.mock.glue.MockNotificationQueueModule;
+import org.killbill.billing.mock.glue.MockSubscriptionModule;
 import org.killbill.billing.payment.TestPaymentHelper;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPluginModule;
-import org.killbill.billing.util.bus.InMemoryBusModule;
-import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.clock.Clock;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.billing.util.glue.CacheModule;
-import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.glue.MemoryGlobalLockerModule;
 import org.killbill.billing.util.tag.Tag;
+import org.killbill.clock.Clock;
+import org.mockito.Mockito;
 
 import com.google.common.collect.ImmutableList;
 
@@ -44,34 +44,34 @@ public class TestPaymentModule extends PaymentModule {
 
     private final Clock clock;
 
-    public TestPaymentModule(final ConfigSource configSource, final Clock clock) {
+    public TestPaymentModule(final KillbillConfigSource configSource, final Clock clock) {
         super(configSource);
         this.clock = clock;
     }
 
     @Override
     protected void installPaymentProviderPlugins(final PaymentConfig config) {
-        install(new MockPaymentProviderPluginModule(MockPaymentProviderPlugin.PLUGIN_NAME, clock));
+        install(new MockPaymentProviderPluginModule(MockPaymentProviderPlugin.PLUGIN_NAME, clock, configSource));
     }
 
     private void installExternalApis() {
-        final TagInternalApi tagUserApi = Mockito.mock(TagInternalApi.class);
-        bind(TagInternalApi.class).toInstance(tagUserApi);
-        Mockito.when(tagUserApi.getTags(Mockito.<UUID>any(), Mockito.<ObjectType>any(), Mockito.<InternalTenantContext>any())).thenReturn(ImmutableList.<Tag>of());
+        final TagInternalApi tagInternalApi = Mockito.mock(TagInternalApi.class);
+        bind(TagInternalApi.class).toInstance(tagInternalApi);
+        Mockito.when(tagInternalApi.getTags(Mockito.<UUID>any(), Mockito.<ObjectType>any(), Mockito.<InternalTenantContext>any())).thenReturn(ImmutableList.<Tag>of());
+
+        final TagUserApi tagUserApi = Mockito.mock(TagUserApi.class);
+        bind(TagUserApi.class).toInstance(tagUserApi);
     }
 
     @Override
     protected void configure() {
         super.configure();
-        install(new InMemoryBusModule(configSource));
-        install(new MockNotificationQueueModule(configSource));
-        install(new MockInvoiceModule());
-        install(new MockAccountModule());
-        install(new MockSubscriptionModule());
-        install(new MemoryGlobalLockerModule());
+        install(new MockInvoiceModule(configSource));
+        install(new MockAccountModule(configSource));
+        install(new MockSubscriptionModule(configSource));
+        install(new MemoryGlobalLockerModule(configSource));
         install(new CacheModule(configSource));
         installExternalApis();
-
         bind(TestPaymentHelper.class).asEagerSingleton();
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModuleNoDB.java b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModuleNoDB.java
index 9a6f6c5..0920123 100644
--- a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModuleNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,17 +18,18 @@
 
 package org.killbill.billing.payment.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
 import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
+import org.killbill.billing.payment.core.sm.MockRetryablePaymentAutomatonRunner;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
 import org.killbill.billing.payment.dao.MockPaymentDao;
 import org.killbill.billing.payment.dao.PaymentDao;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.clock.Clock;
 
 public class TestPaymentModuleNoDB extends TestPaymentModule {
 
-    public TestPaymentModuleNoDB(final ConfigSource configSource, final Clock clock) {
+    public TestPaymentModuleNoDB(final KillbillConfigSource configSource, final Clock clock) {
         super(configSource, clock);
     }
 
@@ -37,8 +40,12 @@ public class TestPaymentModuleNoDB extends TestPaymentModule {
 
     @Override
     protected void configure() {
-        install(new GuicyKillbillTestNoDBModule());
-        install(new MockNonEntityDaoModule());
+        install(new GuicyKillbillTestNoDBModule(configSource));
+        install(new MockNonEntityDaoModule(configSource));
         super.configure();
     }
+
+    protected void installAutomatonRunner() {
+        bind(PluginControlledPaymentAutomatonRunner.class).to(MockRetryablePaymentAutomatonRunner.class).asEagerSingleton();
+    }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModuleWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModuleWithEmbeddedDB.java
index 2a9495c..92ded7c 100644
--- a/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModuleWithEmbeddedDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/glue/TestPaymentModuleWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,22 +18,22 @@
 
 package org.killbill.billing.payment.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
-import org.killbill.clock.Clock;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
+import org.killbill.clock.Clock;
 
 public class TestPaymentModuleWithEmbeddedDB extends TestPaymentModule {
 
-    public TestPaymentModuleWithEmbeddedDB(final ConfigSource configSource, final Clock clock) {
+    public TestPaymentModuleWithEmbeddedDB(final KillbillConfigSource configSource, final Clock clock) {
         super(configSource, clock);
     }
 
     @Override
     protected void configure() {
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
-        install(new NonEntityDaoModule());
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
+        install(new NonEntityDaoModule(configSource));
+
         super.configure();
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
index 2152a4c..69fa238 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,25 +18,27 @@
 
 package org.killbill.billing.payment;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
-
 import org.killbill.billing.GuicyKillbillTestSuiteNoDB;
-import org.killbill.billing.TestKillbillConfigSource;
 import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApi;
-import org.killbill.billing.payment.core.PaymentMethodProcessor;
 import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.core.PaymentMethodProcessor;
+import org.killbill.billing.payment.core.PluginControlledPaymentProcessor;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
+import org.killbill.billing.payment.core.sm.PluginControlledPaymentAutomatonRunner;
+import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.glue.TestPaymentModuleNoDB;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
-import org.killbill.billing.payment.retry.FailedPaymentRetryService;
-import org.killbill.billing.payment.retry.PluginFailureRetryService;
-import org.killbill.billing.util.KillbillConfigSource;
+import org.killbill.billing.payment.retry.DefaultRetryService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.bus.api.PersistentBus;
+import org.killbill.commons.profiling.Profiling;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
@@ -49,31 +53,40 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
     @Inject
     protected PaymentConfig paymentConfig;
     @Inject
-    protected PaymentProcessor paymentProcessor;
-    @Inject
     protected PaymentMethodProcessor paymentMethodProcessor;
     @Inject
     protected InvoiceInternalApi invoiceApi;
     @Inject
     protected OSGIServiceRegistration<PaymentPluginApi> registry;
     @Inject
-    protected FailedPaymentRetryService retryService;
-    @Inject
-    protected PluginFailureRetryService pluginRetryService;
-    @Inject
     protected PersistentBus eventBus;
     @Inject
     protected PaymentApi paymentApi;
     @Inject
-    protected AccountInternalApi accountApi;
+    protected AccountInternalApi accountInternalApi;
     @Inject
     protected TestPaymentHelper testHelper;
+    @Inject
+    protected PaymentDao paymentDao;
+    @Inject
+    protected PaymentStateMachineHelper paymentSMHelper;
+    @Inject
+    protected PaymentProcessor paymentProcessor;
+    @Inject
+    protected PluginControlledPaymentProcessor pluginControlledPaymentProcessor;
+    @Inject
+    protected PluginControlledPaymentAutomatonRunner retryablePaymentAutomatonRunner;
+    @Inject
+    protected DefaultRetryService retryService;
+    @Inject
+    protected CacheControllerDispatcher cacheControllerDispatcher;
 
     @Override
-    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
-        return new TestKillbillConfigSource("/payment.properties",
-                                            ImmutableMap.<String, String>of("org.killbill.payment.provider.default", MockPaymentProviderPlugin.PLUGIN_NAME,
-                                                                            "killbill.payment.engine.events.off", "false"));
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/payment.properties",
+                               ImmutableMap.<String, String>of("org.killbill.payment.provider.default", MockPaymentProviderPlugin.PLUGIN_NAME,
+                                                               "killbill.payment.engine.events.off", "false"));
+
     }
 
     @BeforeClass(groups = "fast")
@@ -85,6 +98,7 @@ public abstract class PaymentTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
         eventBus.start();
+        Profiling.resetPerThreadProfilingData();
     }
 
     @AfterMethod(groups = "fast")
diff --git a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
index 7819d0d..3d144dc 100644
--- a/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
+++ b/payment/src/test/java/org/killbill/billing/payment/PaymentTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,26 +18,22 @@
 
 package org.killbill.billing.payment;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
-
 import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
-import org.killbill.billing.TestKillbillConfigSource;
 import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApi;
-import org.killbill.billing.payment.core.PaymentMethodProcessor;
 import org.killbill.billing.payment.core.PaymentProcessor;
+import org.killbill.billing.payment.core.PaymentMethodProcessor;
+import org.killbill.billing.payment.core.sm.PaymentStateMachineHelper;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.glue.TestPaymentModuleWithEmbeddedDB;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
-import org.killbill.billing.payment.retry.FailedPaymentRetryService;
-import org.killbill.billing.payment.retry.PluginFailureRetryService;
-import org.killbill.billing.util.KillbillConfigSource;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.bus.api.PersistentBus;
+import org.killbill.commons.profiling.Profiling;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
@@ -50,33 +48,31 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @Inject
     protected PaymentConfig paymentConfig;
     @Inject
-    protected PaymentProcessor paymentProcessor;
-    @Inject
     protected PaymentMethodProcessor paymentMethodProcessor;
     @Inject
+    protected PaymentProcessor paymentProcessor;
+    @Inject
     protected InvoiceInternalApi invoiceApi;
     @Inject
     protected OSGIServiceRegistration<PaymentPluginApi> registry;
     @Inject
-    protected FailedPaymentRetryService retryService;
-    @Inject
-    protected PluginFailureRetryService pluginRetryService;
-    @Inject
     protected PersistentBus eventBus;
     @Inject
     protected PaymentApi paymentApi;
     @Inject
     protected AccountInternalApi accountApi;
     @Inject
+    protected PaymentStateMachineHelper paymentSMHelper;
+    @Inject
     protected PaymentDao paymentDao;
     @Inject
     protected TestPaymentHelper testHelper;
 
     @Override
-    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
-        return new TestKillbillConfigSource("/payment.properties",
-                                            ImmutableMap.<String, String>of("org.killbill.payment.provider.default", MockPaymentProviderPlugin.PLUGIN_NAME,
-                                                                            "killbill.payment.engine.events.off", "false"));
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/payment.properties",
+                               ImmutableMap.<String, String>of("org.killbill.payment.provider.default", MockPaymentProviderPlugin.PLUGIN_NAME,
+                                                               "killbill.payment.engine.events.off", "false"));
     }
 
     @BeforeClass(groups = "slow")
@@ -89,6 +85,8 @@ public abstract class PaymentTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     public void beforeMethod() throws Exception {
         super.beforeMethod();
         eventBus.start();
+        Profiling.resetPerThreadProfilingData();
+
     }
 
     @AfterMethod(groups = "slow")
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
new file mode 100644
index 0000000..fc0b8a3
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentControlProviderPlugin.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment.provider;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.payment.retry.DefaultFailureCallResult;
+import org.killbill.billing.payment.retry.DefaultPriorPaymentControlResult;
+import org.killbill.billing.retry.plugin.api.FailureCallResult;
+import org.killbill.billing.retry.plugin.api.PaymentControlApiException;
+import org.killbill.billing.retry.plugin.api.PaymentControlContext;
+import org.killbill.billing.retry.plugin.api.PaymentControlPluginApi;
+import org.killbill.billing.retry.plugin.api.PriorPaymentControlResult;
+
+public class MockPaymentControlProviderPlugin implements PaymentControlPluginApi {
+
+    public static final String PLUGIN_NAME = "MOCK_RETRY_PLUGIN";
+
+    private boolean isAborted;
+    private DateTime nextRetryDate;
+
+    public MockPaymentControlProviderPlugin setAborted(final boolean isAborted) {
+        this.isAborted = isAborted;
+        return this;
+    }
+
+    public MockPaymentControlProviderPlugin setNextRetryDate(final DateTime nextRetryDate) {
+        this.nextRetryDate = nextRetryDate;
+        return this;
+    }
+
+    @Override
+    public PriorPaymentControlResult priorCall(final PaymentControlContext paymentControlContext) throws PaymentControlApiException{
+        return new DefaultPriorPaymentControlResult(isAborted, null);
+    }
+
+    @Override
+    public void onSuccessCall(final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
+
+    }
+
+    @Override
+    public FailureCallResult onFailureCall(final PaymentControlContext paymentControlContext) throws PaymentControlApiException {
+        return new DefaultFailureCallResult(nextRetryDate);
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
index 5b98fc5..28f90ed 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPlugin.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,7 +19,6 @@
 package org.killbill.billing.payment.provider;
 
 import java.math.BigDecimal;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -25,31 +26,31 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.clock.Clock;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.api.TestPaymentMethodPlugin;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.plugin.api.GatewayNotification;
+import org.killbill.billing.payment.plugin.api.HostedPaymentPageFormDescriptor;
 import org.killbill.billing.payment.plugin.api.NoOpPaymentPluginApi;
-import org.killbill.billing.payment.plugin.api.PaymentInfoPlugin;
 import org.killbill.billing.payment.plugin.api.PaymentMethodInfoPlugin;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
-import org.killbill.billing.payment.plugin.api.RefundInfoPlugin;
-import org.killbill.billing.payment.plugin.api.RefundPluginStatus;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.entity.DefaultPagination;
 import org.killbill.billing.util.entity.Pagination;
+import org.killbill.clock.Clock;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.Multimap;
 import com.google.inject.Inject;
 
 /**
  * This MockPaymentProviderPlugin only works for a single accounts as we don't specify the accountId
- * for opeartions such as addPaymentMethod.
+ * for operations such as addPaymentMethod.
  */
 public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
 
@@ -59,14 +60,116 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
     private final AtomicBoolean makeNextInvoiceFailWithException = new AtomicBoolean(false);
     private final AtomicBoolean makeAllInvoicesFailWithError = new AtomicBoolean(false);
 
-    private final Map<String, PaymentInfoPlugin> payments = new ConcurrentHashMap<String, PaymentInfoPlugin>();
+    private final Map<String, InternalPaymentInfo> payments = new ConcurrentHashMap<String, InternalPaymentInfo>();
     // Note: we can't use HashMultiMap as we care about storing duplicate key/value pairs
-    private final Multimap<String, RefundInfoPlugin> refunds = LinkedListMultimap.<String, RefundInfoPlugin>create();
     private final Map<String, PaymentMethodPlugin> paymentMethods = new ConcurrentHashMap<String, PaymentMethodPlugin>();
     private final Map<String, PaymentMethodInfoPlugin> paymentMethodsInfo = new ConcurrentHashMap<String, PaymentMethodInfoPlugin>();
 
     private final Clock clock;
 
+    private class InternalPaymentInfo {
+
+        private BigDecimal authAmount;
+        private BigDecimal captureAmount;
+        private BigDecimal purchasedAmount;
+        private BigDecimal refundAmount;
+        private BigDecimal creditAmount;
+
+        private InternalPaymentInfo() {
+            this.authAmount = BigDecimal.ZERO;
+            this.captureAmount = BigDecimal.ZERO;
+            this.purchasedAmount = BigDecimal.ZERO;
+            this.refundAmount = BigDecimal.ZERO;
+            this.creditAmount = BigDecimal.ZERO;
+        }
+
+        public BigDecimal getAuthAmount() {
+            return authAmount;
+        }
+
+        public BigDecimal getCaptureAmount() {
+            return captureAmount;
+        }
+
+        public BigDecimal getPurchasedAmount() {
+            return purchasedAmount;
+        }
+
+        public BigDecimal getRefundAmount() {
+            return refundAmount;
+        }
+
+        public BigDecimal getCreditAmount() {
+            return creditAmount;
+        }
+
+        public BigDecimal getAmount(TransactionType type) {
+            switch (type) {
+                case AUTHORIZE:
+                    return getAuthAmount();
+                case CAPTURE:
+                    return getCaptureAmount();
+                case PURCHASE:
+                    return getPurchasedAmount();
+                case VOID:
+                    return BigDecimal.ZERO;
+                case CREDIT:
+                    return getCreditAmount();
+                case REFUND:
+                    return getRefundAmount();
+                default:
+                    throw new RuntimeException("Unsupported type " + type);
+            }
+        }
+
+        public void addAmount(TransactionType type, BigDecimal amount) {
+            switch (type) {
+                case AUTHORIZE:
+                    addAuthAmount(amount);
+                    break;
+                case CAPTURE:
+                    addCaptureAmount(amount);
+                    break;
+                case PURCHASE:
+                    addPurchasedAmount(amount);
+                    break;
+                case VOID:
+                    voidAuthAmount();
+                    break;
+                case CREDIT:
+                    addCreditAmount(amount);
+                    break;
+                case REFUND:
+                    addRefundAmount(amount);
+                    break;
+            }
+        }
+
+        public void addAuthAmount(final BigDecimal authAmount) {
+            this.authAmount = this.authAmount.add(authAmount);
+        }
+
+        public void addCaptureAmount(final BigDecimal captureAmount) {
+            this.captureAmount = this.captureAmount.add(captureAmount);
+        }
+
+        public void addPurchasedAmount(final BigDecimal purchasedAmount) {
+            this.purchasedAmount = this.purchasedAmount.add(purchasedAmount);
+        }
+
+        public void addRefundAmount(final BigDecimal refundAmount) {
+            this.refundAmount = this.refundAmount.add(refundAmount);
+        }
+
+        public void addCreditAmount(final BigDecimal creditAmount) {
+            this.creditAmount = this.creditAmount.add(creditAmount);
+        }
+
+        public void voidAuthAmount() {
+            this.authAmount = BigDecimal.ZERO;
+        }
+    }
+
     @Inject
     public MockPaymentProviderPlugin(final Clock clock) {
         this.clock = clock;
@@ -78,6 +181,9 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
         makeNextInvoiceFailWithException.set(false);
         makeAllInvoicesFailWithError.set(false);
         makeNextInvoiceFailWithError.set(false);
+        paymentMethods.clear();
+        payments.clear();
+        paymentMethodsInfo.clear();
     }
 
     @Override
@@ -96,41 +202,53 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
-    public PaymentInfoPlugin processPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
-        if (makeNextInvoiceFailWithException.getAndSet(false)) {
-            throw new PaymentPluginApiException("", "test error");
-        }
+    public PaymentTransactionInfoPlugin authorizePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
+            throws PaymentPluginApiException {
+        return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.AUTHORIZE, amount, currency);
+    }
 
-        final PaymentPluginStatus status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED;
-        final PaymentInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, amount, currency, clock.getUTCNow(), clock.getUTCNow(), status, null);
-        payments.put(kbPaymentId.toString(), result);
-        return result;
+    @Override
+    public PaymentTransactionInfoPlugin capturePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
+            throws PaymentPluginApiException {
+        return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.CAPTURE, amount, currency);
+    }
+
+    @Override
+    public PaymentTransactionInfoPlugin purchasePayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+        return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, currency);
+    }
+
+    @Override
+    public PaymentTransactionInfoPlugin voidPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context)
+            throws PaymentPluginApiException {
+        return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.VOID, BigDecimal.ZERO, null);
+    }
+
+    @Override
+    public PaymentTransactionInfoPlugin creditPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbTransactionId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context)
+            throws PaymentPluginApiException {
+        return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.CREDIT, amount, currency);
     }
 
     @Override
-    public PaymentInfoPlugin getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) throws PaymentPluginApiException {
-        final PaymentInfoPlugin payment = payments.get(kbPaymentId.toString());
-        if (payment == null) {
+    public List<PaymentTransactionInfoPlugin> getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentPluginApiException {
+        /*
+        final InternalPaymentInfo paymentInfo = payments.get(kbPaymentId.toString());
+        if (paymentInfo == null) {
             throw new PaymentPluginApiException("", "No payment found for payment id " + kbPaymentId.toString());
         }
-        return payment;
+        */
+        // Can't be implemented because we did not keep transaction details.
+        return ImmutableList.<PaymentTransactionInfoPlugin>of();
     }
 
     @Override
-    public Pagination<PaymentInfoPlugin> searchPayments(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
-        final ImmutableList<PaymentInfoPlugin> results = ImmutableList.<PaymentInfoPlugin>copyOf(Iterables.<PaymentInfoPlugin>filter(payments.values(), new Predicate<PaymentInfoPlugin>() {
-            @Override
-            public boolean apply(final PaymentInfoPlugin input) {
-                return (input.getKbPaymentId() != null && input.getKbPaymentId().toString().equals(searchKey)) ||
-                       (input.getFirstPaymentReferenceId() != null && input.getFirstPaymentReferenceId().contains(searchKey)) ||
-                       (input.getSecondPaymentReferenceId() != null && input.getSecondPaymentReferenceId().contains(searchKey));
-            }
-        }));
-        return DefaultPagination.<PaymentInfoPlugin>build(offset, limit, results);
+    public Pagination<PaymentTransactionInfoPlugin> searchPayments(final String searchKey, final Long offset, final Long limit, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentPluginApiException {
+        throw new IllegalStateException("Not implemented");
     }
 
     @Override
-    public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
+    public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
         // externalPaymentMethodId is set to a random value
         final PaymentMethodPlugin realWithID = new TestPaymentMethodPlugin(kbPaymentMethodId, paymentMethodProps, UUID.randomUUID().toString());
         paymentMethods.put(kbPaymentMethodId.toString(), realWithID);
@@ -140,44 +258,45 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
-    public void deletePaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    public void deletePaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
         paymentMethods.remove(kbPaymentMethodId.toString());
         paymentMethodsInfo.remove(kbPaymentMethodId.toString());
     }
 
     @Override
-    public PaymentMethodPlugin getPaymentMethodDetail(final UUID kbAccountId, final UUID kbPaymentMethodId, final TenantContext context) throws PaymentPluginApiException {
+    public PaymentMethodPlugin getPaymentMethodDetail(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final TenantContext context) throws PaymentPluginApiException {
         return paymentMethods.get(kbPaymentMethodId.toString());
     }
 
     @Override
-    public void setDefaultPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    public void setDefaultPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
     }
 
     @Override
-    public List<PaymentMethodInfoPlugin> getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final CallContext context) {
+    public List<PaymentMethodInfoPlugin> getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final Iterable<PluginProperty> properties, final CallContext context) {
         return ImmutableList.<PaymentMethodInfoPlugin>copyOf(paymentMethodsInfo.values());
     }
 
     @Override
-    public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
+    public Pagination<PaymentMethodPlugin> searchPaymentMethods(final String searchKey, final Long offset, final Long limit, final Iterable<PluginProperty> properties, final TenantContext tenantContext) throws PaymentPluginApiException {
         final ImmutableList<PaymentMethodPlugin> results = ImmutableList.<PaymentMethodPlugin>copyOf(Iterables.<PaymentMethodPlugin>filter(paymentMethods.values(), new Predicate<PaymentMethodPlugin>() {
             @Override
             public boolean apply(final PaymentMethodPlugin input) {
-                return (input.getAddress1() != null && input.getAddress1().contains(searchKey)) ||
-                       (input.getAddress2() != null && input.getAddress2().contains(searchKey)) ||
-                       (input.getCCLast4() != null && input.getCCLast4().contains(searchKey)) ||
-                       (input.getCCName() != null && input.getCCName().contains(searchKey)) ||
-                       (input.getCity() != null && input.getCity().contains(searchKey)) ||
-                       (input.getState() != null && input.getState().contains(searchKey)) ||
-                       (input.getCountry() != null && input.getCountry().contains(searchKey));
+                if (input.getProperties() !=  null) {
+                    for (PluginProperty cur : input.getProperties()) {
+                        if (cur.getValue().equals(searchKey)) {
+                            return true;
+                        }
+                    }
+                }
+                return (input.getKbPaymentMethodId().toString().equals(searchKey));
             }
         }));
         return DefaultPagination.<PaymentMethodPlugin>build(offset, limit, results);
     }
 
     @Override
-    public void resetPaymentMethods(final UUID kbAccountId, final List<PaymentMethodInfoPlugin> input) {
+    public void resetPaymentMethods(final UUID kbAccountId, final List<PaymentMethodInfoPlugin> input, final Iterable<PluginProperty> properties, final CallContext callContext) {
         paymentMethodsInfo.clear();
         if (input != null) {
             for (final PaymentMethodInfoPlugin cur : input) {
@@ -187,42 +306,46 @@ public class MockPaymentProviderPlugin implements NoOpPaymentPluginApi {
     }
 
     @Override
-    public RefundInfoPlugin processRefund(final UUID kbAccountId, final UUID kbPaymentId, final BigDecimal refundAmount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
-        final PaymentInfoPlugin paymentInfoPlugin = getPaymentInfo(kbAccountId, kbPaymentId, context);
-        if (paymentInfoPlugin == null) {
-            throw new PaymentPluginApiException("", String.format("No payment found for payment id %s (plugin %s)", kbPaymentId.toString(), PLUGIN_NAME));
-        }
+    public HostedPaymentPageFormDescriptor buildFormDescriptor(final UUID kbAccountId, final Iterable<PluginProperty> customFields, final Iterable<PluginProperty> properties, final CallContext callContext) {
+        return null;
+    }
 
-        BigDecimal maxAmountRefundable = paymentInfoPlugin.getAmount();
-        for (final RefundInfoPlugin refund : refunds.get(kbPaymentId.toString())) {
-            maxAmountRefundable = maxAmountRefundable.add(refund.getAmount().negate());
+    @Override
+    public GatewayNotification processNotification(final String notification, final Iterable<PluginProperty> properties, final CallContext callContext) throws PaymentPluginApiException {
+        return null;
+    }
+
+    @Override
+    public PaymentTransactionInfoPlugin refundPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbPaymentMethodId, final UUID kbTransactionId, final BigDecimal refundAmount, final Currency currency, final Iterable<PluginProperty> properties, final CallContext context) throws PaymentPluginApiException {
+
+        final InternalPaymentInfo info = payments.get(kbPaymentId.toString());
+        if (info == null) {
+            throw new PaymentPluginApiException("", String.format("No payment found for payment id %s (plugin %s)", kbPaymentId.toString(), PLUGIN_NAME));
         }
-        if (maxAmountRefundable.compareTo(refundAmount) < 0) {
+        BigDecimal maxAmountRefundable = info.getCaptureAmount().add(info.getPurchasedAmount());
+        if (maxAmountRefundable.compareTo(info.getRefundAmount()) < 0) {
             throw new PaymentPluginApiException("", String.format("Refund amount of %s for payment id %s is bigger than the payment amount %s (plugin %s)",
-                                                                  refundAmount, kbPaymentId.toString(), paymentInfoPlugin.getAmount(), PLUGIN_NAME));
+                                                                  refundAmount, kbPaymentId.toString(), maxAmountRefundable, PLUGIN_NAME));
         }
+        return getPaymentTransactionInfoPluginResult(kbPaymentId, kbTransactionId, TransactionType.REFUND, refundAmount, currency);
+    }
 
-        final DefaultNoOpRefundInfoPlugin refundInfoPlugin = new DefaultNoOpRefundInfoPlugin(kbPaymentId, refundAmount, currency, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
-        refunds.put(kbPaymentId.toString(), refundInfoPlugin);
+    private PaymentTransactionInfoPlugin getPaymentTransactionInfoPluginResult(final UUID kbPaymentId, final UUID kbTransactionId, final TransactionType type, final BigDecimal amount, final Currency currency) throws PaymentPluginApiException {
 
-        return refundInfoPlugin;
-    }
+        if (makeNextInvoiceFailWithException.getAndSet(false)) {
+            throw new PaymentPluginApiException("", "test error");
+        }
 
-    @Override
-    public List<RefundInfoPlugin> getRefundInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) throws PaymentPluginApiException {
-        return Collections.<RefundInfoPlugin>emptyList();
-    }
+        final PaymentPluginStatus status = (makeAllInvoicesFailWithError.get() || makeNextInvoiceFailWithError.getAndSet(false)) ? PaymentPluginStatus.ERROR : PaymentPluginStatus.PROCESSED;
 
-    @Override
-    public Pagination<RefundInfoPlugin> searchRefunds(final String searchKey, final Long offset, final Long limit, final TenantContext tenantContext) throws PaymentPluginApiException {
-        final ImmutableList<RefundInfoPlugin> results = ImmutableList.<RefundInfoPlugin>copyOf(Iterables.<RefundInfoPlugin>filter(refunds.values(), new Predicate<RefundInfoPlugin>() {
-            @Override
-            public boolean apply(final RefundInfoPlugin input) {
-                return (input.getKbPaymentId() != null && input.getKbPaymentId().toString().equals(searchKey)) ||
-                       (input.getFirstRefundReferenceId() != null && input.getFirstRefundReferenceId().contains(searchKey)) ||
-                       (input.getSecondRefundReferenceId() != null && input.getSecondRefundReferenceId().contains(searchKey));
-            }
-        }));
-        return DefaultPagination.<RefundInfoPlugin>build(offset, limit, results);
+        InternalPaymentInfo info = payments.get(kbPaymentId.toString());
+        if (info == null) {
+            info = new InternalPaymentInfo();
+            payments.put(kbPaymentId.toString(), info);
+        }
+        info.addAmount(type, amount);
+
+        final PaymentTransactionInfoPlugin result = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, type, amount, currency, clock.getUTCNow(), clock.getUTCNow(), status, null);
+        return result;
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPluginModule.java b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPluginModule.java
index 2edf947..adf299e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPluginModule.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/MockPaymentProviderPluginModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,17 +18,19 @@
 
 package org.killbill.billing.payment.provider;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.clock.Clock;
 
-import com.google.inject.AbstractModule;
 import com.google.inject.name.Names;
 
-public class MockPaymentProviderPluginModule extends AbstractModule {
+public class MockPaymentProviderPluginModule extends KillBillModule {
 
     private final String instanceName;
     private final Clock clock;
 
-    public MockPaymentProviderPluginModule(final String instanceName, final Clock clock) {
+    public MockPaymentProviderPluginModule(final String instanceName, final Clock clock, final KillbillConfigSource configSource) {
+        super(configSource);
         this.instanceName = instanceName;
         this.clock = clock;
     }
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java
index b0c0907..c4efb54 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentInfoPlugin.java
@@ -20,6 +20,7 @@ import java.math.BigDecimal;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.killbill.billing.payment.api.TransactionType;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -32,21 +33,22 @@ public class TestDefaultNoOpPaymentInfoPlugin extends PaymentTestSuiteNoDB {
     @Test(groups = "fast")
     public void testEquals() throws Exception {
         final UUID kbPaymentId = UUID.randomUUID();
+        final UUID kbTransactionId = UUID.randomUUID();
         final BigDecimal amount = new BigDecimal("1.394810E-3");
         final DateTime effectiveDate = clock.getUTCNow().plusDays(1);
         final DateTime createdDate = clock.getUTCNow();
         final PaymentPluginStatus status = PaymentPluginStatus.UNDEFINED;
         final String error = UUID.randomUUID().toString();
 
-        final DefaultNoOpPaymentInfoPlugin info = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, amount, Currency.USD, effectiveDate, createdDate,
+        final DefaultNoOpPaymentInfoPlugin info = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, Currency.USD, effectiveDate, createdDate,
                                                                                    status, error);
         Assert.assertEquals(info, info);
 
-        final DefaultNoOpPaymentInfoPlugin sameInfo = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, amount, Currency.USD, effectiveDate, createdDate,
+        final DefaultNoOpPaymentInfoPlugin sameInfo = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, Currency.USD, effectiveDate, createdDate,
                                                                                        status, error);
         Assert.assertEquals(sameInfo, info);
 
-        final DefaultNoOpPaymentInfoPlugin otherInfo = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, amount, Currency.USD, effectiveDate, createdDate,
+        final DefaultNoOpPaymentInfoPlugin otherInfo = new DefaultNoOpPaymentInfoPlugin(kbPaymentId, kbTransactionId, TransactionType.PURCHASE, amount, Currency.USD, effectiveDate, createdDate,
                                                                                         status, UUID.randomUUID().toString());
         Assert.assertNotEquals(otherInfo, info);
     }
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentMethodPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentMethodPlugin.java
index 566cf50..04808e4 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentMethodPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/TestDefaultNoOpPaymentMethodPlugin.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,12 +21,11 @@ package org.killbill.billing.payment.provider;
 import java.util.List;
 import java.util.UUID;
 
+import org.killbill.billing.payment.PaymentTestSuiteNoDB;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
-import org.killbill.billing.payment.PaymentTestSuiteNoDB;
-import org.killbill.billing.payment.api.PaymentMethodKVInfo;
-
 import com.google.common.collect.ImmutableList;
 
 public class TestDefaultNoOpPaymentMethodPlugin extends PaymentTestSuiteNoDB {
@@ -33,7 +34,7 @@ public class TestDefaultNoOpPaymentMethodPlugin extends PaymentTestSuiteNoDB {
     public void testEquals() throws Exception {
         final String externalId = UUID.randomUUID().toString();
         final boolean isDefault = false;
-        final List<PaymentMethodKVInfo> props = ImmutableList.<PaymentMethodKVInfo>of(new PaymentMethodKVInfo(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false));
+        final List<PluginProperty> props = ImmutableList.<PluginProperty>of(new PluginProperty(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false));
 
         final DefaultNoOpPaymentMethodPlugin paymentMethod = new DefaultNoOpPaymentMethodPlugin(externalId, isDefault, props);
         Assert.assertEquals(paymentMethod, paymentMethod);
@@ -41,23 +42,23 @@ public class TestDefaultNoOpPaymentMethodPlugin extends PaymentTestSuiteNoDB {
         final DefaultNoOpPaymentMethodPlugin samePaymentMethod = new DefaultNoOpPaymentMethodPlugin(externalId, isDefault, props);
         Assert.assertEquals(samePaymentMethod, paymentMethod);
 
-        final DefaultNoOpPaymentMethodPlugin otherPaymentMethod = new DefaultNoOpPaymentMethodPlugin(externalId, isDefault, ImmutableList.<PaymentMethodKVInfo>of());
+        final DefaultNoOpPaymentMethodPlugin otherPaymentMethod = new DefaultNoOpPaymentMethodPlugin(externalId, isDefault, ImmutableList.<PluginProperty>of());
         Assert.assertNotEquals(otherPaymentMethod, paymentMethod);
     }
 
     @Test(groups = "fast")
-    public void testEqualsForPaymentMethodKVInfo() throws Exception {
+    public void testEqualsForPluginProperty() throws Exception {
         final String key = UUID.randomUUID().toString();
         final Object value = UUID.randomUUID();
         final boolean updatable = false;
 
-        final PaymentMethodKVInfo kvInfo = new PaymentMethodKVInfo(key, value, updatable);
+        final PluginProperty kvInfo = new PluginProperty(key, value, updatable);
         Assert.assertEquals(kvInfo, kvInfo);
 
-        final PaymentMethodKVInfo sameKvInfo = new PaymentMethodKVInfo(key, value, updatable);
+        final PluginProperty sameKvInfo = new PluginProperty(key, value, updatable);
         Assert.assertEquals(sameKvInfo, kvInfo);
 
-        final PaymentMethodKVInfo otherKvInfo = new PaymentMethodKVInfo(key, value, !updatable);
+        final PluginProperty otherKvInfo = new PluginProperty(key, value, !updatable);
         Assert.assertNotEquals(otherKvInfo, kvInfo);
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/provider/TestExternalPaymentProviderPlugin.java b/payment/src/test/java/org/killbill/billing/payment/provider/TestExternalPaymentProviderPlugin.java
index fe00cea..3241ea5 100644
--- a/payment/src/test/java/org/killbill/billing/payment/provider/TestExternalPaymentProviderPlugin.java
+++ b/payment/src/test/java/org/killbill/billing/payment/provider/TestExternalPaymentProviderPlugin.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -17,21 +19,22 @@
 package org.killbill.billing.payment.provider;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 
-import org.mockito.Mockito;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.payment.PaymentTestSuiteNoDB;
-import org.killbill.billing.payment.plugin.api.PaymentInfoPlugin;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.plugin.api.PaymentTransactionInfoPlugin;
 import org.killbill.billing.payment.plugin.api.PaymentPluginStatus;
-import org.killbill.billing.payment.plugin.api.PaymentPluginApiException;
 import org.killbill.clock.Clock;
 import org.killbill.clock.ClockMock;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
 
 public class TestExternalPaymentProviderPlugin extends PaymentTestSuiteNoDB {
 
@@ -45,22 +48,23 @@ public class TestExternalPaymentProviderPlugin extends PaymentTestSuiteNoDB {
         plugin = new ExternalPaymentProviderPlugin(clock);
     }
 
-
     @Test(groups = "fast")
     public void testProcessPayment() throws Exception {
-
+        final List<PluginProperty> properties = ImmutableList.<PluginProperty>of();
         final UUID accountId = UUID.randomUUID();
         final UUID paymentId = UUID.randomUUID();
+        final UUID kbTransactionId = UUID.randomUUID();
         final UUID paymentMethodId = UUID.randomUUID();
         final BigDecimal amount = BigDecimal.TEN;
-        final PaymentInfoPlugin paymentInfoPlugin = plugin.processPayment(accountId, paymentId, paymentMethodId, amount, Currency.BRL, callContext);
+        final PaymentTransactionInfoPlugin paymentInfoPlugin = plugin.purchasePayment(accountId, paymentId, kbTransactionId, paymentMethodId, amount, Currency.BRL, properties, callContext);
 
         Assert.assertEquals(paymentInfoPlugin.getAmount(), amount);
         Assert.assertNull(paymentInfoPlugin.getGatewayError());
         Assert.assertNull(paymentInfoPlugin.getGatewayErrorCode());
         Assert.assertEquals(paymentInfoPlugin.getStatus(), PaymentPluginStatus.PROCESSED);
 
-        final PaymentInfoPlugin retrievedPaymentInfoPlugin = plugin.getPaymentInfo(accountId, paymentId, callContext);
-        Assert.assertEquals(retrievedPaymentInfoPlugin.getStatus(), PaymentPluginStatus.PROCESSED);
+        final List<PaymentTransactionInfoPlugin> retrievedPaymentTransactionInfoPlugin = plugin.getPaymentInfo(accountId, paymentId, properties, callContext);
+        // getPaymentInfo mock is not implemented (yet)
+        //Assert.assertEquals(retrievedPaymentTransactionInfoPlugin.get(0).getStatus(), PaymentPluginStatus.PROCESSED);
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestDefaultStateMachineConfigDOTGenerator.java b/payment/src/test/java/org/killbill/billing/payment/TestDefaultStateMachineConfigDOTGenerator.java
new file mode 100644
index 0000000..bd4245b
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/TestDefaultStateMachineConfigDOTGenerator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * Groupon licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment;
+
+import org.killbill.automaton.DefaultStateMachineConfig;
+import org.killbill.automaton.dot.DefaultStateMachineConfigDOTGenerator;
+import org.killbill.xmlloader.XMLLoader;
+import org.testng.annotations.Test;
+
+import com.google.common.io.Resources;
+
+public class TestDefaultStateMachineConfigDOTGenerator extends PaymentTestSuiteNoDB {
+
+    @Test(groups = "fast")
+    public void testStateMachine() throws Exception {
+        final DefaultStateMachineConfig sms = XMLLoader.getObjectFromString(Resources.getResource("org/killbill/billing/payment/PaymentStates.xml").toExternalForm(), DefaultStateMachineConfig.class);
+
+        final DefaultStateMachineConfigDOTGenerator generator = new DefaultStateMachineConfigDOTGenerator("Payment", sms);
+        generator.build();
+
+        System.out.println(generator.toString());
+        System.out.flush();
+
+        //Files.write((new File("/var/tmp/PaymentStates.dot")).toPath(), generator.toString().getBytes());
+    }
+}
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
new file mode 100644
index 0000000..422ccfd
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/TestJanitor.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.payment;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.payment.api.Payment;
+import org.killbill.billing.payment.api.PaymentTransaction;
+import org.killbill.billing.payment.api.PaymentApiException;
+import org.killbill.billing.payment.api.PaymentOptions;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
+import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
+import org.killbill.billing.payment.core.Janitor;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
+import org.killbill.billing.payment.glue.TestPaymentModuleWithEmbeddedDB;
+import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+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.common.collect.ImmutableMap;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestJanitor extends PaymentTestSuiteWithEmbeddedDB {
+
+    final PaymentOptions INVOICE_PAYMENT = new PaymentOptions() {
+        @Override
+        public boolean isExternalPayment() {
+            return false;
+        }
+
+        @Override
+        public String getPaymentControlPluginName() {
+            return InvoicePaymentControlPluginApi.PLUGIN_NAME;
+        }
+    };
+
+
+    @Inject
+    private Janitor janitor;
+
+    private Account account;
+
+    @Override
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/payment.properties",
+                               ImmutableMap.<String, String>of("org.killbill.payment.provider.default", MockPaymentProviderPlugin.PLUGIN_NAME,
+                                                               "killbill.payment.engine.events.off", "false",
+                                                               "org.killbill.payment.janitor.rate", "500ms")
+                              );
+    }
+
+    @BeforeClass(groups = "slow")
+    protected void beforeClass() throws Exception {
+        super.beforeClass();
+        janitor.start();
+    }
+
+    @AfterClass(groups = "slow")
+    protected void afterClass() throws Exception {
+        janitor.stop();
+    }
+
+
+    @BeforeMethod(groups = "slow")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        account = testHelper.createTestAccount("bobo@gmail.com", true);
+    }
+
+    @AfterMethod(groups = "slow")
+    public void afterMethod() throws Exception {
+        super.afterMethod();
+    }
+
+    @Test(groups = "slow")
+    public void testCreateSuccessPurchaseWithPaymentControl() throws PaymentApiException, InvoiceApiException, EventBusException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final LocalDate now = clock.getUTCToday();
+
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+
+        final String paymentExternalKey = invoice.getId().toString();
+        final String transactionExternalKey = "wouf wouf";
+
+        invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
+                                                            subscriptionId,
+                                                            bundleId,
+                                                            "test plan",
+                                                            "test phase", null,
+                                                            now,
+                                                            now.plusMonths(1),
+                                                            requestedAmount,
+                                                            new BigDecimal("1.0"),
+                                                            Currency.USD));
+
+        final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+        assertEquals(payment.getTransactions().size(), 1);
+        assertEquals(payment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        assertEquals(payment.getTransactions().get(0).getTransactionType(), TransactionType.PURCHASE);
+
+        final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
+        assertEquals(attempts.size(), 1);
+
+        final PaymentAttemptModelDao attempt = attempts.get(0);
+        assertEquals(attempt.getStateName(), "SUCCESS");
+
+        // Ok now the fun part starts... we modify the attempt state to be 'INIT' and wait the the Janitor to do its job.
+        paymentDao.updatePaymentAttempt(attempt.getId(), attempt.getTransactionId(), "INIT", internalCallContext);
+        final PaymentAttemptModelDao attempt2 = paymentDao.getPaymentAttempt(attempt.getId(), internalCallContext);
+        assertEquals(attempt2.getStateName(), "INIT");
+
+        clock.addDays(1);
+        try { Thread.sleep(1500); } catch (InterruptedException e) {};
+
+        final PaymentAttemptModelDao attempt3 = paymentDao.getPaymentAttempt(attempt.getId(), internalCallContext);
+        assertEquals(attempt3.getStateName(), "SUCCESS");
+    }
+
+    @Test(groups = "slow")
+    public void testCreateSuccessRefundPaymentControlWithItemAdjustments() throws PaymentApiException, InvoiceApiException, EventBusException {
+
+        final BigDecimal requestedAmount = BigDecimal.TEN;
+        final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final LocalDate now = clock.getUTCToday();
+
+        final Invoice invoice = testHelper.createTestInvoice(account, now, Currency.USD);
+
+        final String paymentExternalKey = invoice.getId().toString();
+        final String transactionExternalKey = "craboom";
+        final String transactionExternalKey2 = "qwerty";
+
+        final InvoiceItem invoiceItem = new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
+                                                                     subscriptionId,
+                                                                     bundleId,
+                                                                     "test plan", "test phase", null,
+                                                                     now,
+                                                                     now.plusMonths(1),
+                                                                     requestedAmount,
+                                                                     new BigDecimal("1.0"),
+                                                                     Currency.USD);
+        invoice.addInvoiceItem(invoiceItem);
+
+        final Payment payment = paymentApi.createPurchaseWithPaymentControl(account, account.getPaymentMethodId(), null, requestedAmount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                                                  createPropertiesForInvoice(invoice), INVOICE_PAYMENT, callContext);
+
+        final List<PluginProperty> refundProperties = new ArrayList<PluginProperty>();
+        final HashMap<UUID, BigDecimal> uuidBigDecimalHashMap = new HashMap<UUID, BigDecimal>();
+        uuidBigDecimalHashMap.put(invoiceItem.getId(), new BigDecimal("1.0"));
+        final PluginProperty refundIdsProp = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_REFUND_IDS_WITH_AMOUNT_KEY, uuidBigDecimalHashMap, false);
+        refundProperties.add(refundIdsProp);
+
+        final Payment payment2 = paymentApi.createRefundWithPaymentControl(account, payment.getId(), null, Currency.USD, transactionExternalKey2,
+                                                                                 refundProperties, INVOICE_PAYMENT, callContext);
+
+        assertEquals(payment2.getTransactions().size(), 2);
+        PaymentTransaction refundTransaction = payment2.getTransactions().get(1);
+        assertEquals(refundTransaction.getTransactionType(), TransactionType.REFUND);
+
+        final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
+        assertEquals(attempts.size(), 2);
+
+        final PaymentAttemptModelDao refundAttempt = attempts.get(1);
+        assertEquals(refundAttempt.getTransactionType(), TransactionType.REFUND);
+
+        // Ok now the fun part starts... we modify the attempt state to be 'INIT' and wait the the Janitor to do its job.
+        paymentDao.updatePaymentAttempt(refundAttempt.getId(), refundAttempt.getTransactionId(), "INIT", internalCallContext);
+        final PaymentAttemptModelDao attempt2 = paymentDao.getPaymentAttempt(refundAttempt.getId(), internalCallContext);
+        assertEquals(attempt2.getStateName(), "INIT");
+
+        clock.addDays(1);
+        try { Thread.sleep(1500); } catch (InterruptedException e) {};
+
+        final PaymentAttemptModelDao attempt3 = paymentDao.getPaymentAttempt(refundAttempt.getId(), internalCallContext);
+        assertEquals(attempt3.getStateName(), "SUCCESS");
+
+    }
+
+
+
+    private List<PluginProperty> createPropertiesForInvoice(final Invoice invoice) {
+        final List<PluginProperty> result = new ArrayList<PluginProperty>();
+        result.add(new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false));
+        return result;
+    }
+}
+
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestPaymentHelper.java b/payment/src/test/java/org/killbill/billing/payment/TestPaymentHelper.java
index 9bdb5fe..562a94d 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestPaymentHelper.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestPaymentHelper.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,27 +21,28 @@ package org.killbill.billing.payment;
 import java.util.UUID;
 
 import org.joda.time.LocalDate;
-import org.mockito.Mockito;
-
 import org.killbill.billing.account.api.Account;
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
+import org.killbill.billing.events.InvoiceCreationInternalEvent;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
+import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentMethodPlugin;
+import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
 import org.killbill.billing.util.callcontext.CallContext;
-import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
 import org.killbill.clock.Clock;
-import org.killbill.billing.events.InvoiceCreationInternalEvent;
-import org.killbill.billing.account.api.AccountInternalApi;
-import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.mockito.Mockito;
 
+import com.google.common.collect.ImmutableList;
 import com.google.inject.Inject;
 
 public class TestPaymentHelper {
@@ -51,25 +54,23 @@ public class TestPaymentHelper {
     private final Clock clock;
 
     private final CallContext context;
-    private final InternalCallContext internalCallContext;
 
     @Inject
     public TestPaymentHelper(final AccountInternalApi AccountApi, final InvoiceInternalApi invoiceApi,
-                             final PaymentApi paymentApi, final PersistentBus eventBus, final Clock clock,
-                             final CallContext context, final InternalCallContext internalCallContext) {
+                             final PaymentApi paymentApi, final PersistentBus eventBus,
+                             final Clock clock,
+                             final CallContext context) {
         this.eventBus = eventBus;
         this.AccountApi = AccountApi;
         this.invoiceApi = invoiceApi;
         this.paymentApi = paymentApi;
         this.clock = clock;
         this.context = context;
-        this.internalCallContext = internalCallContext;
     }
 
     public Invoice createTestInvoice(final Account account,
                                      final LocalDate targetDate,
                                      final Currency currency,
-                                     final CallContext context,
                                      final InvoiceItem... items) throws EventBusException, InvoiceApiException {
         final Invoice invoice = new MockInvoice(account.getId(), clock.getUTCToday(), targetDate, currency);
 
@@ -92,6 +93,8 @@ public class TestPaymentHelper {
         }
 
         Mockito.when(invoiceApi.getInvoiceById(Mockito.eq(invoice.getId()), Mockito.<InternalTenantContext>any())).thenReturn(invoice);
+        Mockito.when(invoiceApi.getInvoiceForPaymentId(Mockito.<UUID>any(), Mockito.<InternalCallContext>any())).thenReturn(invoice);
+
         final InvoiceCreationInternalEvent event = new MockInvoiceCreationEvent(invoice.getId(), invoice.getAccountId(),
                                                                                 invoice.getBalance(), invoice.getCurrency(),
                                                                                 invoice.getInvoiceDate(), 1L, 2L, null);
@@ -127,7 +130,7 @@ public class TestPaymentHelper {
     }
 
     public void addTestPaymentMethod(final Account account, final PaymentMethodPlugin paymentMethodInfo) throws Exception {
-        final UUID paymentMethodId = paymentApi.addPaymentMethod(MockPaymentProviderPlugin.PLUGIN_NAME, account, true, paymentMethodInfo, context);
+        final UUID paymentMethodId = paymentApi.addPaymentMethod(account, paymentMethodInfo.getExternalPaymentMethodId(), MockPaymentProviderPlugin.PLUGIN_NAME, true, paymentMethodInfo, ImmutableList.<PluginProperty>of(), context);
         Mockito.when(account.getPaymentMethodId()).thenReturn(paymentMethodId);
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
index be4208a..9b9537e 100644
--- a/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/org/killbill/billing/payment/TestRetryService.java
@@ -17,6 +17,7 @@
 package org.killbill.billing.payment;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -25,21 +26,30 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.TimeoutException;
 
 import org.joda.time.LocalDate;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.payment.api.Payment;
-import org.killbill.billing.payment.api.PaymentAttempt;
 import org.killbill.billing.payment.api.PaymentApiException;
-import org.killbill.billing.payment.api.PaymentStatus;
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.control.InvoicePaymentControlPluginApi;
+import org.killbill.billing.payment.dao.MockPaymentDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
+import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
 import org.killbill.billing.payment.glue.DefaultPaymentService;
 import org.killbill.billing.payment.provider.MockPaymentProviderPlugin;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.jayway.awaitility.Awaitility;
+import com.jayway.awaitility.Duration;
 
 import static com.jayway.awaitility.Awaitility.await;
+import static com.jayway.awaitility.Awaitility.setDefaultPollInterval;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
@@ -53,14 +63,16 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
-        pluginRetryService.initialize(DefaultPaymentService.SERVICE_NAME);
-        pluginRetryService.start();
 
-        retryService.initialize(DefaultPaymentService.SERVICE_NAME);
-        retryService.start();
+        setDefaultPollInterval(Duration.ONE_HUNDRED_MILLISECONDS);
+        Awaitility.setDefaultPollDelay(Duration.SAME_AS_POLL_INTERVAL);
 
+        ((MockPaymentDao) paymentDao).reset();
         mockPaymentProviderPlugin = (MockPaymentProviderPlugin) registry.getServiceForName(MockPaymentProviderPlugin.PLUGIN_NAME);
         mockPaymentProviderPlugin.clear();
+        retryService.initialize(DefaultPaymentService.SERVICE_NAME);
+        retryService.start();
+
     }
 
     @Override
@@ -68,51 +80,47 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
     public void afterMethod() throws Exception {
         super.afterMethod();
         retryService.stop();
-        pluginRetryService.stop();
     }
 
-    private Payment getPaymentForInvoice(final UUID invoiceId) throws PaymentApiException {
-        final List<Payment> payments = paymentProcessor.getInvoicePayments(invoiceId, internalCallContext);
-        assertEquals(payments.size(), 1);
-        final Payment payment = payments.get(0);
-        assertEquals(payment.getInvoiceId(), invoiceId);
+    private Payment getPaymentForExternalKey(final String externalKey) throws PaymentApiException {
+        final Payment payment = paymentProcessor.getPaymentByExternalKey(externalKey, false, ImmutableList.<PluginProperty>of(), callContext, internalCallContext);
         return payment;
     }
 
     @Test(groups = "fast")
     public void testFailedPluginWithOneSuccessfulRetry() throws Exception {
-        testSchedulesRetryInternal(1, FailureType.PLUGIN_EXCEPTION);
+        testSchedulesRetryInternal(1, true, FailureType.PLUGIN_EXCEPTION);
     }
 
     @Test(groups = "fast")
-    public void testFailedPpluginWithLastRetrySuccess() throws Exception {
-        testSchedulesRetryInternal(paymentConfig.getPluginFailureRetryMaxAttempts(), FailureType.PLUGIN_EXCEPTION);
+    public void testFailedPluginWithLastRetrySuccess() throws Exception {
+        testSchedulesRetryInternal(paymentConfig.getPluginFailureRetryMaxAttempts(), true, FailureType.PLUGIN_EXCEPTION);
     }
 
     @Test(groups = "fast")
     public void testAbortedPlugin() throws Exception {
-        testSchedulesRetryInternal(paymentConfig.getPluginFailureRetryMaxAttempts() + 1, FailureType.PLUGIN_EXCEPTION);
+        testSchedulesRetryInternal(paymentConfig.getPluginFailureRetryMaxAttempts(), false, FailureType.PLUGIN_EXCEPTION);
     }
 
     @Test(groups = "fast")
     public void testFailedPaymentWithOneSuccessfulRetry() throws Exception {
-        testSchedulesRetryInternal(1, FailureType.PAYMENT_FAILURE);
+        testSchedulesRetryInternal(1, true, FailureType.PAYMENT_FAILURE);
     }
 
     @Test(groups = "fast")
     public void testFailedPaymentWithLastRetrySuccess() throws Exception {
-        testSchedulesRetryInternal(paymentConfig.getPaymentRetryDays().size(), FailureType.PAYMENT_FAILURE);
+        testSchedulesRetryInternal(paymentConfig.getPaymentRetryDays().size(), true, FailureType.PAYMENT_FAILURE);
     }
 
     @Test(groups = "fast")
     public void testAbortedPayment() throws Exception {
-        testSchedulesRetryInternal(paymentConfig.getPaymentRetryDays().size() + 1, FailureType.PAYMENT_FAILURE);
+        testSchedulesRetryInternal(paymentConfig.getPaymentRetryDays().size(), false, FailureType.PAYMENT_FAILURE);
     }
 
-    private void testSchedulesRetryInternal(final int maxTries, final FailureType failureType) throws Exception {
+    private void testSchedulesRetryInternal(final int maxTries, final boolean lastSuccess, final FailureType failureType) throws Exception {
 
         final Account account = testHelper.createTestAccount("yiyi.gmail.com", true);
-        final Invoice invoice = testHelper.createTestInvoice(account, clock.getUTCToday(), Currency.USD, callContext);
+        final Invoice invoice = testHelper.createTestInvoice(account, clock.getUTCToday(), Currency.USD);
         final BigDecimal amount = new BigDecimal("10.00");
         final UUID subscriptionId = UUID.randomUUID();
         final UUID bundleId = UUID.randomUUID();
@@ -130,69 +138,78 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
                                                             new BigDecimal("1.0"),
                                                             Currency.USD));
         setPaymentFailure(failureType);
+
         boolean failed = false;
+        final String paymentExternalKey = UUID.randomUUID().toString();
+        final String transactionExternalKey = UUID.randomUUID().toString();
         try {
-            paymentProcessor.createPayment(account, invoice.getId(), amount, internalCallContext, false, false);
-        } catch (PaymentApiException e) {
+            pluginControlledPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amount, Currency.USD, paymentExternalKey, transactionExternalKey,
+                                                            createPropertiesForInvoice(invoice), InvoicePaymentControlPluginApi.PLUGIN_NAME, callContext, internalCallContext);
+        } catch (final PaymentApiException e) {
             failed = true;
         }
         assertTrue(failed);
 
+        Payment payment = getPaymentForExternalKey(paymentExternalKey);
+        List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
+        assertEquals(attempts.size(), 1);
+
+        final List<PaymentTransactionModelDao> transactions = paymentDao.getTransactionsForPayment(payment.getId(), internalCallContext);
+        assertEquals(transactions.size(), 1);
+
+
         for (int curFailure = 0; curFailure < maxTries; curFailure++) {
 
-            if (curFailure < maxTries - 1) {
+            // Set plugin to fail with specific type unless this is the last attempt and we want a success
+            if (curFailure < (maxTries - 1) || !lastSuccess) {
                 setPaymentFailure(failureType);
             }
 
-            if (curFailure < getMaxRetrySizeForFailureType(failureType)) {
-
-                moveClockForFailureType(failureType, curFailure);
-                try {
-                    await().atMost(3, SECONDS).until(new Callable<Boolean>() {
-                        @Override
-                        public Boolean call() throws Exception {
-                            final Payment payment = getPaymentForInvoice(invoice.getId());
-                            return payment.getPaymentStatus() == PaymentStatus.SUCCESS;
-                        }
-                    });
-                } catch (TimeoutException e) {
-                    if (curFailure == maxTries - 1) {
-                        fail("Failed to find successful payment for attempt " + (curFailure + 1) + "/" + maxTries);
+            moveClockForFailureType(failureType, curFailure);
+            final int curFailureCondition = curFailure;
+
+
+
+            try {
+                await().atMost(5, SECONDS).until(new Callable<Boolean>() {
+                    @Override
+                    public Boolean call() throws Exception {
+                        final List<PaymentAttemptModelDao> attempts = paymentDao.getPaymentAttempts(paymentExternalKey, internalCallContext);
+                        final List<PaymentAttemptModelDao> filteredAttempts = ImmutableList.copyOf(Iterables.filter(attempts, new Predicate<PaymentAttemptModelDao>() {
+                            @Override
+                            public boolean apply(final PaymentAttemptModelDao input) {
+                                return input.getStateName().equals("SUCCESS") ||
+                                       input.getStateName().equals("RETRIED") ||
+                                       input.getStateName().equals("ABORTED");
+                            }
+                        }));
+                        return filteredAttempts.size() == curFailureCondition + 2;
                     }
-                }
+                });
+            } catch (final TimeoutException e) {
+                fail("Timeout curFailure = " + curFailureCondition);
             }
         }
-        final Payment payment = getPaymentForInvoice(invoice.getId());
-        final List<PaymentAttempt> attempts = payment.getAttempts();
-
+        attempts = paymentDao.getPaymentAttempts(payment.getExternalKey(), internalCallContext);
         final int expectedAttempts = maxTries < getMaxRetrySizeForFailureType(failureType) ?
-                maxTries + 1 : getMaxRetrySizeForFailureType(failureType) + 1;
+                                     maxTries + 1 : getMaxRetrySizeForFailureType(failureType) + 1;
         assertEquals(attempts.size(), expectedAttempts);
-        Collections.sort(attempts, new Comparator<PaymentAttempt>() {
+        Collections.sort(attempts, new Comparator<PaymentAttemptModelDao>() {
             @Override
-            public int compare(final PaymentAttempt o1, final PaymentAttempt o2) {
-                return o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
+            public int compare(final PaymentAttemptModelDao o1, final PaymentAttemptModelDao o2) {
+                return o1.getCreatedDate().compareTo(o2.getCreatedDate());
             }
         });
 
         for (int i = 0; i < attempts.size(); i++) {
-            final PaymentAttempt cur = attempts.get(i);
+            final PaymentAttemptModelDao cur = attempts.get(i);
             if (i < attempts.size() - 1) {
-                if (failureType == FailureType.PAYMENT_FAILURE) {
-                    assertEquals(cur.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE);
-                } else {
-                    assertEquals(cur.getPaymentStatus(), PaymentStatus.PLUGIN_FAILURE);
-                }
-            } else if (maxTries <= getMaxRetrySizeForFailureType(failureType)) {
-                assertEquals(cur.getPaymentStatus(), PaymentStatus.SUCCESS);
-                assertEquals(payment.getPaymentStatus(), PaymentStatus.SUCCESS);
+                assertEquals(cur.getStateName(), "RETRIED");
             } else {
-                if (failureType == FailureType.PAYMENT_FAILURE) {
-                    assertEquals(cur.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE_ABORTED);
-                    assertEquals(payment.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE_ABORTED);
+                if (lastSuccess) {
+                    assertEquals(cur.getStateName(), "SUCCESS");
                 } else {
-                    assertEquals(cur.getPaymentStatus(), PaymentStatus.PLUGIN_FAILURE_ABORTED);
-                    assertEquals(payment.getPaymentStatus(), PaymentStatus.PLUGIN_FAILURE_ABORTED);
+                    assertEquals(cur.getStateName(), "ABORTED");
                 }
             }
         }
@@ -211,13 +228,14 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
         }
     }
 
-    private void moveClockForFailureType(final FailureType failureType, final int curFailure) {
+    private void moveClockForFailureType(final FailureType failureType, final int curFailure) throws InterruptedException {
+        final int nbDays;
         if (failureType == FailureType.PAYMENT_FAILURE) {
-            final int nbDays = paymentConfig.getPaymentRetryDays().get(curFailure);
-            clock.addDays(nbDays + 1);
+            nbDays = paymentConfig.getPaymentRetryDays().get(curFailure) + 1;
         } else {
-            clock.addDays(1);
+            nbDays = 1;
         }
+        clock.addDays(nbDays);
     }
 
     private int getMaxRetrySizeForFailureType(final FailureType failureType) {
@@ -227,4 +245,11 @@ public class TestRetryService extends PaymentTestSuiteNoDB {
             return paymentConfig.getPluginFailureRetryMaxAttempts();
         }
     }
+
+    private List<PluginProperty> createPropertiesForInvoice(final Invoice invoice) {
+        final List<PluginProperty> result = new ArrayList<PluginProperty>();
+        result.add(new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, invoice.getId().toString(), false));
+        return result;
+    }
+
 }

pom.xml 11(+5 -6)

diff --git a/pom.xml b/pom.xml
index eb74d3e..fd19ffc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,8 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
   ~ Copyright 2010-2013 Ning, Inc.
+  ~ Copyright 2014 Groupon, Inc
   ~
-  ~ Ning licenses this file to you under the Apache License, version 2.0
+  ~ Groupon 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:
   ~
@@ -19,10 +20,10 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.5.30-usage</version>
+        <version>0.7.23-SNAPSHOT</version>
     </parent>
     <artifactId>killbill</artifactId>
-    <version>0.9.3-SNAPSHOT</version>
+    <version>0.11.10-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>
@@ -41,11 +42,9 @@
         <module>usage</module>
         <module>util</module>
         <module>jaxrs</module>
-        <module>server</module>
         <module>tenant</module>
-        <module>osgi</module>
-        <module>osgi-bundles</module>
         <module>currency</module>
+        <module>profiles</module>
     </modules>
     <scm>
         <connection>scm:git:git://github.com/killbill/killbill.git</connection>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ProfilingContainerResponseFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ProfilingContainerResponseFilter.java
new file mode 100644
index 0000000..e5d707e
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/filters/ProfilingContainerResponseFilter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.filters;
+
+import java.util.List;
+
+import org.killbill.billing.jaxrs.json.ProfilingDataJson;
+import org.killbill.billing.util.jackson.ObjectMapper;
+import org.killbill.commons.profiling.Profiling;
+import org.killbill.commons.profiling.ProfilingData;
+import org.killbill.commons.profiling.ProfilingFeature.ProfilingFeatureType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.google.inject.Singleton;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponse;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+
+@Singleton
+public class ProfilingContainerResponseFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+    private static final Logger logger = LoggerFactory.getLogger(ProfilingContainerResponseFilter.class);
+
+    private static final String PROFILING_HEADER_REQ = "X-Killbill-Profiling-Req";
+    private static final String PROFILING_HEADER_RESP = "X-Killbill-Profiling-Resp";
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    static {
+        mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false);
+    }
+
+    @Override
+    public ContainerRequest filter(final ContainerRequest request) {
+
+        final List<String> profilingHeaderRequests = request.getRequestHeader(PROFILING_HEADER_REQ);
+        final String profilingHeaderRequest = (profilingHeaderRequests == null || profilingHeaderRequests.isEmpty()) ? null : profilingHeaderRequests.get(0);
+        if (profilingHeaderRequest != null) {
+            try {
+                Profiling.setPerThreadProfilingData(profilingHeaderRequest);
+                // If we need to profile JAXRS let's do it...
+                final ProfilingData profilingData = Profiling.getPerThreadProfilingData();
+                if (profilingData.getProfileFeature().isProfilingJAXRS()) {
+                    profilingData.addStart(ProfilingFeatureType.JAXRS, request.getPath());
+                }
+            } catch (IllegalArgumentException e) {
+                logger.info("Profiling data output " + profilingHeaderRequest + " is not supported, profiling NOT enabled");
+            }
+        }
+        return request;
+    }
+
+    @Override
+    public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response) {
+        try {
+            final ProfilingData rawData = Profiling.getPerThreadProfilingData();
+            if (rawData != null) {
+                if (rawData.getProfileFeature().isProfilingJAXRS()) {
+                    rawData.addEnd(ProfilingFeatureType.JAXRS, request.getPath());
+                }
+                final ProfilingDataJson profilingData = new ProfilingDataJson(rawData);
+
+                final String value;
+                try {
+                    value = mapper.writeValueAsString(profilingData);
+                    response.getHttpHeaders().add(PROFILING_HEADER_RESP, value);
+                } catch (JsonProcessingException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        } finally {
+            Profiling.resetPerThreadProfilingData();
+        }
+        return response;
+    }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java
new file mode 100644
index 0000000..c0079d4
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/CleanupListener.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.listeners;
+
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Enumeration;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mysql.jdbc.AbandonedConnectionCleanupThread;
+
+public class CleanupListener implements ServletContextListener {
+
+    private static final Logger logger = LoggerFactory.getLogger(CleanupListener.class);
+
+    @Override
+    public void contextInitialized(final ServletContextEvent servletContextEvent) {
+    }
+
+    @Override
+    public void contextDestroyed(final ServletContextEvent servletContextEvent) {
+        final Enumeration<Driver> drivers = DriverManager.getDrivers();
+        while (drivers.hasMoreElements()) {
+            try {
+                final Driver driver = drivers.nextElement();
+                DriverManager.deregisterDriver(driver);
+            } catch (final SQLException e) {
+                logger.warn("Unable to de-register driver", e);
+            }
+        }
+
+        // See http://docs.oracle.com/cd/E17952_01/connector-j-relnotes-en/news-5-1-23.html
+        try {
+            AbandonedConnectionCleanupThread.shutdown();
+        } catch (final InterruptedException e) {
+            logger.warn("Unable to shutdown MySQL threads", e);
+        }
+    }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
new file mode 100644
index 0000000..21dfe24
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/listeners/KillbillGuiceListener.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.listeners;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import javax.servlet.ServletContext;
+
+import org.killbill.billing.jaxrs.resources.JaxRsResourceBase;
+import org.killbill.billing.server.filters.ProfilingContainerResponseFilter;
+import org.killbill.billing.jaxrs.util.KillbillEventHandler;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.platform.config.DefaultKillbillConfigSource;
+import org.killbill.billing.server.modules.KillbillServerModule;
+import org.killbill.billing.server.security.TenantFilter;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.commons.skeleton.modules.BaseServerModuleBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Module;
+import com.google.inject.servlet.ServletModule;
+
+public class KillbillGuiceListener extends KillbillPlatformGuiceListener {
+
+    private static final Logger logger = LoggerFactory.getLogger(KillbillGuiceListener.class);
+
+    private KillbillEventHandler killbilleventHandler;
+
+    @Override
+    protected ServletModule getServletModule() {
+        // Don't filter all requests through Jersey, only the JAX-RS APIs (otherwise,
+        // things like static resources, favicon, etc. are 404'ed)
+        final BaseServerModuleBuilder builder = new BaseServerModuleBuilder().setJaxrsUriPattern("(" + JaxRsResourceBase.PREFIX + "|" + JaxRsResourceBase.PLUGINS_PATH + ")" + "/.*")
+                                                                             .addJaxrsResource("org.killbill.billing.jaxrs.mappers")
+                                                                             .addJaxrsResource("org.killbill.billing.jaxrs.resources");
+
+        //
+        // Add jersey filters which are executed prior jersey write the output stream
+        //
+        builder.addJerseyFilter("com.sun.jersey.api.container.filter.LoggingFilter");
+
+        // The logging filter is still incompatible with the GZIP filter
+        //builder.addJerseyFilter(GZIPContentEncodingFilter.class.getName());
+        builder.addJerseyFilter(ProfilingContainerResponseFilter.class.getName());
+
+        // Add TenantFilter right after is multi-tenancy has been configured.
+        if (config.isMultiTenancyEnabled()) {
+            builder.addFilter("/*", TenantFilter.class);
+        }
+        return builder.build();
+    }
+
+
+    @Override
+    protected Module getModule(final ServletContext servletContext) {
+        return new KillbillServerModule(servletContext, config, configSource);
+    }
+
+    @Override
+    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
+        final ImmutableMap<String, String> defaultProperties = ImmutableMap.<String, String>of("org.killbill.server.updateCheck.url",
+                                                                                               "https://raw.github.com/killbill/killbill/master/profiles/killbill/src/main/resources/update-checker/killbill-server-update-list.properties");
+        return new DefaultKillbillConfigSource(defaultProperties);
+    }
+
+    @Override
+    protected void startLifecycleStage2() {
+        killbilleventHandler = injector.getInstance(KillbillEventHandler.class);
+
+        // Perform Bus registration
+        try {
+            killbillBusService.getBus().register(killbilleventHandler);
+        } catch (final PersistentBus.EventBusException e) {
+            logger.error("Failed to register for event notifications, this is bad exiting!", e);
+            System.exit(1);
+        }
+    }
+
+    @Override
+    protected void stopLifecycleStage2() {
+        try {
+            killbillBusService.getBus().unregister(killbilleventHandler);
+        } catch (final PersistentBus.EventBusException e) {
+            logger.warn("Failed to unregister for event notifications", e);
+        }
+    }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/log/ThreadNameBasedDiscriminator.java b/profiles/killbill/src/main/java/org/killbill/billing/server/log/ThreadNameBasedDiscriminator.java
new file mode 100644
index 0000000..41b9e48
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/log/ThreadNameBasedDiscriminator.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.log;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.sift.Discriminator;
+
+public class ThreadNameBasedDiscriminator implements Discriminator<ILoggingEvent> {
+
+    private static final String KEY = "threadName";
+    private static final String ORG_KILLBILL_DOT = "org.killbill.";
+    private static final String BILLING_DOT = "billing.";
+    private static final String COMMONS_DOT = "commons.";
+
+    private final KillbillSecurityManager killbillSecurityManager = new KillbillSecurityManager();
+
+    private boolean started;
+
+    @Override
+    public String getDiscriminatingValue(final ILoggingEvent iLoggingEvent) {
+        final String element = getKillbillCaller();
+        if (element != null) {
+            return element;
+        } else {
+            return Thread.currentThread().getName();
+        }
+    }
+
+    @Override
+    public String getKey() {
+        return KEY;
+    }
+
+    public void start() {
+        started = true;
+    }
+
+    public void stop() {
+        started = false;
+    }
+
+    public boolean isStarted() {
+        return started;
+    }
+
+    private String getKillbillCaller() {
+        final Class[] stackTrace = killbillSecurityManager.getClassContext();
+        if (stackTrace == null || stackTrace.length <= 3) {
+            return null;
+        }
+
+        // Skip first ones (i.e. skip this class)
+        for (int i = 4; i < stackTrace.length; i++) {
+            final Class aStackTrace = stackTrace[i];
+            final String className = aStackTrace.getName();
+            final char[] classNameChars = className.toCharArray();
+
+            // Try to be faster than using patterns or String methods
+            if (classNameChars.length > 13 &&
+                classNameChars[0] == 'o' &&
+                classNameChars[1] == 'r' &&
+                classNameChars[2] == 'g' &&
+                classNameChars[3] == '.' &&
+                classNameChars[4] == 'k' &&
+                classNameChars[5] == 'i' &&
+                classNameChars[6] == 'l' &&
+                classNameChars[7] == 'l' &&
+                classNameChars[8] == 'b' &&
+                classNameChars[9] == 'i' &&
+                classNameChars[10] == 'l' &&
+                classNameChars[11] == 'l' &&
+                classNameChars[12] == '.') {
+                String markerName = ORG_KILLBILL_DOT;
+
+                // Extract the killbill module for Kill Bill proper calls, otherwise get the top-level package
+                int startPosition = 13;
+                if (classNameChars.length > 21 &&
+                    classNameChars[13] == 'b' &&
+                    classNameChars[14] == 'i' &&
+                    classNameChars[15] == 'l' &&
+                    classNameChars[16] == 'l' &&
+                    classNameChars[17] == 'i' &&
+                    classNameChars[18] == 'n' &&
+                    classNameChars[19] == 'g' &&
+                    classNameChars[20] == '.') {
+                    startPosition = 21;
+                    markerName += BILLING_DOT;
+
+                    // Extract the submodule in util
+                    if (classNameChars.length > 26 &&
+                        classNameChars[21] == 'u' &&
+                        classNameChars[22] == 't' &&
+                        classNameChars[23] == 'i' &&
+                        classNameChars[24] == 'l' &&
+                        classNameChars[25] == '.') {
+                        startPosition = 26;
+
+                        // Skip JDBI wrappers
+                        if (classNameChars.length > 33 &&
+                            classNameChars[26] == 'e' &&
+                            classNameChars[27] == 'n' &&
+                            classNameChars[28] == 't' &&
+                            classNameChars[29] == 'i' &&
+                            classNameChars[30] == 't' &&
+                            classNameChars[31] == 'y' &&
+                            classNameChars[32] == '.') {
+                            continue;
+                        } else if (classNameChars.length > 30 &&
+                                   classNameChars[26] == 'd' &&
+                                   classNameChars[27] == 'a' &&
+                                   classNameChars[28] == 'o' &&
+                                   classNameChars[29] == '.') {
+                            continue;
+                        }
+                    }
+                    // Extract the submodule in commons
+                } else if (classNameChars.length > 21 &&
+                           classNameChars[13] == 'c' &&
+                           classNameChars[14] == 'o' &&
+                           classNameChars[15] == 'm' &&
+                           classNameChars[16] == 'm' &&
+                           classNameChars[17] == 'o' &&
+                           classNameChars[18] == 'n' &&
+                           classNameChars[19] == 's' &&
+                           classNameChars[20] == '.') {
+                    startPosition = 21;
+                    markerName += COMMONS_DOT;
+
+                    // Skip profiling
+                    if (classNameChars.length > 31 &&
+                        classNameChars[21] == 'p' &&
+                        classNameChars[22] == 'r' &&
+                        classNameChars[23] == 'o' &&
+                        classNameChars[24] == 'f' &&
+                        classNameChars[25] == 'i' &&
+                        classNameChars[26] == 'l' &&
+                        classNameChars[27] == 'i' &&
+                        classNameChars[28] == 'n' &&
+                        classNameChars[29] == 'g' &&
+                        classNameChars[30] == '.') {
+                        continue;
+                        // Skip JDBI utilities
+                    } else if (classNameChars.length > 26 &&
+                               classNameChars[21] == 'j' &&
+                               classNameChars[22] == 'd' &&
+                               classNameChars[23] == 'b' &&
+                               classNameChars[24] == 'i' &&
+                               classNameChars[25] == '.') {
+                        continue;
+                    }
+                }
+
+                for (int j = startPosition; j < classNameChars.length; j++) {
+                    final char kar = classNameChars[j];
+                    if (kar == '.') {
+                        break;
+                    }
+                    markerName += kar;
+                }
+                return markerName;
+            }
+        }
+
+        return null;
+    }
+
+    // Simple utility class used to provide public access to the protected getClassContext() method of SecurityManager.
+    // Faster than new Throwable().getStackTrace() and Thread.currentThread().getStackTrace()
+    private static final class KillbillSecurityManager extends SecurityManager {
+
+        public Class[] getClassContext() {
+            return super.getClassContext();
+        }
+    }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillEmbeddedDBProvider.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillEmbeddedDBProvider.java
new file mode 100644
index 0000000..c7ebe7e
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillEmbeddedDBProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.modules;
+
+import java.util.Collection;
+import java.util.LinkedList;
+
+import org.killbill.commons.jdbi.guice.DaoConfig;
+
+public class KillBillEmbeddedDBProvider extends EmbeddedDBProvider {
+
+    public KillBillEmbeddedDBProvider(final DaoConfig daoConfig) {super(daoConfig);}
+
+    @Override
+    protected Iterable<String> getDDLFiles() {
+        final Collection<String> ddlFiles = new LinkedList<String>();
+        for (final String module : new String[]{"account",
+                                                "beatrix",
+                                                "entitlement",
+                                                "invoice",
+                                                "payment",
+                                                "subscription",
+                                                "tenant",
+                                                "usage",
+                                                "util",
+                                                "server"}) {
+            ddlFiles.add("org/killbill/billing/" + module + "/ddl.sql");
+        }
+        return ddlFiles;
+    }
+}
diff --git a/profiles/killbill/src/main/resources/logback.xml b/profiles/killbill/src/main/resources/logback.xml
new file mode 100644
index 0000000..b146654
--- /dev/null
+++ b/profiles/killbill/src/main/resources/logback.xml
@@ -0,0 +1,189 @@
+<!--
+  ~ Copyright 2010-2013 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.
+  -->
+
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <!-- encoders are assigned the type
+             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
+        <encoder>
+            <pattern>%date [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <!-- JDBC appenders -->
+    <appender name="SIFT-jdbc-sqlonly" class="ch.qos.logback.classic.sift.SiftingAppender">
+        <discriminator class="org.killbill.billing.server.log.ThreadNameBasedDiscriminator"/>
+        <sift>
+            <appender name="jdbc-sqlonly-${threadName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
+                <file>${LOGS_DIR:-./logs}/jdbc-sqlonly.${threadName}.out</file>
+                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+                    <!-- rollover daily -->
+                    <fileNamePattern>${LOGS_DIR:-./logs}/jdbc-sqlonly-%d{yyyy-MM-dd}.%i.${threadName}.out.gz</fileNamePattern>
+                    <maxHistory>3</maxHistory>
+                    <cleanHistoryOnStart>true</cleanHistoryOnStart>
+                    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                        <!-- or whenever the file size reaches 100MB -->
+                        <maxFileSize>100MB</maxFileSize>
+                    </timeBasedFileNamingAndTriggeringPolicy>
+                </rollingPolicy>
+                <encoder>
+                    <pattern>%date [%thread] %msg%n</pattern>
+                </encoder>
+            </appender>
+        </sift>
+    </appender>
+    <appender name="SIFT-jdbc-sqltiming" class="ch.qos.logback.classic.sift.SiftingAppender">
+        <discriminator class="org.killbill.billing.server.log.ThreadNameBasedDiscriminator"/>
+        <sift>
+            <appender name="jdbc-sqltiming-${threadName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
+                <file>${LOGS_DIR:-./logs}/jdbc-sqltiming.${threadName}.out</file>
+                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+                    <!-- rollover daily -->
+                    <fileNamePattern>${LOGS_DIR:-./logs}/jdbc-sqltiming-%d{yyyy-MM-dd}.%i.${threadName}.out.gz</fileNamePattern>
+                    <maxHistory>3</maxHistory>
+                    <cleanHistoryOnStart>true</cleanHistoryOnStart>
+                    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                        <!-- or whenever the file size reaches 100MB -->
+                        <maxFileSize>100MB</maxFileSize>
+                    </timeBasedFileNamingAndTriggeringPolicy>
+                </rollingPolicy>
+                <encoder>
+                    <pattern>%date [%thread] %msg%n</pattern>
+                </encoder>
+            </appender>
+        </sift>
+    </appender>
+    <appender name="SIFT-jdbc-audit" class="ch.qos.logback.classic.sift.SiftingAppender">
+        <discriminator class="org.killbill.billing.server.log.ThreadNameBasedDiscriminator"/>
+        <sift>
+            <appender name="jdbc-audit-${threadName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
+                <file>${LOGS_DIR:-./logs}/jdbc-audit.${threadName}.out</file>
+                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+                    <!-- rollover daily -->
+                    <fileNamePattern>${LOGS_DIR:-./logs}/jdbc-audit-%d{yyyy-MM-dd}.%i.${threadName}.out.gz</fileNamePattern>
+                    <maxHistory>3</maxHistory>
+                    <cleanHistoryOnStart>true</cleanHistoryOnStart>
+                    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                        <!-- or whenever the file size reaches 100MB -->
+                        <maxFileSize>100MB</maxFileSize>
+                    </timeBasedFileNamingAndTriggeringPolicy>
+                </rollingPolicy>
+                <encoder>
+                    <pattern>%date [%thread] %msg%n</pattern>
+                </encoder>
+            </appender>
+        </sift>
+    </appender>
+    <appender name="SIFT-jdbc-resultset" class="ch.qos.logback.classic.sift.SiftingAppender">
+        <discriminator class="org.killbill.billing.server.log.ThreadNameBasedDiscriminator"/>
+        <sift>
+            <appender name="jdbc-resultset-${threadName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
+                <file>${LOGS_DIR:-./logs}/jdbc-resultset.${threadName}.out</file>
+                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+                    <!-- rollover daily -->
+                    <fileNamePattern>${LOGS_DIR:-./logs}/jdbc-resultset-%d{yyyy-MM-dd}.%i.${threadName}.out.gz</fileNamePattern>
+                    <maxHistory>3</maxHistory>
+                    <cleanHistoryOnStart>true</cleanHistoryOnStart>
+                    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                        <!-- or whenever the file size reaches 100MB -->
+                        <maxFileSize>100MB</maxFileSize>
+                    </timeBasedFileNamingAndTriggeringPolicy>
+                </rollingPolicy>
+                <encoder>
+                    <pattern>%date [%thread] %msg%n</pattern>
+                </encoder>
+            </appender>
+        </sift>
+    </appender>
+    <appender name="SIFT-jdbc-resultsettable" class="ch.qos.logback.classic.sift.SiftingAppender">
+        <discriminator class="org.killbill.billing.server.log.ThreadNameBasedDiscriminator"/>
+        <sift>
+            <appender name="jdbc-resultsettable-${threadName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
+                <file>${LOGS_DIR:-./logs}/jdbc-resultsettable.${threadName}.out</file>
+                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+                    <!-- rollover daily -->
+                    <fileNamePattern>${LOGS_DIR:-./logs}/jdbc-resultsettable-%d{yyyy-MM-dd}.%i.${threadName}.out.gz</fileNamePattern>
+                    <maxHistory>3</maxHistory>
+                    <cleanHistoryOnStart>true</cleanHistoryOnStart>
+                    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                        <!-- or whenever the file size reaches 100MB -->
+                        <maxFileSize>100MB</maxFileSize>
+                    </timeBasedFileNamingAndTriggeringPolicy>
+                </rollingPolicy>
+                <encoder>
+                    <pattern>%date [%thread] %msg%n</pattern>
+                </encoder>
+            </appender>
+        </sift>
+    </appender>
+    <appender name="SIFT-jdbc-connection" class="ch.qos.logback.classic.sift.SiftingAppender">
+        <discriminator class="org.killbill.billing.server.log.ThreadNameBasedDiscriminator"/>
+        <sift>
+            <appender name="jdbc-connection-${threadName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
+                <file>${LOGS_DIR:-./logs}/jdbc-connection.${threadName}.out</file>
+                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+                    <!-- rollover daily -->
+                    <fileNamePattern>${LOGS_DIR:-./logs}/jdbc-connection-%d{yyyy-MM-dd}.%i.${threadName}.out.gz</fileNamePattern>
+                    <maxHistory>3</maxHistory>
+                    <cleanHistoryOnStart>true</cleanHistoryOnStart>
+                    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                        <!-- or whenever the file size reaches 100MB -->
+                        <maxFileSize>100MB</maxFileSize>
+                    </timeBasedFileNamingAndTriggeringPolicy>
+                </rollingPolicy>
+                <encoder>
+                    <pattern>%date [%thread] %msg%n</pattern>
+                </encoder>
+            </appender>
+        </sift>
+    </appender>
+
+    <!-- Logs only SQL. SQL executed within a prepared statement is automatically shown with it's bind arguments replaced with the data bound at that position, for greatly increased readability. -->
+    <logger name="jdbc.sqlonly" level="ERROR" additivity="false">
+        <appender-ref ref="SIFT-jdbc-sqlonly"/>
+    </logger>
+    <!-- Logs the SQL, post-execution, including timing statistics on how long the SQL took to execute. -->
+    <logger name="jdbc.sqltiming" level="INFO" additivity="false">
+        <appender-ref ref="SIFT-jdbc-sqltiming"/>
+    </logger>
+    <!-- Logs ALL JDBC calls except for ResultSets. This is a very voluminous output, and is not normally needed unless tracking down a specific JDBC problem. -->
+    <logger name="jdbc.audit" level="OFF" additivity="false">
+        <appender-ref ref="SIFT-jdbc-audit"/>
+    </logger>
+    <!-- Even more voluminous, because all calls to ResultSet objects are logged. -->
+    <logger name="jdbc.resultset" level="OFF" additivity="false">
+        <appender-ref ref="SIFT-jdbc-resultset"/>
+    </logger>
+    <!-- Log the jdbc results as a table. Level debug will fill in unread values in the result set. -->
+    <logger name="jdbc.resultsettable" level="OFF" additivity="false">
+        <appender-ref ref="SIFT-jdbc-resultsettable"/>
+    </logger>
+    <!-- Logs connection open and close events as well as dumping all open connection numbers. This is very useful for hunting down connection leak problems. -->
+    <logger name="jdbc.connection" level="OFF" additivity="false">
+        <appender-ref ref="SIFT-jdbc-connection"/>
+    </logger>
+
+    <!-- Silence verbose loggers in DEBUG mode -->
+    <logger name="com.dmurph" level="OFF"/>
+    <logger name="org.killbill.billing.notificationq" level="INFO"/>
+    <logger name="org.killbill.billing.queue" level="INFO"/>
+    <logger name="org.killbill.billing.server.updatechecker" level="INFO"/>
+    <logger name="org.eclipse" level="INFO"/>
+
+    <root level="INFO">
+        <appender-ref ref="STDOUT"/>
+    </root>
+</configuration>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
new file mode 100644
index 0000000..1c11ac8
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestPayment.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.InvoicePayments;
+import org.killbill.billing.client.model.Payment;
+import org.killbill.billing.client.model.PaymentMethod;
+import org.killbill.billing.client.model.PaymentMethodPluginDetail;
+import org.killbill.billing.client.model.Payments;
+import org.killbill.billing.client.model.PaymentTransaction;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Objects;
+
+public class TestPayment extends TestJaxrsBase {
+
+    @Test(groups = "slow")
+    public void testCreateRetrievePayment() throws Exception {
+        final Account account = createAccountWithDefaultPaymentMethod();
+        testCreateRetrievePayment(account, null, UUID.randomUUID().toString(), 1);
+
+        final PaymentMethod paymentMethodJson = new PaymentMethod(null, UUID.randomUUID().toString(), account.getAccountId(), false, PLUGIN_NAME, new PaymentMethodPluginDetail());
+        final PaymentMethod nonDefaultPaymentMethod = killBillClient.createPaymentMethod(paymentMethodJson, createdBy, reason, comment);
+        testCreateRetrievePayment(account, nonDefaultPaymentMethod.getPaymentMethodId(), UUID.randomUUID().toString(), 2);
+    }
+
+    public void testCreateRetrievePayment(final Account account, @Nullable final UUID paymentMethodId,
+                                          final String PaymentExternalKey, final int PaymentNb) throws Exception {
+        // Authorization
+        final String authTransactionExternalKey = UUID.randomUUID().toString();
+        final PaymentTransaction authTransaction = new PaymentTransaction();
+        authTransaction.setAmount(BigDecimal.TEN);
+        authTransaction.setCurrency(account.getCurrency());
+        authTransaction.setPaymentExternalKey(PaymentExternalKey);
+        authTransaction.setTransactionExternalKey(authTransactionExternalKey);
+        authTransaction.setTransactionType("AUTHORIZE");
+        final Payment authPayment = killBillClient.createPayment(account.getAccountId(), paymentMethodId, authTransaction, createdBy, reason, comment);
+        verifyPayment(account, paymentMethodId, authPayment, PaymentExternalKey, authTransactionExternalKey,
+                            BigDecimal.TEN, BigDecimal.ZERO, BigDecimal.ZERO, 1, PaymentNb);
+
+        // Capture 1
+        final String capture1TransactionExternalKey = UUID.randomUUID().toString();
+        final PaymentTransaction captureTransaction = new PaymentTransaction();
+        captureTransaction.setPaymentId(authPayment.getPaymentId());
+        captureTransaction.setAmount(BigDecimal.ONE);
+        captureTransaction.setCurrency(account.getCurrency());
+        captureTransaction.setPaymentExternalKey(PaymentExternalKey);
+        captureTransaction.setTransactionExternalKey(capture1TransactionExternalKey);
+        final Payment capturedPayment1 = killBillClient.captureAuthorization(captureTransaction, createdBy, reason, comment);
+        verifyPayment(account, paymentMethodId, capturedPayment1, PaymentExternalKey, authTransactionExternalKey,
+                            BigDecimal.TEN, BigDecimal.ONE, BigDecimal.ZERO, 2, PaymentNb);
+        verifyPaymentTransaction(authPayment.getPaymentId(), capturedPayment1.getTransactions().get(1),
+                                       PaymentExternalKey, capture1TransactionExternalKey,
+                                       account, captureTransaction.getAmount(), "CAPTURE");
+
+        // Capture 2
+        final String capture2TransactionExternalKey = UUID.randomUUID().toString();
+        captureTransaction.setTransactionExternalKey(capture2TransactionExternalKey);
+        final Payment capturedPayment2 = killBillClient.captureAuthorization(captureTransaction, createdBy, reason, comment);
+        verifyPayment(account, paymentMethodId, capturedPayment2, PaymentExternalKey, authTransactionExternalKey,
+                            BigDecimal.TEN, new BigDecimal("2"), BigDecimal.ZERO, 3, PaymentNb);
+        verifyPaymentTransaction(authPayment.getPaymentId(), capturedPayment2.getTransactions().get(2),
+                                       PaymentExternalKey, capture2TransactionExternalKey,
+                                       account, captureTransaction.getAmount(), "CAPTURE");
+
+        // Refund
+        final String refundTransactionExternalKey = UUID.randomUUID().toString();
+        final PaymentTransaction refundTransaction = new PaymentTransaction();
+        refundTransaction.setPaymentId(authPayment.getPaymentId());
+        refundTransaction.setAmount(new BigDecimal("2"));
+        refundTransaction.setCurrency(account.getCurrency());
+        refundTransaction.setPaymentExternalKey(PaymentExternalKey);
+        refundTransaction.setTransactionExternalKey(refundTransactionExternalKey);
+        final Payment refundPayment = killBillClient.refundPayment(refundTransaction, createdBy, reason, comment);
+        verifyPayment(account, paymentMethodId, refundPayment, PaymentExternalKey, authTransactionExternalKey,
+                            BigDecimal.TEN, new BigDecimal("2"), new BigDecimal("2"), 4, PaymentNb);
+        verifyPaymentTransaction(authPayment.getPaymentId(), refundPayment.getTransactions().get(3),
+                                       PaymentExternalKey, refundTransactionExternalKey,
+                                       account, refundTransaction.getAmount(), "REFUND");
+    }
+
+    private void verifyPayment(final Account account, @Nullable final UUID paymentMethodId, final Payment Payment,
+                                     final String PaymentExternalKey, final String authTransactionExternalKey,
+                                     final BigDecimal authAmount, final BigDecimal capturedAmount,
+                                     final BigDecimal refundedAmount, final int nbTransactions, final int PaymentNb) throws KillBillClientException {
+        Assert.assertEquals(Payment.getAccountId(), account.getAccountId());
+        Assert.assertEquals(Payment.getPaymentMethodId(), Objects.firstNonNull(paymentMethodId, account.getPaymentMethodId()));
+        Assert.assertNotNull(Payment.getPaymentId());
+        Assert.assertNotNull(Payment.getPaymentNumber());
+        Assert.assertEquals(Payment.getPaymentExternalKey(), PaymentExternalKey);
+        Assert.assertEquals(Payment.getAuthAmount().compareTo(authAmount), 0);
+        Assert.assertEquals(Payment.getCapturedAmount().compareTo(capturedAmount), 0);
+        Assert.assertEquals(Payment.getRefundedAmount().compareTo(refundedAmount), 0);
+        Assert.assertEquals(Payment.getCurrency(), account.getCurrency());
+        Assert.assertEquals(Payment.getTransactions().size(), nbTransactions);
+
+        verifyPaymentTransaction(Payment.getPaymentId(), Payment.getTransactions().get(0),
+                                       PaymentExternalKey, authTransactionExternalKey, account, authAmount, "AUTHORIZE");
+
+        final Payments Payments = killBillClient.getPayments();
+        Assert.assertEquals(Payments.size(), PaymentNb);
+        Assert.assertEquals(Payments.get(PaymentNb - 1), Payment);
+
+        final Payment retrievedPayment = killBillClient.getPayment(Payment.getPaymentId());
+        Assert.assertEquals(retrievedPayment, Payment);
+
+        final Payments paymentsForAccount = killBillClient.getPaymentsForAccount(account.getAccountId());
+        Assert.assertEquals(paymentsForAccount.size(), PaymentNb);
+        Assert.assertEquals(paymentsForAccount.get(PaymentNb - 1), Payment);
+    }
+
+    private void verifyPaymentTransaction(final UUID PaymentId, final PaymentTransaction PaymentTransaction,
+                                                final String PaymentExternalKey, final String TransactionExternalKey,
+                                                final Account account, @Nullable final BigDecimal amount, final String transactionType) {
+        Assert.assertEquals(PaymentTransaction.getPaymentId(), PaymentId);
+        Assert.assertNotNull(PaymentTransaction.getTransactionId());
+        Assert.assertEquals(PaymentTransaction.getTransactionType(), transactionType);
+        Assert.assertEquals(PaymentTransaction.getStatus(), "SUCCESS");
+        if (amount == null) {
+            Assert.assertNull(PaymentTransaction.getAmount());
+            Assert.assertNull(PaymentTransaction.getCurrency());
+        } else {
+            Assert.assertEquals(PaymentTransaction.getAmount().compareTo(amount), 0);
+            Assert.assertEquals(PaymentTransaction.getCurrency(), account.getCurrency());
+        }
+        Assert.assertEquals(PaymentTransaction.getTransactionExternalKey(), TransactionExternalKey);
+        Assert.assertEquals(PaymentTransaction.getPaymentExternalKey(), PaymentExternalKey);
+    }
+}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestKillbillJdbcRealm.java b/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestKillbillJdbcRealm.java
new file mode 100644
index 0000000..fcd8711
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestKillbillJdbcRealm.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.security;
+
+import java.util.UUID;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.subject.support.DelegatingSubject;
+import org.killbill.billing.jaxrs.TestJaxrsBase;
+import org.killbill.billing.tenant.api.DefaultTenant;
+import org.killbill.billing.tenant.dao.DefaultTenantDao;
+import org.killbill.billing.tenant.dao.TenantModelDao;
+import org.killbill.billing.util.dao.DefaultNonEntityDao;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.jolbox.bonecp.BoneCPConfig;
+import com.jolbox.bonecp.BoneCPDataSource;
+
+public class TestKillbillJdbcRealm extends TestJaxrsBase {
+
+    private SecurityManager securityManager;
+    private DefaultTenant tenant;
+
+    @Override
+    @BeforeMethod(groups = "slow")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+
+        // Create the tenant
+        final DefaultTenantDao tenantDao = new DefaultTenantDao(dbi, clock, cacheControllerDispatcher, new DefaultNonEntityDao(dbi));
+        tenant = new DefaultTenant(UUID.randomUUID(), null, null, UUID.randomUUID().toString(),
+                                   UUID.randomUUID().toString(), UUID.randomUUID().toString());
+        tenantDao.create(new TenantModelDao(tenant), internalCallContext);
+
+        // Setup the security manager
+        final BoneCPConfig dbConfig = new BoneCPConfig();
+        dbConfig.setJdbcUrl(helper.getJdbcConnectionString());
+        dbConfig.setUsername(helper.getUsername());
+        dbConfig.setPassword(helper.getPassword());
+
+        final KillbillJdbcRealm jdbcRealm;
+        jdbcRealm = new KillbillJdbcRealm(daoConfig);
+        jdbcRealm.setDataSource(new BoneCPDataSource(dbConfig));
+
+        securityManager = new DefaultSecurityManager(jdbcRealm);
+    }
+
+    @Test(groups = "slow")
+    public void testAuthentication() throws Exception {
+        final DelegatingSubject subject = new DelegatingSubject(securityManager);
+
+        // Good combo
+        final AuthenticationToken goodToken = new UsernamePasswordToken(tenant.getApiKey(), tenant.getApiSecret());
+        try {
+            securityManager.login(subject, goodToken);
+            Assert.assertTrue(true);
+        } catch (final AuthenticationException e) {
+            Assert.fail();
+        }
+
+        // Bad login
+        final AuthenticationToken badPasswordToken = new UsernamePasswordToken(tenant.getApiKey(), tenant.getApiSecret() + "T");
+        try {
+            securityManager.login(subject, badPasswordToken);
+            Assert.fail();
+        } catch (final AuthenticationException e) {
+            Assert.assertTrue(true);
+        }
+
+        // Bad password
+        final AuthenticationToken badLoginToken = new UsernamePasswordToken(tenant.getApiKey() + "U", tenant.getApiSecret());
+        try {
+            securityManager.login(subject, badLoginToken);
+            Assert.fail();
+        } catch (final AuthenticationException e) {
+            Assert.assertTrue(true);
+        }
+    }
+}
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestTenantFilter.java b/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestTenantFilter.java
new file mode 100644
index 0000000..fdc95ce
--- /dev/null
+++ b/profiles/killbill/src/test/java/org/killbill/billing/server/security/TestTenantFilter.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.security;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.model.Account;
+import org.killbill.billing.client.model.Tenant;
+import org.killbill.billing.jaxrs.TestJaxrsBase;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+public class TestTenantFilter extends TestJaxrsBase {
+
+    @AfterMethod(groups = "slow")
+    public void tearDown() throws Exception {
+        // Default credentials
+        loginTenant(DEFAULT_API_KEY, DEFAULT_API_SECRET);
+    }
+
+    @Test(groups = "slow")
+    public void testTenantShouldOnlySeeOwnAccount() throws Exception {
+        // Try to create an account without being logged-in
+        logoutTenant();
+        try {
+            killBillClient.createAccount(getAccount(), createdBy, reason, comment);
+            Assert.fail();
+        } catch (final KillBillClientException e) {
+            Assert.assertEquals(e.getResponse().getStatusCode(), Status.UNAUTHORIZED.getStatusCode());
+        }
+
+        // Create the tenant
+        final String apiKeyTenant1 = "pierre";
+        final String apiSecretTenant1 = "pierreIsFr3nch";
+        loginTenant(apiKeyTenant1, apiSecretTenant1);
+        final Tenant tenant1 = new Tenant();
+        tenant1.setApiKey(apiKeyTenant1);
+        tenant1.setApiSecret(apiSecretTenant1);
+        killBillClient.createTenant(tenant1, createdBy, reason, comment);
+
+        final Account account1 = createAccount();
+        Assert.assertEquals(killBillClient.getAccount(account1.getExternalKey()), account1);
+
+        logoutTenant();
+
+        // Create another tenant
+        final String apiKeyTenant2 = "stephane";
+        final String apiSecretTenant2 = "stephane1sAlsoFr3nch";
+        loginTenant(apiKeyTenant2, apiSecretTenant2);
+        final Tenant tenant2 = new Tenant();
+        tenant2.setApiKey(apiKeyTenant2);
+        tenant2.setApiSecret(apiSecretTenant2);
+        killBillClient.createTenant(tenant2, createdBy, reason, comment);
+
+        final Account account2 = createAccount();
+        Assert.assertEquals(killBillClient.getAccount(account2.getExternalKey()), account2);
+
+        // We should not be able to retrieve the first account as tenant2
+        Assert.assertNull(killBillClient.getAccount(account1.getExternalKey()));
+
+        // Same for tenant1 and account2
+        loginTenant(apiKeyTenant1, apiSecretTenant1);
+        Assert.assertNull(killBillClient.getAccount(account2.getExternalKey()));
+    }
+}
diff --git a/profiles/killbill/src/test/resources/overdue.xml b/profiles/killbill/src/test/resources/overdue.xml
new file mode 100644
index 0000000..8eb8014
--- /dev/null
+++ b/profiles/killbill/src/test/resources/overdue.xml
@@ -0,0 +1,61 @@
+<!--
+  ~ Copyright 2010-2013 Ning, Inc.
+  ~ Copyright 2014 Groupon, Inc
+  ~ Copyright 2014 The Billing Project, LLC
+  ~
+  ~ The Billing Project licenses this file to you under the Apache License, version 2.0
+  ~ (the "License"); you may not use this file except in compliance with the
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  ~ License for the specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<overdueConfig>
+   <accountOverdueStates>
+       <state name="OD3">
+           <condition>
+               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
+                   <unit>DAYS</unit><number>50</number>
+               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
+           </condition>
+           <externalMessage>Reached OD3</externalMessage>
+           <blockChanges>true</blockChanges>
+           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>
+           <autoReevaluationInterval>
+               <unit>DAYS</unit><number>5</number>
+           </autoReevaluationInterval>
+       </state>
+       <state name="OD2">
+           <condition>
+               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
+                   <unit>DAYS</unit><number>40</number>
+               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
+           </condition>
+           <externalMessage>Reached OD2</externalMessage>
+           <blockChanges>true</blockChanges>
+           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>
+           <autoReevaluationInterval>
+               <unit>DAYS</unit><number>5</number>
+           </autoReevaluationInterval>
+       </state>
+       <state name="OD1">
+           <condition>
+               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
+                   <unit>DAYS</unit><number>30</number>
+               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>
+           </condition>
+           <externalMessage>Reached OD1</externalMessage>
+           <blockChanges>true</blockChanges>
+           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>
+           <autoReevaluationInterval>
+               <unit>DAYS</unit><number>5</number>
+           </autoReevaluationInterval>
+       </state>
+   </accountOverdueStates>
+</overdueConfig>
diff --git a/profiles/killbill/src/test/resources/shiro.ini b/profiles/killbill/src/test/resources/shiro.ini
new file mode 100644
index 0000000..e37f796
--- /dev/null
+++ b/profiles/killbill/src/test/resources/shiro.ini
@@ -0,0 +1,32 @@
+###################################################################################
+#                                                                                 #
+#                   Copyright 2010-2013 Ning, Inc.                                #
+#                   Copyright 2014 Groupon, Inc                                   #
+#                   Copyright 2014 The Billing Project, LLC                       #
+#                                                                                 #
+#      The Billing Project licenses this file to you under the Apache License,    #
+#      version 2.0 (the "License"); you may not use this file except in           #
+#      compliance with the License.  You may obtain a copy of the License at:     #
+#                                                                                 #
+#          http://www.apache.org/licenses/LICENSE-2.0                             #
+#                                                                                 #
+#      Unless required by applicable law or agreed to in writing, software        #
+#      distributed under the License is distributed on an "AS IS" BASIS, WITHOUT  #
+#      WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the  #
+#      License for the specific language governing permissions and limitations    #
+#      under the License.                                                         #
+#                                                                                 #
+###################################################################################
+
+[users]
+tester = tester, admin
+pierre = password, creditor
+stephane = password, refunder
+
+[roles]
+admin = *:*
+creditor = invoice:credit, invoice:item_adjust
+refunder = payment:refund
+
+[urls]
+/1.0/kb/** = authcBasic
diff --git a/profiles/killpay/pom.xml b/profiles/killpay/pom.xml
new file mode 100644
index 0000000..913b7ab
--- /dev/null
+++ b/profiles/killpay/pom.xml
@@ -0,0 +1,264 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2014 Groupon, Inc
+  ~ Copyright 2014 The Billing Project, LLC
+  ~
+  ~ The Billing Project licenses this file to you under the Apache License, version 2.0
+  ~ (the "License"); you may not use this file except in compliance with the
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  ~ License for the specific language governing permissions and limitations
+  ~ under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>killbill-profiles</artifactId>
+        <groupId>org.kill-bill.billing</groupId>
+        <version>0.11.10-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>killbill-profiles-killpay</artifactId>
+    <packaging>war</packaging>
+    <name>killbill-profiles-killpay</name>
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <!--Needed by jmxutils-->
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-multibindings</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-servlet</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-account</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-beatrix</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-catalog</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-currency</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-entitlement</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-invoice</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-jaxrs</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-junction</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-overdue</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-payment</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-server</artifactId>
+            <classifier>classes</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-profiles-killbill</artifactId>
+            <classifier>classes</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-subscription</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-tenant</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-usage</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-embeddeddb-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-embeddeddb-h2</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-embeddeddb-mysql</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.4</version>
+                <executions>
+                    <execution>
+                        <!-- We would like to be able to run the war, jar and jettyconsole plugins at the same time;
+                        and we could, except they rely on a strict ordering (jettyconsole has to run after the war and
+                        before the jar, or jettyconsole won't find the war artifact). This could be done by relying
+                        on the declaration ordering of the various plugins (all have to be bound to the package phase),
+                        but that's fragile. Instead, ignore altogether the jar for now, until maven is smarter. -->
+                        <phase>pierre-s-hack-for-maven</phase>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>2.1</version>
+                <executions>
+                    <execution>
+                        <id>assemble-killpay</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <createSourcesJar>true</createSourcesJar>
+                            <shadedArtifactAttached>true</shadedArtifactAttached>
+                            <shadedClassifierName>jar-with-dependencies</shadedClassifierName>
+                            <filters>
+                                <filter>
+                                    <artifact>*</artifact>
+                                    <excludes>
+                                        <exclude>META-INF/LICENSE</exclude>
+                                        <!-- conflicts on OS X with license/ -->
+                                    </excludes>
+                                </filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>jetty-maven-plugin</artifactId>
+                <version>${jetty.version}</version>
+                <dependencies>
+                    <dependency>
+                        <groupId>ch.qos.logback</groupId>
+                        <artifactId>logback-classic</artifactId>
+                        <version>${logback.version}</version>
+                        <scope>runtime</scope>
+                    </dependency>
+                    <dependency>
+                        <groupId>ch.qos.logback</groupId>
+                        <artifactId>logback-core</artifactId>
+                        <version>${logback.version}</version>
+                        <scope>runtime</scope>
+                    </dependency>
+                    <dependency>
+                        <groupId>org.eclipse.jetty</groupId>
+                        <artifactId>jetty-deploy</artifactId>
+                        <version>${jetty.version}</version>
+                    </dependency>
+                    <dependency>
+                        <groupId>org.eclipse.jetty</groupId>
+                        <artifactId>jetty-jmx</artifactId>
+                        <version>${jetty.version}</version>
+                    </dependency>
+                    <!-- Needed to redirect Jetty logs to slf4j -->
+                    <dependency>
+                        <groupId>org.slf4j</groupId>
+                        <artifactId>slf4j-api</artifactId>
+                        <version>${slf4j.version}</version>
+                    </dependency>
+                </dependencies>
+                <configuration>
+                    <jettyXml>${basedir}/src/main/jetty-config/jetty-conf.xml</jettyXml>
+                    <contextXml>${basedir}/src/main/jetty-config/contexts/root.xml</contextXml>
+                    <systemProperties>
+                        <systemProperty>
+                            <!-- See root.xml -->
+                            <name>xn.jetty.webapps.defaultsDescriptor</name>
+                            <value>${basedir}/src/main/jetty-config/etc/webdefault.xml</value>
+                        </systemProperty>
+                        <systemProperty>
+                            <name>logback.configurationFile</name>
+                            <!-- Use the killbill one on the classpth -->
+                            <value>logback.xml</value>
+                        </systemProperty>
+                    </systemProperties>
+                    <scanIntervalSeconds>0</scanIntervalSeconds>
+                    <stopPort>9966</stopPort>
+                    <stopKey>foo</stopKey>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.simplericity.jettyconsole</groupId>
+                <artifactId>jetty-console-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>createconsole</goal>
+                        </goals>
+                        <configuration>
+                            <backgroundImage>${basedir}/src/main/jettyconsole/killbill.png</backgroundImage>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/profiles/killpay/src/main/java/org/killbill/billing/server/listeners/KillpayGuiceListener.java b/profiles/killpay/src/main/java/org/killbill/billing/server/listeners/KillpayGuiceListener.java
new file mode 100644
index 0000000..81a0ef6
--- /dev/null
+++ b/profiles/killpay/src/main/java/org/killbill/billing/server/listeners/KillpayGuiceListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.listeners;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import javax.servlet.ServletContext;
+
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.platform.config.DefaultKillbillConfigSource;
+import org.killbill.billing.server.modules.KillpayServerModule;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Module;
+
+public class KillpayGuiceListener extends KillbillGuiceListener {
+
+    @Override
+    protected Module getModule(final ServletContext servletContext) {
+        return new KillpayServerModule(servletContext, config, configSource);
+    }
+
+    @Override
+    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
+        final ImmutableMap<String, String> defaultProperties = ImmutableMap.<String, String>of("org.killbill.server.updateCheck.url",
+                                                                                               "https://raw.github.com/killbill/killbill/master/profiles/killpay/src/main/resources/update-checker/killbill-server-update-list.properties");
+        return new DefaultKillbillConfigSource(defaultProperties);
+    }
+}
diff --git a/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java b/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java
new file mode 100644
index 0000000..2f42574
--- /dev/null
+++ b/profiles/killpay/src/main/java/org/killbill/billing/server/modules/KillpayServerModule.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.server.modules;
+
+import javax.servlet.ServletContext;
+
+import org.killbill.billing.account.glue.DefaultAccountModule;
+import org.killbill.billing.beatrix.glue.BeatrixModule;
+import org.killbill.billing.catalog.glue.CatalogModule;
+import org.killbill.billing.currency.glue.CurrencyModule;
+import org.killbill.billing.entitlement.glue.DefaultEntitlementModule;
+import org.killbill.billing.invoice.glue.DefaultInvoiceModule;
+import org.killbill.billing.jaxrs.resources.AccountResource;
+import org.killbill.billing.jaxrs.resources.CustomFieldResource;
+import org.killbill.billing.jaxrs.resources.ExportResource;
+import org.killbill.billing.jaxrs.resources.InvoicePaymentResource;
+import org.killbill.billing.jaxrs.resources.PaymentMethodResource;
+import org.killbill.billing.jaxrs.resources.PaymentResource;
+import org.killbill.billing.jaxrs.resources.PluginResource;
+import org.killbill.billing.jaxrs.resources.TagDefinitionResource;
+import org.killbill.billing.jaxrs.resources.TagResource;
+import org.killbill.billing.jaxrs.resources.TenantResource;
+import org.killbill.billing.jaxrs.resources.TransactionResource;
+import org.killbill.billing.jaxrs.util.KillbillEventHandler;
+import org.killbill.billing.junction.glue.DefaultJunctionModule;
+import org.killbill.billing.overdue.glue.DefaultOverdueModule;
+import org.killbill.billing.payment.glue.PaymentModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.server.config.KillbillServerConfig;
+import org.killbill.billing.subscription.glue.DefaultSubscriptionModule;
+import org.killbill.billing.tenant.glue.TenantModule;
+import org.killbill.billing.usage.glue.UsageModule;
+import org.killbill.billing.util.email.EmailModule;
+import org.killbill.billing.util.email.templates.TemplateModule;
+import org.killbill.billing.util.glue.AuditModule;
+import org.killbill.billing.util.glue.CacheModule;
+import org.killbill.billing.util.glue.CallContextModule;
+import org.killbill.billing.util.glue.CustomFieldModule;
+import org.killbill.billing.util.glue.ExportModule;
+import org.killbill.billing.util.glue.GlobalLockerModule;
+import org.killbill.billing.util.glue.KillBillShiroAopModule;
+import org.killbill.billing.util.glue.KillbillApiAopModule;
+import org.killbill.billing.util.glue.NonEntityDaoModule;
+import org.killbill.billing.util.glue.RecordIdModule;
+import org.killbill.billing.util.glue.SecurityModule;
+import org.killbill.billing.util.glue.TagStoreModule;
+
+public class KillpayServerModule extends KillbillServerModule {
+
+    public KillpayServerModule(final ServletContext servletContext, final KillbillServerConfig serverConfig, final KillbillConfigSource configSource) {
+        super(servletContext, serverConfig, configSource);
+    }
+
+    @Override
+    protected void installKillbillModules() {
+        install(new AuditModule(configSource));
+        install(new BeatrixModule(configSource));
+        install(new CacheModule(configSource));
+        install(new CallContextModule(configSource));
+        install(new CurrencyModule(configSource));
+        install(new CustomFieldModule(configSource));
+        install(new DefaultAccountModule(configSource));
+        install(new ExportModule(configSource));
+        install(new GlobalLockerModule(embeddedDB.getDBEngine(), configSource));
+        install(new KillBillShiroAopModule());
+        install(new KillbillApiAopModule());
+        install(new KillBillShiroWebModule(servletContext, skifeConfigSource));
+        install(new NonEntityDaoModule(configSource));
+        install(new PaymentModule(configSource));
+        install(new RecordIdModule(configSource));
+        install(new SecurityModule(configSource));
+        install(new TagStoreModule(configSource));
+        install(new TenantModule(configSource));
+
+        // TODO Required by payment for InvoiceInternalApi and InvoicePaymentApi
+        install(new DefaultInvoiceModule(configSource));
+        // TODO Dependencies for DefaultInvoiceModule
+        install(new CatalogModule(configSource));
+        install(new DefaultEntitlementModule(configSource));
+        install(new DefaultJunctionModule(configSource));
+        install(new DefaultSubscriptionModule(configSource));
+        install(new TemplateModule(configSource));
+        install(new UsageModule(configSource));
+        // TODO Dependencies for AccountResource
+        install(new DefaultOverdueModule(configSource));
+        install(new EmailModule(configSource));
+    }
+
+    @Override
+    protected void configureResources() {
+        bind(AccountResource.class).asEagerSingleton();
+        bind(CustomFieldResource.class).asEagerSingleton();
+        bind(ExportResource.class).asEagerSingleton();
+        bind(KillbillEventHandler.class).asEagerSingleton();
+        bind(PaymentMethodResource.class).asEagerSingleton();
+        bind(InvoicePaymentResource.class).asEagerSingleton();
+        bind(PaymentResource.class).asEagerSingleton();
+        bind(TransactionResource.class).asEagerSingleton();
+        bind(PluginResource.class).asEagerSingleton();
+        bind(PluginResource.class).asEagerSingleton();
+        bind(TagDefinitionResource.class).asEagerSingleton();
+        bind(TagResource.class).asEagerSingleton();
+        bind(TenantResource.class).asEagerSingleton();
+    }
+}
diff --git a/profiles/killpay/src/main/jetty-config/contexts/root.xml b/profiles/killpay/src/main/jetty-config/contexts/root.xml
new file mode 100755
index 0000000..e1bdd37
--- /dev/null
+++ b/profiles/killpay/src/main/jetty-config/contexts/root.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"  encoding="UTF-8"?>
+<!--
+  ~ Copyright 2014 Groupon, Inc
+  ~ Copyright 2014 The Billing Project, LLC
+  ~
+  ~ The Billing Project licenses this file to you under the Apache License, version 2.0
+  ~ (the "License"); you may not use this file except in compliance with the
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  ~ License for the specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+    <Set name="contextPath">/</Set>
+    <Set name="war">
+        <SystemProperty name="xn.jetty.webapp.path" default="webapps/root"/>
+    </Set>
+    <Set name="defaultsDescriptor">
+        <SystemProperty name="xn.jetty.webapps.defaultsDescriptor" default="etc/webdefault.xml"/>
+    </Set>
+</Configure>
diff --git a/profiles/killpay/src/main/jetty-config/etc/webdefault.xml b/profiles/killpay/src/main/jetty-config/etc/webdefault.xml
new file mode 100755
index 0000000..7b20997
--- /dev/null
+++ b/profiles/killpay/src/main/jetty-config/etc/webdefault.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  ~ Copyright 2014 Groupon, Inc
+  ~ Copyright 2014 The Billing Project, LLC
+  ~
+  ~ The Billing Project licenses this file to you under the Apache License, version 2.0
+  ~ (the "License"); you may not use this file except in compliance with the
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  ~ License for the specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<web-app
+        xmlns="http://java.sun.com/xml/ns/javaee"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+        metadata-complete="true"
+        version="2.5">
+    <description>
+        Default web.xml file.
+        This file is applied to a Web application before it's own WEB_INF/web.xml file
+    </description>
+
+    <servlet>
+        <servlet-name>default</servlet-name>
+        <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
+        <init-param>
+            <param-name>aliases</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>acceptRanges</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <init-param>
+            <param-name>dirAllowed</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>welcomeServlets</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>redirectWelcome</param-name>
+            <param-value>false</param-value>
+        </init-param>
+        <init-param>
+            <param-name>maxCacheSize</param-name>
+            <param-value>256000000</param-value>
+        </init-param>
+        <init-param>
+            <param-name>maxCachedFileSize</param-name>
+            <param-value>200000000</param-value>
+        </init-param>
+        <init-param>
+            <param-name>maxCachedFiles</param-name>
+            <param-value>2048</param-value>
+        </init-param>
+        <init-param>
+            <param-name>gzip</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <init-param>
+            <param-name>etags</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <init-param>
+            <param-name>useFileMappedBuffer</param-name>
+            <param-value>true</param-value>
+        </init-param>
+        <load-on-startup>0</load-on-startup>
+    </servlet>
+
+    <context-param>
+        <param-name>org.mortbay.jetty.webapp.NoTLDJarPattern</param-name>
+        <param-value>
+            start.jar|ant-.*\.jar|dojo-.*\.jar|jetty-.*\.jar|jsp-api-.*\.jar|junit-.*\.jar|servlet-api-.*\.jar|dnsns\.jar|rt\.jar|jsse\.jar|tools\.jar|sunpkcs11\.jar|sunjce_provider\.jar|xerces.*\.jar
+        </param-value>
+    </context-param>
+    <locale-encoding-mapping-list>
+        <locale-encoding-mapping>
+            <locale>ar</locale>
+            <encoding>ISO-8859-6</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>be</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>bg</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ca</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>cs</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>da</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>de</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>el</locale>
+            <encoding>ISO-8859-7</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>en</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>es</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>et</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>fi</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>fr</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>hr</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>hu</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>is</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>it</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>iw</locale>
+            <encoding>ISO-8859-8</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ja</locale>
+            <encoding>Shift_JIS</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ko</locale>
+            <encoding>EUC-KR</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>lt</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>lv</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>mk</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>nl</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>no</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>pl</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>pt</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ro</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>ru</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sh</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sk</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sl</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sq</locale>
+            <encoding>ISO-8859-2</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sr</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>sv</locale>
+            <encoding>ISO-8859-1</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>tr</locale>
+            <encoding>ISO-8859-9</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>uk</locale>
+            <encoding>ISO-8859-5</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>zh</locale>
+            <encoding>GB2312</encoding>
+        </locale-encoding-mapping>
+        <locale-encoding-mapping>
+            <locale>zh_TW</locale>
+            <encoding>Big5</encoding>
+        </locale-encoding-mapping>
+    </locale-encoding-mapping-list>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Disable TRACE</web-resource-name>
+            <url-pattern>/</url-pattern>
+            <http-method>TRACE</http-method>
+        </web-resource-collection>
+        <auth-constraint/>
+    </security-constraint>
+</web-app>
+
diff --git a/profiles/killpay/src/main/jetty-config/jetty-conf.xml b/profiles/killpay/src/main/jetty-config/jetty-conf.xml
new file mode 100644
index 0000000..c98c7ce
--- /dev/null
+++ b/profiles/killpay/src/main/jetty-config/jetty-conf.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2014 Groupon, Inc
+  ~ Copyright 2014 The Billing Project, LLC
+  ~
+  ~ The Billing Project licenses this file to you under the Apache License, version 2.0
+  ~ (the "License"); you may not use this file except in compliance with the
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  ~ License for the specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <!-- =============================================================== -->
+    <!-- Setup MBean Server early                                        -->
+    <!-- =============================================================== -->
+    <Call id="MBeanServer" class="java.lang.management.ManagementFactory" name="getPlatformMBeanServer"/>
+
+    <New id="MBeanContainer" class="org.eclipse.jetty.jmx.MBeanContainer">
+        <Arg>
+            <Ref id="MBeanServer"/>
+        </Arg>
+    </New>
+
+    <Get id="Container" name="container">
+        <Call name="addEventListener">
+            <Arg>
+                <Ref id="MBeanContainer"/>
+            </Arg>
+        </Call>
+    </Get>
+
+    <!-- =========================================================== -->
+    <!-- Server Thread Pool                                          -->
+    <!-- =========================================================== -->
+    <Set name="ThreadPool">
+        <!-- Default queued blocking threadpool -->
+        <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+            <Set name="minThreads">
+                <SystemProperty name="xn.server.threads.min" default="10"/>
+            </Set>
+            <Set name="maxThreads">
+                <SystemProperty name="xn.server.threads.max" default="200"/>
+            </Set>
+        </New>
+    </Set>
+
+    <!-- =========================================================== -->
+    <!-- Set connectors                                              -->
+    <!-- =========================================================== -->
+
+    <!-- Use this connector if NIO is not available. -->
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.bio.SocketConnector">
+                <Set name="host">
+                    <SystemProperty name="xn.server.ip"/>
+                </Set>
+                <Set name="port">
+                    <SystemProperty name="xn.server.port" default="8080"/>
+                </Set>
+                <Set name="maxIdleTime">300000</Set>
+                <Set name="Acceptors">2</Set>
+                <Set name="statsOn">true</Set>
+                <Set name="confidentialPort">
+                    <SystemProperty name="xn.server.ssl.port" default="8443"/>
+                </Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <Set name="handler">
+        <New class="org.eclipse.jetty.server.handler.StatisticsHandler">
+            <Set name="handler">
+                <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+                    <Set name="handlers">
+                        <Array type="org.eclipse.jetty.server.Handler">
+                            <Item>
+                                <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+                            </Item>
+                            <Item>
+                                <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+                            </Item>
+                            <Item>
+                                <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>
+                            </Item>
+                        </Array>
+                    </Set>
+                </New>
+            </Set>
+        </New>
+    </Set>
+
+    <Ref id="RequestLog">
+        <Set name="requestLog">
+            <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+                <Arg>
+                    <SystemProperty name="jetty.logs" default="./logs"/>/yyyy_mm_dd.request.log
+                </Arg>
+                <Set name="retainDays">30</Set>
+                <Set name="append">true</Set>
+                <Set name="extended">false</Set>
+                <Set name="LogTimeZone">GMT</Set>
+            </New>
+        </Set>
+    </Ref>
+
+    <Call name="addLifeCycle">
+        <Arg>
+            <New class="org.eclipse.jetty.deploy.ContextDeployer">
+                <Set name="contexts">
+                    <Ref id="Contexts"/>
+                </Set>
+                <Set name="configurationDir">
+                    <SystemProperty name="xn.jetty.contextDir" default="contexts"/>
+                </Set>
+                <Set name="scanInterval">1</Set>
+            </New>
+        </Arg>
+    </Call>
+
+    <!-- =========================================================== -->
+    <!-- extra options                                               -->
+    <!-- =========================================================== -->
+    <Set name="stopAtShutdown">true</Set>
+    <Set name="sendServerVersion">false</Set>
+    <Set name="sendDateHeader">true</Set>
+    <Set name="gracefulShutdown">
+        <SystemProperty name="xn.jetty.gracefulShutdownTimeoutInMs" default="1000"/>
+    </Set>
+</Configure>
diff --git a/profiles/killpay/src/main/jettyconsole/killbill.png b/profiles/killpay/src/main/jettyconsole/killbill.png
new file mode 100644
index 0000000..610f58a
Binary files /dev/null and b/profiles/killpay/src/main/jettyconsole/killbill.png differ
diff --git a/profiles/killpay/src/main/resources/org/killbill/billing/server/version.properties b/profiles/killpay/src/main/resources/org/killbill/billing/server/version.properties
new file mode 100644
index 0000000..3ea3ec3
--- /dev/null
+++ b/profiles/killpay/src/main/resources/org/killbill/billing/server/version.properties
@@ -0,0 +1,6 @@
+product-name    = ${project.name}
+version         = ${project.version}
+built-by        = ${user.name}
+build-jdk       = ${java.version}
+build-time      = ${build.timestamp}
+enterprise      = false
diff --git a/profiles/killpay/src/main/resources/update-checker/killbill-server-update-list.properties b/profiles/killpay/src/main/resources/update-checker/killbill-server-update-list.properties
new file mode 100644
index 0000000..e143c38
--- /dev/null
+++ b/profiles/killpay/src/main/resources/update-checker/killbill-server-update-list.properties
@@ -0,0 +1,34 @@
+## Top level keys
+# general.notice = This notice should rarely, if ever, be used as everyone will see it
+
+### 0.11.x series ###
+
+## 0.11.10 -- latest unstable release
+0.11.10.updates           =
+0.11.10.notices           = This is the latest dev release.
+0.11.10.release-notes     = http://kill-bill.org
+
+## 0.11.9
+0.11.9.updates           = 0.11.10
+0.11.9.notices           = We recommend upgrading to 0.11.10, our latest dev release.
+0.11.9.release-notes     = http://kill-bill.org
+
+## 0.11.8
+0.11.8.updates           = 0.11.10
+0.11.8.notices           = We recommend upgrading to 0.11.10, our latest dev release.
+0.11.8.release-notes     = http://kill-bill.org
+
+## 0.11.7
+0.11.7.updates           = 0.11.10
+0.11.7.notices           = We recommend upgrading to 0.11.10, our latest dev release.
+0.11.7.release-notes     = http://kill-bill.org
+
+## 0.11.6
+0.11.6.updates           = 0.11.10
+0.11.6.notices           = We recommend upgrading to 0.11.10, our latest dev release.
+0.11.6.release-notes     = http://kill-bill.org
+
+## 0.11.5
+0.11.5.updates           = 0.11.10
+0.11.5.notices           = We recommend upgrading to 0.11.10, our latest dev release.
+0.11.5.release-notes     = http://kill-bill.org
diff --git a/profiles/killpay/src/main/webapp/img/glyphicons-halflings.png b/profiles/killpay/src/main/webapp/img/glyphicons-halflings.png
new file mode 100644
index 0000000..a996999
Binary files /dev/null and b/profiles/killpay/src/main/webapp/img/glyphicons-halflings.png differ
diff --git a/profiles/killpay/src/main/webapp/img/glyphicons-halflings-white.png b/profiles/killpay/src/main/webapp/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..3bf6484
Binary files /dev/null and b/profiles/killpay/src/main/webapp/img/glyphicons-halflings-white.png differ
diff --git a/profiles/killpay/src/main/webapp/img/KillBillLogo400x400.png b/profiles/killpay/src/main/webapp/img/KillBillLogo400x400.png
new file mode 100644
index 0000000..610f58a
Binary files /dev/null and b/profiles/killpay/src/main/webapp/img/KillBillLogo400x400.png differ
diff --git a/profiles/killpay/src/main/webapp/index.html b/profiles/killpay/src/main/webapp/index.html
new file mode 100644
index 0000000..e6f4b1f
--- /dev/null
+++ b/profiles/killpay/src/main/webapp/index.html
@@ -0,0 +1,64 @@
+<!--
+  ~ Copyright 2014 Groupon, Inc
+  ~ Copyright 2014 The Billing Project, LLC
+  ~
+  ~ The Billing Project licenses this file to you under the Apache License, version 2.0
+  ~ (the "License"); you may not use this file except in compliance with the
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  ~ License for the specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+  <!DOCTYPE html>
+  <html lang="en">
+      <head>
+          <meta charset="utf-8">
+          <title>Kill Pay, the Open-Source payment platform</title>
+
+          <!--[if lt IE 9]>
+          <script src=javascripts/html5.js" type="text/javascript"></script>
+          <![endif]-->
+          <script src="javascripts/jquery.min.js"></script>
+          <script src="javascripts/bootstrap.min.js"></script>
+
+          <link rel=stylesheet type="text/css" href="stylesheets/bootstrap.min.css">
+          <link rel=stylesheet type="text/css" href="stylesheets/killbill.css">
+      </head>
+
+      <body data-spy="scroll" data-target=".bs-docs-sidebar">
+          <div class="navbar navbar-inverse navbar-fixed-top">
+              <div class="navbar-inner">
+                  <div class="container">
+                      <div class="nav-collapse collapse">
+                          <ul class="nav pull-right">
+                              <li><a href="http://groups.google.com/group/killbilling-users" target="_blank">User Mailing-List</a></li>
+                              <li><a href="http://groups.google.com/group/killbilling-dev" target="_blank">Dev Mailing-List</a></li>
+                          </ul>
+                      </div>
+                  </div>
+              </div>
+          </div>
+          <div class="container">
+              <div class="jumbotron">
+                  <h1>Kill Bill</h1>
+                  <h2>The Open-Source payment Platform</h2>
+                  <img src="img/KillBillLogo400x400.png" style="height: 200px; margin-bottom: 20px;" />
+              </div>
+
+              <div class="marketing">
+                  <h1>Congratulations!</h1>
+                  <p class="lead">Kill Pay is up and running.</p>
+                  <ul class="inline">
+                      <li><a class="btn btn-primary btn-large" href="/1.0/metrics?pretty=true">Metrics</a></li>
+                      <li><a class="btn btn-primary btn-large" href="/1.0/threads">Threads</a></li>
+                  </ul>
+              </div>
+          </div>
+    </body>
+</html>
diff --git a/profiles/killpay/src/main/webapp/javascripts/bootstrap.min.js b/profiles/killpay/src/main/webapp/javascripts/bootstrap.min.js
new file mode 100644
index 0000000..e05923d
--- /dev/null
+++ b/profiles/killpay/src/main/webapp/javascripts/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+* Bootstrap.js by @fat & @mdo
+* Copyright 2012 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f<s.length-1&&f++,~f||(f=0),s.eq(f).focus()}};var s=e.fn.dropdown;e.fn.dropdown=function(t){return this.each(function(){var r=e(this),i=r.data("dropdown");i||r.data("dropdown",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.dropdown.Constructor=n,e.fn.dropdown.noConflict=function(){return e.fn.dropdown=s,this},e(document).on("click.dropdown.data-api",r).on("click.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on(".dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api",t,n.prototype.toggle).on("keydown.dropdown.data-api",t+", [role=menu]",n.prototype.keydown)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss="modal"]',"click.dismiss.modal",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};t.prototype={constructor:t,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var t=this,n=e.Event("show");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var n=e.support.transition&&t.$element.hasClass("fade");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass("in").attr("aria-hidden",!1),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.focus().trigger("shown")}):t.$element.focus().trigger("shown")})},hide:function(t){t&&t.preventDefault();var n=this;t=e.Event("hide"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,this.escape(),e(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),e.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var t=this;e(document).on("focusin.modal",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},escape:function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var t=this,n=setTimeout(function(){t.$element.off(e.support.transition.end),t.hideModal()},500);this.$element.one(e.support.transition.end,function(){clearTimeout(n),t.hideModal()})},hideModal:function(){var e=this;this.$element.hide(),this.backdrop(function(){e.removeBackdrop(),e.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(t){var n=this,r=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var i=e.support.transition&&r;this.$backdrop=e('<div class="modal-backdrop '+r+'" />').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(n=e.isFunction(this.source)?this.source(this.query,e.proxy(this.process,this)):this.source,n?this.process(n):this)},process:function(t){var n=this;return t=e.grep(t,function(e){return n.matcher(e)}),t=this.sorter(t),t.length?this.render(t.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(e){return~e.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(e){var t=[],n=[],r=[],i;while(i=e.shift())i.toLowerCase().indexOf(this.query.toLowerCase())?~i.indexOf(this.query)?n.push(i):r.push(i):t.push(i);return t.concat(n,r)},highlighter:function(e){var t=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return e.replace(new RegExp("("+t+")","ig"),function(e,t){return"<strong>"+t+"</strong>"})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
\ No newline at end of file
diff --git a/profiles/killpay/src/main/webapp/javascripts/html5.js b/profiles/killpay/src/main/webapp/javascripts/html5.js
new file mode 100644
index 0000000..087417a
--- /dev/null
+++ b/profiles/killpay/src/main/webapp/javascripts/html5.js
@@ -0,0 +1,9 @@
+/*
+ HTML5 Shiv v3.6.2pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
+ Uncompressed source: https://github.com/aFarkas/html5shiv
+*/
+(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
+a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}</style>";
+c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
+"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
+for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);
\ No newline at end of file
diff --git a/profiles/killpay/src/main/webapp/javascripts/jquery.min.js b/profiles/killpay/src/main/webapp/javascripts/jquery.min.js
new file mode 100644
index 0000000..198b3ff
--- /dev/null
+++ b/profiles/killpay/src/main/webapp/javascripts/jquery.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.1 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cb(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function ca(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bE.test(a)?d(a,e):ca(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)ca(a+"["+e+"]",b[e],c,d);else d(a,b)}function b_(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bT,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bP),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bC(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bx:by,g=0,h=e.length;if(d>0){if(c!=="border")for(;g<h;g++)c||(d-=parseFloat(f.css(a,"padding"+e[g]))||0),c==="margin"?d+=parseFloat(f.css(a,c+e[g]))||0:d-=parseFloat(f.css(a,"border"+e[g]+"Width"))||0;return d+"px"}d=bz(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0;if(c)for(;g<h;g++)d+=parseFloat(f.css(a,"padding"+e[g]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+e[g]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+e[g]))||0);return d+"px"}function bp(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p,q=c.createElement("div"),r=c.documentElement;q.setAttribute("className","t"),q.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="<div style='width:4px;'></div>",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h<g;h++)e=d[h],e&&(c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};
+f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=[],j,k,l,m,n,o,p,q,r,s,t;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click")){m=f(this),m.context=this.ownerDocument||this;for(l=c.target;l!=this;l=l.parentNode||this){o={},q=[],m[0]=l;for(j=0;j<e;j++)r=d[j],s=r.selector,o[s]===b&&(o[s]=r.quick?H(l,r.quick):m.is(s)),o[s]&&q.push(r);q.length&&i.push({elem:l,matches:q})}}d.length>e&&i.push({elem:this,matches:d.slice(e)});for(j=0;j<i.length&&!c.isPropagationStopped();j++){p=i[j],c.currentTarget=p.elem;for(k=0;k<p.matches.length&&!c.isImmediatePropagationStopped();k++){r=p.matches[k];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=((f.event.special[r.origType]||{}).handle||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.POS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function()
+{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bp)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bn(k[i]);else bn(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bq=/alpha\([^)]*\)/i,br=/opacity=([^)]*)/,bs=/([A-Z]|^ms)/g,bt=/^-?\d+(?:px)?$/i,bu=/^-?\d/,bv=/^([\-+])=([\-+.\de]+)/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bv.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bC(a,b,d);f.swap(a,bw,function(){e=bC(a,b,d)});return e}},set:function(a,b){if(!bt.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cv(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cn.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=co.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cr||cs(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cp&&(cp=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cr||cs(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cp),cp=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file
diff --git a/profiles/killpay/src/main/webapp/stylesheets/bootstrap.min.css b/profiles/killpay/src/main/webapp/stylesheets/bootstrap.min.css
new file mode 100644
index 0000000..fd5ed73
--- /dev/null
+++ b/profiles/killpay/src/main/webapp/stylesheets/bootstrap.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap v2.3.0
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}
diff --git a/profiles/killpay/src/main/webapp/stylesheets/killbill.css b/profiles/killpay/src/main/webapp/stylesheets/killbill.css
new file mode 100644
index 0000000..a5f7cca
--- /dev/null
+++ b/profiles/killpay/src/main/webapp/stylesheets/killbill.css
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010-2013 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.
+ */
+
+body {
+    padding-top: 60px;
+}
+
+section {
+    padding-top: 30px;
+}
+
+.api_table {
+    margin-left: 15px;
+}
+
+.jumbotron {
+  margin: 80px 0;
+  text-align: center;
+}
+.jumbotron h1 {
+  font-size: 100px;
+  line-height: 1;
+}
+.jumbotron .lead {
+  font-size: 24px;
+  line-height: 1.25;
+}
+.jumbotron .btn {
+  font-size: 21px;
+  padding: 14px 24px;
+}
+.jumbotron ul {
+  list-style: none;
+}
+
+.marketing {
+  text-align: center;
+}
+
+.center {
+  float: none;
+  margin-left: auto;
+  margin-right: auto;
+  text-align: center;
+}
diff --git a/profiles/killpay/src/main/webapp/WEB-INF/web.xml b/profiles/killpay/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..6090924
--- /dev/null
+++ b/profiles/killpay/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2014 Groupon, Inc
+  ~ Copyright 2014 The Billing Project, LLC
+  ~
+  ~ The Billing Project licenses this file to you under the Apache License, version 2.0
+  ~ (the "License"); you may not use this file except in compliance with the
+  ~ License.  You may obtain a copy of the License at:
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+  ~ License for the specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<web-app
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xmlns="http://java.sun.com/xml/ns/javaee"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+        version="2.5">
+    <filter>
+        <!-- Filter all requests through Shiro -->
+        <filter-name>ShiroFilter</filter-name>
+        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <!-- The Shiro filter-mapping should come first -->
+        <filter-name>ShiroFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+        <dispatcher>REQUEST</dispatcher>
+        <dispatcher>FORWARD</dispatcher>
+        <dispatcher>INCLUDE</dispatcher>
+        <dispatcher>ERROR</dispatcher>
+    </filter-mapping>
+    <filter>
+        <!-- Guice emulates Servlet API with DI -->
+        <filter-name>guiceFilter</filter-name>
+        <filter-class>org.killbill.billing.server.filters.KillbillGuiceFilter</filter-class>
+    </filter>
+    <filter-mapping>
+        <filter-name>guiceFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+    <listener>
+        <!-- Jersey insists on using java.util.logging (JUL) -->
+        <listener-class>org.killbill.commons.skeleton.listeners.JULServletContextListener</listener-class>
+    </listener>
+    <listener>
+        <!-- Context listener: called at startup time and creates the injector -->
+        <listener-class>org.killbill.billing.server.listeners.KillpayGuiceListener</listener-class>
+    </listener>
+    <listener>
+        <!-- Context listener: called at shutdown time and performs cleanup tasks -->
+        <listener-class>org.killbill.billing.server.listeners.CleanupListener</listener-class>
+    </listener>
+
+    <!-- ServletHandler#handle requires a backend servlet. Besides, this will also be used to serve static resources,
+         such as the favicon or the welcome page -->
+    <servlet-mapping>
+        <servlet-name>default</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+</web-app>

README.md 2(+1 -1)

diff --git a/README.md b/README.md
index 442e402..83e25e8 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
 [![Build Status](https://travis-ci.org/killbill/killbill.png)](https://travis-ci.org/killbill/killbill)
 
-Killbill is an open source subscription management/billing system.
+Killbill is an open source subscription management/billing & payment system.
 You can find the documentation [here](http://kill-bill.org).
diff --git a/subscription/pom.xml b/subscription/pom.xml
index 5cb4726..1b46563 100644
--- a/subscription/pom.xml
+++ b/subscription/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-subscription</artifactId>
@@ -88,6 +88,25 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-lifecycle</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
index d82a0e1..29b1b20 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/svcs/DefaultSubscriptionInternalApi.java
@@ -25,6 +25,8 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -80,6 +82,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
 
     private final AddonUtils addonUtils;
     private final NonEntityDao nonEntityDao;
+    private final CacheControllerDispatcher controllerDispatcher;
 
     @Inject
     public DefaultSubscriptionInternalApi(final SubscriptionDao dao,
@@ -87,10 +90,12 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                                           final Clock clock,
                                           final CatalogService catalogService,
                                           final AddonUtils addonUtils,
-                                          final NonEntityDao nonEntityDao) {
+                                          final NonEntityDao nonEntityDao,
+                                          final CacheControllerDispatcher controllerDispatcher) {
         super(dao, apiService, clock, catalogService);
         this.addonUtils = addonUtils;
         this.nonEntityDao = nonEntityDao;
+        this.controllerDispatcher = controllerDispatcher;
     }
 
     @Override
@@ -151,7 +156,7 @@ public class DefaultSubscriptionInternalApi extends SubscriptionApiBase implemen
                                                                   plan.getProduct().getCategory().toString()));
             }
 
-            final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
+            final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, controllerDispatcher.getCacheController(CacheType.OBJECT_ID));
             return apiService.createPlan(new SubscriptionBuilder()
                                                  .setId(UUID.randomUUID())
                                                  .setBundleId(bundleId)
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
index 32199b0..a75c96a 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
@@ -1,5 +1,6 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, 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
@@ -377,10 +378,10 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
 
         final List<SubscriptionBaseEvent> changeEvents = new ArrayList<SubscriptionBaseEvent>();
         // Only add the PHASE if it does not coincide with the CHANGE, if not this is 'just' a CHANGE.
+        changeEvents.add(changeEvent);
         if (nextPhaseEvent != null && !nextPhaseEvent.getEffectiveDate().equals(changeEvent.getEffectiveDate())) {
             changeEvents.add(nextPhaseEvent);
         }
-        changeEvents.add(changeEvent);
 
         final InternalCallContext internalCallContext = createCallContextFromBundleId(subscription.getBundleId(), context);
         dao.changePlan(subscription, changeEvents, internalCallContext);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
index 8447d80..c38610b 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,12 +21,11 @@ package org.killbill.billing.subscription.engine.core;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.killbill.bus.api.PersistentBus;
-import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.ProductCategory;
+import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
 import org.killbill.billing.subscription.alignment.PlanAligner;
 import org.killbill.billing.subscription.alignment.TimedPhase;
 import org.killbill.billing.subscription.api.SubscriptionBaseApiService;
@@ -40,20 +41,20 @@ import org.killbill.billing.subscription.events.phase.PhaseEvent;
 import org.killbill.billing.subscription.events.phase.PhaseEventData;
 import org.killbill.billing.subscription.events.user.ApiEvent;
 import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
-import org.killbill.billing.lifecycle.LifecycleHandlerType;
-import org.killbill.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.billing.util.callcontext.CallOrigin;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.bus.api.PersistentBus;
+import org.killbill.bus.api.PersistentBus.EventBusException;
+import org.killbill.clock.Clock;
 import org.killbill.notificationq.api.NotificationEvent;
 import org.killbill.notificationq.api.NotificationQueue;
 import org.killbill.notificationq.api.NotificationQueueService;
 import org.killbill.notificationq.api.NotificationQueueService.NoSuchNotificationQueue;
 import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueAlreadyExists;
 import org.killbill.notificationq.api.NotificationQueueService.NotificationQueueHandler;
-import org.killbill.billing.util.callcontext.CallOrigin;
-import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.billing.util.callcontext.InternalCallContextFactory;
-import org.killbill.billing.util.callcontext.UserType;
-import org.killbill.clock.Clock;
-import org.killbill.billing.events.EffectiveSubscriptionInternalEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 
@@ -195,6 +196,4 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
     private int onBasePlanEvent(final DefaultSubscriptionBase baseSubscription, final ApiEvent event, final InternalCallContext context) {
         return apiService.cancelAddOnsIfRequired(baseSubscription, event.getEffectiveDate(), context);
     }
-
-
 }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
index df7d34f..4b1f2c4 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionBundleModelDao.java
@@ -25,8 +25,9 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class SubscriptionBundleModelDao extends EntityBase implements EntityModelDao<SubscriptionBaseBundle> {
+public class SubscriptionBundleModelDao extends EntityModelDaoBase implements EntityModelDao<SubscriptionBaseBundle> {
 
     private String externalKey;
     private UUID accountId;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
index dde70c9..2809ea8 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionEventModelDao.java
@@ -41,8 +41,9 @@ import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class SubscriptionEventModelDao extends EntityBase implements EntityModelDao<SubscriptionBaseEvent> {
+public class SubscriptionEventModelDao extends EntityModelDaoBase implements EntityModelDao<SubscriptionBaseEvent> {
 
     private long totalOrdering;
     private EventType eventType;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java
index 0e09ec5..d9d778c 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/model/SubscriptionModelDao.java
@@ -27,8 +27,9 @@ import org.killbill.billing.subscription.api.SubscriptionBase;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class SubscriptionModelDao extends EntityBase implements EntityModelDao<SubscriptionBase> {
+public class SubscriptionModelDao extends EntityModelDaoBase implements EntityModelDao<SubscriptionBase> {
 
     private UUID bundleId;
     private ProductCategory category;
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java b/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java
index 7d53ac1..01531ea 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/glue/DefaultSubscriptionModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,10 +18,8 @@
 
 package org.killbill.billing.subscription.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
 import org.killbill.billing.glue.SubscriptionModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.alignment.MigrationPlanAligner;
 import org.killbill.billing.subscription.alignment.PlanAligner;
 import org.killbill.billing.subscription.api.SubscriptionBaseApiService;
@@ -41,22 +41,21 @@ import org.killbill.billing.subscription.engine.dao.DefaultSubscriptionDao;
 import org.killbill.billing.subscription.engine.dao.RepairSubscriptionDao;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.util.config.SubscriptionConfig;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.AbstractModule;
 import com.google.inject.name.Names;
 
-public class DefaultSubscriptionModule extends AbstractModule implements SubscriptionModule {
+public class DefaultSubscriptionModule extends KillBillModule implements SubscriptionModule {
 
     public static final String REPAIR_NAMED = "repair";
 
-    protected final ConfigSource configSource;
-
-    public DefaultSubscriptionModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public DefaultSubscriptionModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     protected void installConfig() {
-        final SubscriptionConfig config = new ConfigurationObjectFactory(configSource).build(SubscriptionConfig.class);
+        final SubscriptionConfig config = new ConfigurationObjectFactory(skifeConfigSource).build(SubscriptionConfig.class);
         bind(SubscriptionConfig.class).toInstance(config);
     }
 
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java b/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java
index 0056f86..8e089ba 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java
@@ -17,22 +17,12 @@
 package org.killbill.billing.subscription.alignment;
 
 import java.util.List;
-import java.util.Map;
 
 import org.joda.time.DateTime;
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-import org.testng.Assert;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
-import org.killbill.billing.catalog.DefaultCatalogService;
 import org.killbill.billing.catalog.api.CatalogApiException;
 import org.killbill.billing.catalog.api.PhaseType;
 import org.killbill.billing.catalog.api.Plan;
 import org.killbill.billing.catalog.api.PriceListSet;
-import org.killbill.billing.catalog.io.VersionedCatalogLoader;
-import org.killbill.clock.DefaultClock;
 import org.killbill.billing.subscription.SubscriptionTestSuiteNoDB;
 import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseApiException;
@@ -43,10 +33,12 @@ import org.killbill.billing.subscription.events.user.ApiEventBase;
 import org.killbill.billing.subscription.events.user.ApiEventBuilder;
 import org.killbill.billing.subscription.events.user.ApiEventType;
 import org.killbill.billing.subscription.exceptions.SubscriptionBaseError;
-import org.killbill.billing.util.config.CatalogConfig;
+import org.killbill.clock.DefaultClock;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 
 public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
 
@@ -119,7 +111,8 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
         final TimedPhase newPhase = getNextTimedPhaseOnChange(defaultSubscriptionBase, newProductName, effectiveChangeDate);
         Assert.assertEquals(newPhase.getStartPhase(), defaultSubscriptionBase.getStartDate().plusDays(30),
                             String.format("Start phase: %s, but bundle start date: %s and subscription start date: %s",
-                                          newPhase.getStartPhase(), defaultSubscriptionBase.getBundleStartDate(), defaultSubscriptionBase.getStartDate()));
+                                          newPhase.getStartPhase(), defaultSubscriptionBase.getBundleStartDate(), defaultSubscriptionBase.getStartDate())
+                           );
     }
 
     @Test(groups = "fast")
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/DefaultSubscriptionTestInitializer.java b/subscription/src/test/java/org/killbill/billing/subscription/DefaultSubscriptionTestInitializer.java
index 28b7592..aefb6fb 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/DefaultSubscriptionTestInitializer.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/DefaultSubscriptionTestInitializer.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -20,9 +22,6 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.callcontext.InternalCallContext;
@@ -30,13 +29,15 @@ import org.killbill.billing.catalog.DefaultCatalogService;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogService;
 import org.killbill.billing.catalog.api.Currency;
-import org.killbill.clock.ClockMock;
+import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.mock.MockAccountBuilder;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseService;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
 import org.killbill.billing.subscription.engine.core.DefaultSubscriptionBaseService;
-import org.killbill.billing.util.svcsapi.bus.BusService;
+import org.killbill.clock.ClockMock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static org.testng.Assert.assertNotNull;
 
@@ -47,7 +48,6 @@ public class DefaultSubscriptionTestInitializer implements SubscriptionTestIniti
     protected static final Logger log = LoggerFactory.getLogger(DefaultSubscriptionTestInitializer.class);
 
     public DefaultSubscriptionTestInitializer() {
-
     }
 
     public Catalog initCatalog(final CatalogService catalogService) throws Exception {
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModule.java b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModule.java
index ff38995..9d6cc96 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModule.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,21 +18,20 @@
 
 package org.killbill.billing.subscription.glue;
 
-import org.mockito.Mockito;
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.catalog.glue.CatalogModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.DefaultSubscriptionTestInitializer;
 import org.killbill.billing.subscription.SubscriptionTestInitializer;
 import org.killbill.billing.subscription.api.user.TestSubscriptionHelper;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
+import org.mockito.Mockito;
 
 public class TestDefaultSubscriptionModule extends DefaultSubscriptionModule {
 
-    public TestDefaultSubscriptionModule(final ConfigSource configSource) {
+    public TestDefaultSubscriptionModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -38,7 +39,7 @@ public class TestDefaultSubscriptionModule extends DefaultSubscriptionModule {
     protected void configure() {
         super.configure();
         install(new CatalogModule(configSource));
-        install(new CallContextModule());
+        install(new CallContextModule(configSource));
         install(new CacheModule(configSource));
 
         bind(AccountUserApi.class).toInstance(Mockito.mock(AccountUserApi.class));
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleNoDB.java b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleNoDB.java
index e98712b..4cae607 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleNoDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,26 +18,19 @@
 
 package org.killbill.billing.subscription.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
 import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
-import org.killbill.notificationq.MockNotificationQueueService;
-import org.killbill.notificationq.api.NotificationQueueConfig;
-import org.killbill.notificationq.api.NotificationQueueService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.timeline.RepairSubscriptionLifecycleDao;
 import org.killbill.billing.subscription.engine.dao.MockSubscriptionDaoMemory;
 import org.killbill.billing.subscription.engine.dao.RepairSubscriptionDao;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
-import org.killbill.billing.util.bus.InMemoryBusModule;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.inject.name.Names;
 
 public class TestDefaultSubscriptionModuleNoDB extends TestDefaultSubscriptionModule {
 
-    public TestDefaultSubscriptionModuleNoDB(final ConfigSource configSource) {
+    public TestDefaultSubscriptionModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -47,28 +42,12 @@ public class TestDefaultSubscriptionModuleNoDB extends TestDefaultSubscriptionMo
         bind(RepairSubscriptionDao.class).asEagerSingleton();
     }
 
-    private void installNotificationQueue() {
-        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
-        configureNotificationQueueConfig();
-    }
-
-    protected void configureNotificationQueueConfig() {
-        final NotificationQueueConfig config = new ConfigurationObjectFactory(configSource).buildWithReplacements(NotificationQueueConfig.class,
-                                                                                                                  ImmutableMap.<String, String>of("instanceName", "main"));
-        bind(NotificationQueueConfig.class).toInstance(config);
-    }
-
     @Override
     protected void configure() {
-
-        install(new GuicyKillbillTestNoDBModule());
+        install(new GuicyKillbillTestNoDBModule(configSource));
 
         super.configure();
 
-        install(new InMemoryBusModule(configSource));
-        installNotificationQueue();
-
-        install(new MockNonEntityDaoModule());
-
+        install(new MockNonEntityDaoModule(configSource));
     }
 }
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleWithEmbeddedDB.java b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleWithEmbeddedDB.java
index 7cb0187..51f0305 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleWithEmbeddedDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/glue/TestDefaultSubscriptionModuleWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,24 +18,20 @@
 
 package org.killbill.billing.subscription.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.timeline.RepairSubscriptionLifecycleDao;
 import org.killbill.billing.subscription.engine.dao.MockSubscriptionDaoSql;
 import org.killbill.billing.subscription.engine.dao.RepairSubscriptionDao;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
-import org.killbill.billing.util.glue.BusModule;
 import org.killbill.billing.util.glue.CustomFieldModule;
-import org.killbill.billing.util.glue.MetricsModule;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
-import org.killbill.billing.util.glue.NotificationQueueModule;
 
 import com.google.inject.name.Names;
 
 public class TestDefaultSubscriptionModuleWithEmbeddedDB extends TestDefaultSubscriptionModule {
 
-    public TestDefaultSubscriptionModuleWithEmbeddedDB(final ConfigSource configSource) {
+    public TestDefaultSubscriptionModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -48,16 +46,12 @@ public class TestDefaultSubscriptionModuleWithEmbeddedDB extends TestDefaultSubs
     @Override
     protected void configure() {
 
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
 
-        install(new NonEntityDaoModule());
+        install(new NonEntityDaoModule(configSource));
 
-        //installDBI();
+        install(new CustomFieldModule(configSource));
 
-        install(new NotificationQueueModule(configSource));
-        install(new CustomFieldModule());
-        install(new MetricsModule());
-        install(new BusModule(configSource));
         super.configure();
     }
 }
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestInitializer.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestInitializer.java
index aa0b39c..9fba4e7 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestInitializer.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestInitializer.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -21,11 +23,11 @@ import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogService;
-import org.killbill.clock.ClockMock;
+import org.killbill.billing.lifecycle.api.BusService;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseService;
 import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
-import org.killbill.billing.util.svcsapi.bus.BusService;
+import org.killbill.clock.ClockMock;
 
 public interface SubscriptionTestInitializer {
 
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
index 2d88272..a099dc0 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,17 +18,15 @@
 
 package org.killbill.billing.subscription;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
-
 import javax.inject.Inject;
 
 import org.killbill.billing.GuicyKillbillTestSuiteNoDB;
-import org.killbill.billing.TestKillbillConfigSource;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogService;
+import org.killbill.billing.lifecycle.api.BusService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseService;
 import org.killbill.billing.subscription.api.migration.SubscriptionBaseMigrationApi;
@@ -37,9 +37,7 @@ import org.killbill.billing.subscription.api.user.TestSubscriptionHelper;
 import org.killbill.billing.subscription.engine.dao.MockSubscriptionDaoMemory;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.glue.TestDefaultSubscriptionModuleNoDB;
-import org.killbill.billing.util.KillbillConfigSource;
 import org.killbill.billing.util.config.SubscriptionConfig;
-import org.killbill.billing.util.svcsapi.bus.BusService;
 import org.killbill.clock.ClockMock;
 import org.mockito.Mockito;
 import org.skife.jdbi.v2.IDBI;
@@ -97,8 +95,8 @@ public class SubscriptionTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
     protected SubscriptionBaseBundle bundle;
 
     @Override
-    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
-        return new TestKillbillConfigSource("/subscription.properties");
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/subscription.properties");
     }
 
     @BeforeClass(groups = "fast")
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
index 0be56d3..a1cfc8e 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,17 +18,15 @@
 
 package org.killbill.billing.subscription;
 
-import java.io.IOException;
-import java.net.URISyntaxException;
-
 import javax.inject.Inject;
 
 import org.killbill.billing.GuicyKillbillTestSuiteWithEmbeddedDB;
-import org.killbill.billing.TestKillbillConfigSource;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.api.TestApiListener;
 import org.killbill.billing.catalog.api.Catalog;
 import org.killbill.billing.catalog.api.CatalogService;
+import org.killbill.billing.lifecycle.api.BusService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseService;
 import org.killbill.billing.subscription.api.migration.SubscriptionBaseMigrationApi;
@@ -36,9 +36,7 @@ import org.killbill.billing.subscription.api.user.SubscriptionBaseBundle;
 import org.killbill.billing.subscription.api.user.TestSubscriptionHelper;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.glue.TestDefaultSubscriptionModuleWithEmbeddedDB;
-import org.killbill.billing.util.KillbillConfigSource;
 import org.killbill.billing.util.config.SubscriptionConfig;
-import org.killbill.billing.util.svcsapi.bus.BusService;
 import org.killbill.clock.ClockMock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -91,8 +89,8 @@ public class SubscriptionTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteW
     protected SubscriptionBaseBundle bundle;
 
     @Override
-    protected KillbillConfigSource getConfigSource() throws IOException, URISyntaxException {
-        return new TestKillbillConfigSource("/subscription.properties");
+    protected KillbillConfigSource getConfigSource() {
+        return getConfigSource("/subscription.properties");
     }
 
     @BeforeClass(groups = "slow")

tenant/pom.xml 24(+15 -9)

diff --git a/tenant/pom.xml b/tenant/pom.xml
index 0320936..13b6f60 100644
--- a/tenant/pom.xml
+++ b/tenant/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-tenant</artifactId>
@@ -67,6 +67,20 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
@@ -101,14 +115,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.skife.config</groupId>
-            <artifactId>config-magic</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
             <scope>test</scope>
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVModelDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVModelDao.java
index 04e72a0..014c9e0 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVModelDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantKVModelDao.java
@@ -24,8 +24,9 @@ import org.killbill.billing.tenant.api.TenantKV;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class TenantKVModelDao extends EntityBase implements EntityModelDao<TenantKV> {
+public class TenantKVModelDao extends EntityModelDaoBase implements EntityModelDao<TenantKV> {
 
     private String tenantKey;
     private String tenantValue;
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantModelDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantModelDao.java
index 5cb998e..8d6c92e 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantModelDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/TenantModelDao.java
@@ -24,8 +24,9 @@ import org.killbill.billing.tenant.api.Tenant;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class TenantModelDao extends EntityBase implements EntityModelDao<Tenant> {
+public class TenantModelDao extends EntityModelDaoBase implements EntityModelDao<Tenant> {
 
     private String externalKey;
     private String apiKey;
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/glue/TenantModule.java b/tenant/src/main/java/org/killbill/billing/tenant/glue/TenantModule.java
index 1296cc4..c048bcf 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/glue/TenantModule.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/glue/TenantModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,23 +18,19 @@
 
 package org.killbill.billing.tenant.glue;
 
-import org.skife.config.ConfigSource;
-
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.tenant.api.DefaultTenantService;
 import org.killbill.billing.tenant.api.TenantService;
 import org.killbill.billing.tenant.api.TenantUserApi;
 import org.killbill.billing.tenant.api.user.DefaultTenantUserApi;
 import org.killbill.billing.tenant.dao.DefaultTenantDao;
 import org.killbill.billing.tenant.dao.TenantDao;
+import org.killbill.billing.util.glue.KillBillModule;
 
-import com.google.inject.AbstractModule;
-
-public class TenantModule extends AbstractModule {
-
-    protected final ConfigSource configSource;
+public class TenantModule extends KillBillModule {
 
-    public TenantModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public TenantModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     private void installConfig() {
diff --git a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModule.java b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModule.java
index 3886451..a787d5e 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModule.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,13 @@
 
 package org.killbill.billing.tenant.glue;
 
-import org.skife.config.ConfigSource;
-
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.CacheModule;
 import org.killbill.billing.util.glue.CallContextModule;
 
 public class TestTenantModule extends TenantModule {
 
-    public TestTenantModule(final ConfigSource configSource) {
+    public TestTenantModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -32,6 +33,6 @@ public class TestTenantModule extends TenantModule {
         super.configure();
 
         install(new CacheModule(configSource));
-        install(new CallContextModule());
+        install(new CallContextModule(configSource));
     }
 }
diff --git a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleNoDB.java b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleNoDB.java
index 724211b..7d240aa 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleNoDB.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,13 @@
 
 package org.killbill.billing.tenant.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
 import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestTenantModuleNoDB extends TestTenantModule {
 
-    public TestTenantModuleNoDB(final ConfigSource configSource) {
+    public TestTenantModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -31,7 +32,7 @@ public class TestTenantModuleNoDB extends TestTenantModule {
     public void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestNoDBModule());
-        install(new MockNonEntityDaoModule());
+        install(new GuicyKillbillTestNoDBModule(configSource));
+        install(new MockNonEntityDaoModule(configSource));
     }
 }
diff --git a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleWithEmbeddedDB.java b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleWithEmbeddedDB.java
index 3f05c33..c2acf95 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleWithEmbeddedDB.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/glue/TestTenantModuleWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,13 @@
 
 package org.killbill.billing.tenant.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.NonEntityDaoModule;
 
 public class TestTenantModuleWithEmbeddedDB extends TestTenantModule {
 
-    public TestTenantModuleWithEmbeddedDB(final ConfigSource configSource) {
+    public TestTenantModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -31,7 +32,7 @@ public class TestTenantModuleWithEmbeddedDB extends TestTenantModule {
     public void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
-        install(new NonEntityDaoModule());
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
+        install(new NonEntityDaoModule(configSource));
     }
 }

usage/pom.xml 38(+25 -13)

diff --git a/usage/pom.xml b/usage/pom.xml
index 83e2d17..0d62d1a 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-usage</artifactId>
@@ -36,6 +36,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
         </dependency>
@@ -58,6 +63,25 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-base</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-lifecycle</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-util</artifactId>
         </dependency>
         <dependency>
@@ -69,10 +93,6 @@
         <dependency>
             <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-clock</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.kill-bill.commons</groupId>
-            <artifactId>killbill-clock</artifactId>
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
@@ -92,14 +112,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.skife.config</groupId>
-            <artifactId>config-magic</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
             <scope>test</scope>
diff --git a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
index c685257..5b771d9 100644
--- a/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
+++ b/usage/src/main/java/org/killbill/billing/usage/api/user/DefaultUsageUserApi.java
@@ -17,6 +17,7 @@
 package org.killbill.billing.usage.api.user;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
@@ -26,6 +27,7 @@ import javax.inject.Inject;
 import org.joda.time.DateTime;
 
 import org.killbill.billing.ObjectType;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.usage.api.RolledUpUsage;
 import org.killbill.billing.usage.api.UsageUserApi;
 import org.killbill.billing.usage.dao.RolledUpUsageDao;
@@ -56,14 +58,25 @@ public class DefaultUsageUserApi implements UsageUserApi {
 
     @Override
     public RolledUpUsage getUsageForSubscription(final UUID subscriptionId,  final String unitType, final DateTime startTime, final DateTime endTime, final TenantContext context) {
-        final RolledUpUsageModelDao usageForSubscription = rolledUpUsageDao.getUsageForSubscription(subscriptionId, internalCallContextFactory.createInternalTenantContext(context));
+        final RolledUpUsageModelDao usageForSubscription = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startTime, endTime, unitType, internalCallContextFactory.createInternalTenantContext(context));
         return new DefaultRolledUpUsage(usageForSubscription);
     }
 
     @Override
     public List<RolledUpUsage> getAllUsageForSubscription(final UUID subscriptionId, final Set<String> unitTypes, final List<DateTime> transitionTimes, final TenantContext tenantContext) {
-        // STEPH_USAGE implement
-        // STEPH_USAGE also dates should be inclusive/exclusive so as to not bill twice for last day
-        return null;
+
+        final InternalTenantContext internalCallContext = internalCallContextFactory.createInternalTenantContext(subscriptionId, ObjectType.SUBSCRIPTION, tenantContext);
+        List<RolledUpUsage> result = new ArrayList<RolledUpUsage>();
+        DateTime prevDate = null;
+        for (DateTime curDate : transitionTimes) {
+            if (prevDate != null) {
+                for (String unitType : unitTypes) {
+                    final RolledUpUsageModelDao usageForSubscription = rolledUpUsageDao.getUsageForSubscription(subscriptionId, prevDate, curDate, unitType, internalCallContext);
+                    result.add(new DefaultRolledUpUsage(usageForSubscription));
+                }
+            }
+            prevDate = curDate;
+        }
+        return result;
     }
 }
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java
index 353a799..9c190c8 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/DefaultRolledUpUsageDao.java
@@ -17,11 +17,15 @@
 package org.killbill.billing.usage.dao;
 
 import java.math.BigDecimal;
+import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 
 import javax.inject.Inject;
 
 import org.joda.time.DateTime;
+import org.killbill.billing.usage.api.RolledUpUsage;
+import org.killbill.billing.util.callcontext.TenantContext;
 import org.skife.jdbi.v2.IDBI;
 
 import org.killbill.billing.callcontext.InternalCallContext;
@@ -46,7 +50,8 @@ public class DefaultRolledUpUsageDao implements RolledUpUsageDao {
     }
 
     @Override
-    public RolledUpUsageModelDao getUsageForSubscription(final UUID subscriptionId, final InternalTenantContext context) {
-        return rolledUpUsageSqlDao.getUsageForSubscription(subscriptionId, context);
+    public RolledUpUsageModelDao getUsageForSubscription(UUID subscriptionId, DateTime startTime, DateTime endTime, String unitType, InternalTenantContext context) {
+        final BigDecimal amount = rolledUpUsageSqlDao.getUsageForSubscription(subscriptionId, startTime.toDate(), endTime.toDate(), unitType, context);
+        return new RolledUpUsageModelDao(subscriptionId, unitType, startTime, endTime, amount != null ? amount : BigDecimal.ZERO);
     }
 }
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java
index e0b90a1..225b3c8 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageDao.java
@@ -17,17 +17,21 @@
 package org.killbill.billing.usage.dao;
 
 import java.math.BigDecimal;
+import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
 
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
+import org.killbill.billing.usage.api.RolledUpUsage;
+import org.killbill.billing.util.callcontext.TenantContext;
 
 public interface RolledUpUsageDao {
 
     void record(UUID subscriptionId, String unitType, DateTime startTime,
                 DateTime endTime, BigDecimal amount, InternalCallContext context);
 
-    RolledUpUsageModelDao getUsageForSubscription(UUID subscriptionId, InternalTenantContext context);
+    RolledUpUsageModelDao getUsageForSubscription(UUID subscriptionId, DateTime startTime, DateTime endTime, String unitType, InternalTenantContext context);
 }
diff --git a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
index 29042ca..4bfdb8a 100644
--- a/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
+++ b/usage/src/main/java/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.java
@@ -16,6 +16,8 @@
 
 package org.killbill.billing.usage.dao;
 
+import java.math.BigDecimal;
+import java.util.Date;
 import java.util.UUID;
 
 import org.skife.jdbi.v2.sqlobject.Bind;
@@ -36,6 +38,9 @@ public interface RolledUpUsageSqlDao {
                        @InternalTenantContextBinder final InternalCallContext context);
 
     @SqlQuery
-    public RolledUpUsageModelDao getUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
+    public BigDecimal getUsageForSubscription(@Bind("subscriptionId") final UUID subscriptionId,
+                                                         @Bind("startTime") final Date startTime,
+                                                         @Bind("endTime") final Date endTime,
+                                                         @Bind("unitType") final String unitType,
                                                          @InternalTenantContextBinder final InternalTenantContext context);
 }
diff --git a/usage/src/main/java/org/killbill/billing/usage/glue/UsageModule.java b/usage/src/main/java/org/killbill/billing/usage/glue/UsageModule.java
index 64755b0..749eba1 100644
--- a/usage/src/main/java/org/killbill/billing/usage/glue/UsageModule.java
+++ b/usage/src/main/java/org/killbill/billing/usage/glue/UsageModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,21 +18,17 @@
 
 package org.killbill.billing.usage.glue;
 
-import org.skife.config.ConfigSource;
-
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.usage.api.UsageUserApi;
 import org.killbill.billing.usage.api.user.DefaultUsageUserApi;
 import org.killbill.billing.usage.dao.DefaultRolledUpUsageDao;
 import org.killbill.billing.usage.dao.RolledUpUsageDao;
+import org.killbill.billing.util.glue.KillBillModule;
 
-import com.google.inject.AbstractModule;
-
-public class UsageModule extends AbstractModule {
-
-    protected final ConfigSource configSource;
+public class UsageModule extends KillBillModule {
 
-    public UsageModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public UsageModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     protected void installRolledUpUsageDao() {
diff --git a/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg b/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
index e028757..431d976 100644
--- a/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
+++ b/usage/src/main/resources/org/killbill/billing/usage/dao/RolledUpUsageSqlDao.sql.stg
@@ -1,6 +1,6 @@
-group RolledUpUsageSqlDao;
+group RolledUpUsageSqlDao: EntitySqlDao;
 
-tableName() ::= "usage"
+tableName() ::= "rolled_up_usage"
 
 tableFields(prefix) ::= <<
   <prefix>id
@@ -43,9 +43,13 @@ values (
 
 getUsageForSubscription() ::= <<
 select
-  <tableFields("t.")>
+  sum(amount)
 from <tableName()> t
 where subscription_id = :subscriptionId
+and start_time >= :startTime
+and end_time \<= :endTime
+and unit_type = :unitType
 <AND_CHECK_TENANT()>
 ;
 >>
+
diff --git a/usage/src/main/resources/org/killbill/billing/usage/ddl.sql b/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
index aa1bd96..2d8edc9 100644
--- a/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
+++ b/usage/src/main/resources/org/killbill/billing/usage/ddl.sql
@@ -6,9 +6,9 @@ CREATE TABLE rolled_up_usage (
     id char(36) NOT NULL,
     subscription_id char(36),
     unit_type varchar(50),
-    start_date date NOT NULL,
-    end_date date,
-    amount numeric(10,10) NOT NULL,
+    start_time datetime NOT NULL,
+    end_time datetime,
+    amount decimal(15,9) NOT NULL,
     created_by varchar(50) NOT NULL,
     created_date datetime NOT NULL,
     account_record_id int(11) unsigned default null,
diff --git a/usage/src/test/java/org/killbill/billing/usage/api/user/MockUsageUserApi.java b/usage/src/test/java/org/killbill/billing/usage/api/user/MockUsageUserApi.java
index 2cac2ff..d3dd47a 100644
--- a/usage/src/test/java/org/killbill/billing/usage/api/user/MockUsageUserApi.java
+++ b/usage/src/test/java/org/killbill/billing/usage/api/user/MockUsageUserApi.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
 package org.killbill.billing.usage.api.user;
 
 import java.math.BigDecimal;
diff --git a/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java b/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java
new file mode 100644
index 0000000..fad80b4
--- /dev/null
+++ b/usage/src/test/java/org/killbill/billing/usage/dao/TestDefaultRolledUpUsageDao.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.usage.dao;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.killbill.billing.usage.UsageTestSuiteWithEmbeddedDB;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestDefaultRolledUpUsageDao extends UsageTestSuiteWithEmbeddedDB {
+
+    @BeforeMethod(groups = "slow")
+    public void setUp() throws Exception {
+    }
+
+    @Test(groups = "slow")
+    public void testSimple() {
+        final UUID subscriptionId = UUID.randomUUID();
+        final String unitType = "foo";
+        final DateTime startDate = new DateTime(2013, 1, 1, 0, 0, DateTimeZone.UTC);
+        final DateTime endDate = new DateTime(2013, 2, 1, 0, 0, DateTimeZone.UTC);
+        final BigDecimal amount1 = BigDecimal.TEN;
+        final BigDecimal amount2 = BigDecimal.TEN;
+
+        rolledUpUsageDao.record(subscriptionId, unitType, startDate, endDate, amount1, internalCallContext);
+        rolledUpUsageDao.record(subscriptionId, unitType, startDate, endDate, amount2, internalCallContext);
+
+        final RolledUpUsageModelDao result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
+        assertEquals(result.getSubscriptionId(), subscriptionId);
+        assertEquals(result.getStartTime().compareTo(startDate), 0);
+        assertEquals(result.getEndTime().compareTo(endDate), 0);
+        assertEquals(result.getUnitType(), unitType);
+        assertEquals(result.getSubscriptionId(), subscriptionId);
+        assertEquals(result.getSubscriptionId(), subscriptionId);
+        assertEquals(result.getAmount().compareTo(amount1.add(amount2)), 0);
+    }
+
+    @Test(groups = "slow")
+    public void testNoEntries() {
+        final UUID subscriptionId = UUID.randomUUID();
+        final String unitType = "foo";
+        final DateTime startDate = new DateTime(2013, 1, 1, 0, 0, DateTimeZone.UTC);
+        final DateTime endDate = new DateTime(2013, 2, 1, 0, 0, DateTimeZone.UTC);
+
+        final RolledUpUsageModelDao result = rolledUpUsageDao.getUsageForSubscription(subscriptionId, startDate, endDate, unitType, internalCallContext);
+        assertEquals(result.getSubscriptionId(), subscriptionId);
+        assertEquals(result.getStartTime().compareTo(startDate), 0);
+        assertEquals(result.getEndTime().compareTo(endDate), 0);
+        assertEquals(result.getUnitType(), unitType);
+        assertEquals(result.getSubscriptionId(), subscriptionId);
+        assertEquals(result.getSubscriptionId(), subscriptionId);
+        assertEquals(result.getAmount().compareTo(BigDecimal.ZERO), 0);
+    }
+
+}
diff --git a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModule.java b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModule.java
index 8ea3749..78a0882 100644
--- a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModule.java
+++ b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,13 @@
 
 package org.killbill.billing.usage.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.usage.api.UsageUserApi;
 import org.killbill.billing.usage.api.user.MockUsageUserApi;
-import org.skife.config.ConfigSource;
 
 public class TestUsageModule extends UsageModule {
 
-    public TestUsageModule(final ConfigSource configSource) {
+    public TestUsageModule(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
diff --git a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleNoDB.java b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleNoDB.java
index c2eee78..c82920a 100644
--- a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleNoDB.java
+++ b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,12 @@
 
 package org.killbill.billing.usage.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestUsageModuleNoDB extends TestUsageModule {
 
-    public TestUsageModuleNoDB(final ConfigSource configSource) {
+    public TestUsageModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -30,6 +31,6 @@ public class TestUsageModuleNoDB extends TestUsageModule {
     public void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestNoDBModule());
+        install(new GuicyKillbillTestNoDBModule(configSource));
     }
 }
diff --git a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java
index 6faae57..4db4fd8 100644
--- a/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java
+++ b/usage/src/test/java/org/killbill/billing/usage/glue/TestUsageModuleWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,12 @@
 
 package org.killbill.billing.usage.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestUsageModuleWithEmbeddedDB extends TestUsageModule {
 
-    public TestUsageModuleWithEmbeddedDB(final ConfigSource configSource) {
+    public TestUsageModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -30,6 +31,6 @@ public class TestUsageModuleWithEmbeddedDB extends TestUsageModule {
     public void configure() {
         super.configure();
 
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
     }
 }
diff --git a/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteWithEmbeddedDB.java b/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteWithEmbeddedDB.java
index 9126e99..9ab15ff 100644
--- a/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteWithEmbeddedDB.java
+++ b/usage/src/test/java/org/killbill/billing/usage/UsageTestSuiteWithEmbeddedDB.java
@@ -16,6 +16,9 @@
 
 package org.killbill.billing.usage;
 
+import javax.inject.Inject;
+
+import org.killbill.billing.usage.dao.RolledUpUsageDao;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
@@ -28,6 +31,9 @@ import com.google.inject.Injector;
 
 public class UsageTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteWithEmbeddedDB {
 
+    @Inject
+    protected RolledUpUsageDao rolledUpUsageDao;
+
     @BeforeClass(groups = "slow")
     protected void beforeClass() throws Exception {
         final Injector injector = Guice.createInjector(new TestUsageModuleWithEmbeddedDB(configSource));

util/pom.xml 46(+38 -8)

diff --git a/util/pom.xml b/util/pom.xml
index de245df..71477c8 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- ~ 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 
+<!-- ~ 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. -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>killbill</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.9.3-SNAPSHOT</version>
+        <version>0.11.10-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>
@@ -129,6 +129,24 @@
             <artifactId>killbill-internal-api</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-osgi-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing.plugin</groupId>
+            <artifactId>killbill-plugin-api-invoice</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.kill-bill.billing.plugin</groupId>
             <artifactId>killbill-plugin-api-notification</artifactId>
         </dependency>
@@ -148,6 +166,10 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-concurrent</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-embeddeddb-common</artifactId>
             <scope>compile</scope>
         </dependency>
@@ -163,6 +185,10 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-jdbi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.commons</groupId>
             <artifactId>killbill-locker</artifactId>
         </dependency>
         <dependency>
@@ -176,6 +202,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.kill-bill.commons</groupId>
+            <artifactId>killbill-xmlloader</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-all</artifactId>
             <scope>test</scope>
diff --git a/util/src/main/java/org/killbill/billing/util/audit/DefaultAccountAuditLogs.java b/util/src/main/java/org/killbill/billing/util/audit/DefaultAccountAuditLogs.java
index 85bd2da..1871609 100644
--- a/util/src/main/java/org/killbill/billing/util/audit/DefaultAccountAuditLogs.java
+++ b/util/src/main/java/org/killbill/billing/util/audit/DefaultAccountAuditLogs.java
@@ -91,18 +91,13 @@ public class DefaultAccountAuditLogs implements AccountAuditLogs {
     }
 
     @Override
-    public List<AuditLog> getAuditLogsForPaymentMethod(final UUID paymentMethodId) {
-        return getAuditLogs(ObjectType.PAYMENT_METHOD).getAuditLogs(paymentMethodId);
+    public List<AuditLog> getAuditLogsForPaymentTransaction(final UUID paymentTransactionId) {
+        return getAuditLogs(ObjectType.TRANSACTION).getAuditLogs(paymentTransactionId);
     }
 
     @Override
-    public List<AuditLog> getAuditLogsForRefund(final UUID refundId) {
-        return getAuditLogs(ObjectType.REFUND).getAuditLogs(refundId);
-    }
-
-    @Override
-    public List<AuditLog> getAuditLogsForChargeback(final UUID chargebackId) {
-        return getAuditLogs(ObjectType.INVOICE_PAYMENT).getAuditLogs(chargebackId);
+    public List<AuditLog> getAuditLogsForPaymentMethod(final UUID paymentMethodId) {
+        return getAuditLogs(ObjectType.PAYMENT_METHOD).getAuditLogs(paymentMethodId);
     }
 
     @Override
diff --git a/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java
index 906f452..6c7e01e 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/AccountRecordIdCacheLoader.java
@@ -30,7 +30,7 @@ import org.killbill.billing.util.dao.NonEntityDao;
 import net.sf.ehcache.loader.CacheLoader;
 
 @Singleton
-public class AccountRecordIdCacheLoader extends BaseCacheLoader implements CacheLoader {
+public class AccountRecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
 
     @Inject
     public AccountRecordIdCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
@@ -43,19 +43,7 @@ public class AccountRecordIdCacheLoader extends BaseCacheLoader implements Cache
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
-        }
-
-        final String objectId = (String) key;
-        final ObjectType objectType = ((CacheLoaderArgument) argument).getObjectType();
-
-        return nonEntityDao.retrieveAccountRecordIdFromObject(UUID.fromString(objectId), objectType, null);
+    protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType) {
+        return  nonEntityDao.retrieveAccountRecordIdFromObject(UUID.fromString(rawKey), objectType, null);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java
new file mode 100644
index 0000000..3ff9822
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/BaseIdCacheLoader.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.cache;
+
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.skife.jdbi.v2.IDBI;
+
+public abstract class BaseIdCacheLoader extends BaseCacheLoader {
+
+    protected BaseIdCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
+        super(dbi, nonEntityDao);
+    }
+
+    @Override
+    public abstract CacheType getCacheType();
+
+
+    protected abstract Object doRetrieveOperation(final String rawKey, final ObjectType objectType);
+
+    @Override
+    public Object load(final Object key, final Object argument) {
+        checkCacheLoaderStatus();
+
+        if (!(key instanceof String)) {
+            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
+        }
+        if (!(argument instanceof CacheLoaderArgument)) {
+            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
+        }
+
+        final String rawKey;
+        if (getCacheType().isKeyPrefixedWithTableName()) {
+            String [] parts = ((String) key).split(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
+            rawKey = parts[1];
+        } else {
+            rawKey = (String) key;
+        }
+        final ObjectType objectType = ((CacheLoaderArgument) argument).getObjectType();
+        return doRetrieveOperation(rawKey, objectType);
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
index d9ffbe2..6ede4f6 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
@@ -28,6 +28,7 @@ public @interface Cachable {
     public final String RECORD_ID_CACHE_NAME = "record-id";
     public final String ACCOUNT_RECORD_ID_CACHE_NAME = "account-record-id";
     public final String TENANT_RECORD_ID_CACHE_NAME = "tenant-record-id";
+    public final String OBJECT_ID_CACHE_NAME = "object-id";
     public final String AUDIT_LOG_CACHE_NAME = "audit-log";
     public final String AUDIT_LOG_VIA_HISTORY_CACHE_NAME = "audit-log-via-history";
 
@@ -35,30 +36,37 @@ public @interface Cachable {
 
     public enum CacheType {
         /* Mapping from object 'id (UUID)' -> object 'recordId (Long' */
-        RECORD_ID(RECORD_ID_CACHE_NAME),
+        RECORD_ID(RECORD_ID_CACHE_NAME, false),
 
         /* Mapping from object 'id (UUID)' -> matching account object 'accountRecordId (Long)' */
-        ACCOUNT_RECORD_ID(ACCOUNT_RECORD_ID_CACHE_NAME),
+        ACCOUNT_RECORD_ID(ACCOUNT_RECORD_ID_CACHE_NAME, false),
 
         /* Mapping from object 'id (UUID)' -> matching object 'tenantRecordId (Long)' */
-        TENANT_RECORD_ID(TENANT_RECORD_ID_CACHE_NAME),
+        TENANT_RECORD_ID(TENANT_RECORD_ID_CACHE_NAME, false),
+
+        /* Mapping from object 'recordId (Long') -> object 'id (UUID)'  */
+        OBJECT_ID(OBJECT_ID_CACHE_NAME, true),
 
         /* Mapping from object 'tableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
-        AUDIT_LOG(AUDIT_LOG_CACHE_NAME),
+        AUDIT_LOG(AUDIT_LOG_CACHE_NAME, true),
 
         /* Mapping from object 'tableName::historyTableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
-        AUDIT_LOG_VIA_HISTORY(AUDIT_LOG_VIA_HISTORY_CACHE_NAME);
+        AUDIT_LOG_VIA_HISTORY(AUDIT_LOG_VIA_HISTORY_CACHE_NAME, true);
 
         private final String cacheName;
+        private final boolean isKeyPrefixedWithTableName;
 
-        CacheType(final String cacheName) {
+        CacheType(final String cacheName, final boolean isKeyPrefixedWithTableName) {
             this.cacheName = cacheName;
+            this.isKeyPrefixedWithTableName = isKeyPrefixedWithTableName;
         }
 
         public String getCacheName() {
             return cacheName;
         }
 
+        public boolean isKeyPrefixedWithTableName() { return isKeyPrefixedWithTableName; }
+
         public static CacheType findByName(final String input) {
             for (final CacheType cacheType : CacheType.values()) {
                 if (cacheType.cacheName.equals(input)) {
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
index 1b357ea..5743f65 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheController.java
@@ -16,8 +16,12 @@
 
 package org.killbill.billing.util.cache;
 
+import org.killbill.billing.util.cache.Cachable.CacheType;
+
 public interface CacheController<K, V> {
 
+    public void add(K key, V value);
+
     public V get(K key, CacheLoaderArgument objectType);
 
     public boolean remove(K key);
@@ -25,4 +29,6 @@ public interface CacheController<K, V> {
     public int size();
 
     void removeAll();
+
+    CacheType getCacheType();
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java
index 11c44e3..bf2f6b1 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcher.java
@@ -26,6 +26,8 @@ import org.killbill.billing.util.cache.Cachable.CacheType;
 // Kill Bill generic cache dispatcher
 public class CacheControllerDispatcher {
 
+    public static final String CACHE_KEY_SEPARATOR = "::";
+
     private final Map<CacheType, CacheController<Object, Object>> caches;
 
     @Inject
diff --git a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
index e216033..c41cddb 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/CacheControllerDispatcherProvider.java
@@ -29,6 +29,7 @@ import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
 import net.sf.ehcache.Cache;
 import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
 import net.sf.ehcache.loader.CacheLoader;
 
 // Build the abstraction layer between EhCache and Kill Bill
@@ -47,25 +48,23 @@ public class CacheControllerDispatcherProvider implements Provider<CacheControll
         for (final String cacheName : cacheManager.getCacheNames()) {
             final CacheType cacheType = CacheType.findByName(cacheName);
 
-            final Collection<EhCacheBasedCacheController<Object, Object>> cacheControllersForCacheName = getCacheControllersForCacheName(cacheName);
+            final Collection<EhCacheBasedCacheController<Object, Object>> cacheControllersForCacheName = getCacheControllersForCacheName(cacheName, cacheType);
             // EhCache supports multiple cache loaders per type, but not Kill Bill - take the first one
             if (cacheControllersForCacheName.size() > 0) {
                 final EhCacheBasedCacheController<Object, Object> ehCacheBasedCacheController = cacheControllersForCacheName.iterator().next();
                 cacheControllers.put(cacheType, ehCacheBasedCacheController);
             }
         }
-
         return new CacheControllerDispatcher(cacheControllers);
     }
 
-    public Collection<EhCacheBasedCacheController<Object, Object>> getCacheControllersForCacheName(final String name) {
+    private Collection<EhCacheBasedCacheController<Object, Object>> getCacheControllersForCacheName(final String name, final CacheType cacheType) {
         final Cache cache = cacheManager.getCache(name);
-
         // The CacheLoaders were registered in EhCacheCacheManagerProvider
         return Collections2.transform(cache.getRegisteredCacheLoaders(), new Function<CacheLoader, EhCacheBasedCacheController<Object, Object>>() {
             @Override
             public EhCacheBasedCacheController<Object, Object> apply(final CacheLoader input) {
-                return new EhCacheBasedCacheController<Object, Object>(cache);
+                return new EhCacheBasedCacheController<Object, Object>(cache, cacheType);
             }
         });
     }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
index d12b5d6..eb20466 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheBasedCacheController.java
@@ -16,15 +16,24 @@
 
 package org.killbill.billing.util.cache;
 
+import org.killbill.billing.util.cache.Cachable.CacheType;
+
 import net.sf.ehcache.Cache;
 import net.sf.ehcache.Element;
 
 public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> {
 
     private final Cache cache;
+    private final CacheType cacheType;
 
-    public EhCacheBasedCacheController(final Cache cache) {
+    public EhCacheBasedCacheController(final Cache cache, final CacheType cacheType) {
         this.cache = cache;
+        this.cacheType = cacheType;
+    }
+
+    @Override
+    public void add(final K key, final V value) {
+        cache.putIfAbsent(new Element(key, value));
     }
 
     @Override
@@ -50,4 +59,9 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> 
     public void removeAll() {
         cache.removeAll();
     }
+
+    @Override
+    public CacheType getCacheType() {
+        return cacheType;
+    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
index a4f50f9..c8d7578 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/EhCacheCacheManagerProvider.java
@@ -17,6 +17,8 @@
 package org.killbill.billing.util.cache;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
 import java.util.Collection;
 import java.util.LinkedList;
 
@@ -24,6 +26,7 @@ import javax.inject.Inject;
 import javax.inject.Provider;
 
 import org.killbill.billing.util.config.CacheConfig;
+import org.killbill.xmlloader.UriAccessor;
 
 import net.sf.ehcache.Cache;
 import net.sf.ehcache.CacheManager;
@@ -40,12 +43,14 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
                                        final RecordIdCacheLoader recordIdCacheLoader,
                                        final AccountRecordIdCacheLoader accountRecordIdCacheLoader,
                                        final TenantRecordIdCacheLoader tenantRecordIdCacheLoader,
+                                       final ObjectIdCacheLoader objectIdCacheLoader,
                                        final AuditLogCacheLoader auditLogCacheLoader,
                                        final AuditLogViaHistoryCacheLoader auditLogViaHistoryCacheLoader) {
         this.cacheConfig = cacheConfig;
         cacheLoaders.add(recordIdCacheLoader);
         cacheLoaders.add(accountRecordIdCacheLoader);
         cacheLoaders.add(tenantRecordIdCacheLoader);
+        cacheLoaders.add(objectIdCacheLoader);
         cacheLoaders.add(auditLogCacheLoader);
         cacheLoaders.add(auditLogViaHistoryCacheLoader);
     }
@@ -54,9 +59,12 @@ public class EhCacheCacheManagerProvider implements Provider<CacheManager> {
     public CacheManager get() {
         final CacheManager cacheManager;
         try {
-            cacheManager = CacheManager.create(EhCacheCacheManagerProvider.class.getResource(cacheConfig.getCacheConfigLocation()).openStream());
+            final InputStream inputStream = UriAccessor.accessUri(cacheConfig.getCacheConfigLocation());
+            cacheManager = CacheManager.create(inputStream);
         } catch (IOException e) {
             throw new RuntimeException(e);
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
         }
 
         for (final BaseCacheLoader cacheLoader : cacheLoaders) {
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java
new file mode 100644
index 0000000..f770184
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/ObjectIdCacheLoader.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.cache;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.dao.NonEntityDao;
+import org.skife.jdbi.v2.IDBI;
+
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.loader.CacheLoader;
+
+@Singleton
+public class ObjectIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
+
+    @Inject
+    public ObjectIdCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
+        super(dbi, nonEntityDao);
+    }
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.OBJECT_ID;
+    }
+
+    @Override
+    protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType) {
+        final Long recordId = Long.valueOf(rawKey);
+        return nonEntityDao.retrieveIdFromObject(recordId, objectType, null);
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java
index a2fb825..8398911 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/RecordIdCacheLoader.java
@@ -31,7 +31,7 @@ import net.sf.ehcache.CacheException;
 import net.sf.ehcache.loader.CacheLoader;
 
 @Singleton
-public class RecordIdCacheLoader extends BaseCacheLoader implements CacheLoader {
+public class RecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
 
     @Inject
     public RecordIdCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
@@ -44,19 +44,7 @@ public class RecordIdCacheLoader extends BaseCacheLoader implements CacheLoader 
     }
 
     @Override
-    public Object load(final Object key, final Object argument) throws CacheException {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
-        }
-
-        final String objectId = (String) key;
-        final ObjectType objectType = ((CacheLoaderArgument) argument).getObjectType();
-
-        return nonEntityDao.retrieveRecordIdFromObject(UUID.fromString(objectId), objectType, null);
+    protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType) {
+        return nonEntityDao.retrieveRecordIdFromObject(UUID.fromString(rawKey), objectType, null);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java b/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java
index 753a7fb..008fc52 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/TenantRecordIdCacheLoader.java
@@ -30,7 +30,7 @@ import org.killbill.billing.util.dao.NonEntityDao;
 import net.sf.ehcache.loader.CacheLoader;
 
 @Singleton
-public class TenantRecordIdCacheLoader extends BaseCacheLoader implements CacheLoader {
+public class TenantRecordIdCacheLoader extends BaseIdCacheLoader implements CacheLoader {
 
     @Inject
     public TenantRecordIdCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
@@ -43,19 +43,7 @@ public class TenantRecordIdCacheLoader extends BaseCacheLoader implements CacheL
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
-        checkCacheLoaderStatus();
-
-        if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
-        }
-        if (!(argument instanceof CacheLoaderArgument)) {
-            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
-        }
-
-        final String objectId = (String) key;
-        final ObjectType objectType = ((CacheLoaderArgument) argument).getObjectType();
-
-        return nonEntityDao.retrieveTenantRecordIdFromObject(UUID.fromString(objectId), objectType, null);
+    protected Object doRetrieveOperation(final String rawKey, final ObjectType objectType) {
+        return nonEntityDao.retrieveTenantRecordIdFromObject(UUID.fromString(rawKey), objectType, null);
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
index 6aa8259..71a1fe9 100644
--- a/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
+++ b/util/src/main/java/org/killbill/billing/util/callcontext/InternalCallContextFactory.java
@@ -22,14 +22,13 @@ import javax.annotation.Nullable;
 import javax.inject.Inject;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.ObjectType;
-import org.killbill.clock.Clock;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.clock.Clock;
 
 import com.google.common.base.Objects;
 
@@ -88,6 +87,12 @@ public class InternalCallContextFactory {
         return new InternalTenantContext(tenantRecordId, accountRecordId);
     }
 
+    public InternalTenantContext createInternalTenantContext(final UUID accountId, final UUID objectId, final ObjectType objectType) {
+        final Long tenantRecordId = getTenantRecordId(objectId, objectType);
+        final Long accountRecordId = getAccountRecordId(accountId, ObjectType.ACCOUNT);
+        return new InternalTenantContext(tenantRecordId, accountRecordId);
+    }
+
     /**
      * Crate an internal tenant callcontext from a tenant callcontext, and retrieving the account_record_id from another table
      *
@@ -250,6 +255,10 @@ public class InternalCallContextFactory {
         return nonEntityDao.retrieveAccountRecordIdFromObject(objectId, objectType, cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID));
     }
 
+    private Long getTenantRecordId(final UUID objectId, final ObjectType objectType) {
+        return nonEntityDao.retrieveTenantRecordIdFromObject(objectId, objectType, cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID));
+    }
+
     private Long getTenantRecordId(final TenantContext context) {
         // Default to single default tenant (e.g. single tenant mode)
         // TODO Extract this convention (e.g. BusinessAnalyticsBase needs to know about it)
diff --git a/util/src/main/java/org/killbill/billing/util/config/CacheConfig.java b/util/src/main/java/org/killbill/billing/util/config/CacheConfig.java
index b3ee5a9..7e7c9fd 100644
--- a/util/src/main/java/org/killbill/billing/util/config/CacheConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/CacheConfig.java
@@ -23,7 +23,7 @@ import org.skife.config.Description;
 public interface CacheConfig extends KillbillConfig {
 
     @Config("org.killbill.cache.config.location")
-    @Default("/ehcache.xml")
+    @Default("ehcache.xml")
     @Description("Path to Ehcache XML configuration")
     public String getCacheConfigLocation();
 
diff --git a/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java b/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java
index 1ab5c35..6100e6d 100644
--- a/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/PaymentConfig.java
@@ -31,6 +31,12 @@ public interface PaymentConfig extends KillbillConfig {
     @Description("Default payment provider to use")
     public String getDefaultPaymentProvider();
 
+    // STEPH_RETRY unique property (does not match payment one)
+    @Config("org.killbill.payment.retry.provider.default")
+    @Default("__external_retry__")
+    @Description("Default retry provider to use")
+    public String getDefaultRetryProvider();
+
     @Config("org.killbill.payment.retry.days")
     @Default("8,8,8")
     @Description("Interval in days between payment retries")
@@ -50,7 +56,7 @@ public interface PaymentConfig extends KillbillConfig {
     public int getPluginFailureRetryMaxAttempts();
 
     @Config("org.killbill.payment.plugin.timeout")
-    @Default("90s")
+    @Default("30s")
     @Description("Timeout for each payment attempt")
     public TimeSpan getPaymentPluginTimeout();
 
@@ -59,6 +65,21 @@ public interface PaymentConfig extends KillbillConfig {
     @Description("Number of threads for plugin executor dispatcher")
     public int getPaymentPluginThreadNb();
 
+    @Config("org.killbill.payment.janitor.pending")
+    @Default("12h")
+    @Description("Delay after which pending transactions should be marked as failed")
+    public TimeSpan getJanitorPendingCleanupTime();
+
+    @Config("org.killbill.payment.janitor.attempts")
+    @Default("15m")
+    @Description("Delay after which incomplete  attempts should be completed")
+    public TimeSpan getJanitorAttemptCompletionTime();
+
+    @Config("org.killbill.payment.janitor.rate")
+    @Default("1h")
+    @Description("Rate at which janitor tasks are scheduled")
+    public TimeSpan getJanitorRunningRate();
+
     @Config("org.killbill.payment.off")
     @Default("false")
     @Description("Whether the payment subsystem is off")
diff --git a/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldCreationEvent.java b/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldCreationEvent.java
index 97e131d..a2572b6 100644
--- a/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldCreationEvent.java
+++ b/util/src/main/java/org/killbill/billing/util/customfield/api/DefaultCustomFieldCreationEvent.java
@@ -67,6 +67,16 @@ public class DefaultCustomFieldCreationEvent extends BusEventBase implements Cus
     }
 
     @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("DefaultCustomFieldCreationEvent{");
+        sb.append("customFieldId=").append(customFieldId);
+        sb.append(", objectId=").append(objectId);
+        sb.append(", objectType=").append(objectType);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
     public boolean equals(final Object o) {
         if (this == o) {
             return true;
diff --git a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java
index 3987b3e..7a96c92 100644
--- a/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/customfield/dao/CustomFieldModelDao.java
@@ -25,8 +25,9 @@ import org.killbill.billing.util.customfield.CustomField;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class CustomFieldModelDao extends EntityBase implements EntityModelDao<CustomField> {
+public class CustomFieldModelDao extends EntityModelDaoBase implements EntityModelDao<CustomField> {
 
     private String fieldName;
     private String fieldValue;
diff --git a/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java b/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java
index 785ef8b..e68e0d1 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/DefaultNonEntityDao.java
@@ -21,6 +21,12 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.commons.profiling.Profiling;
+import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
+import org.killbill.commons.profiling.ProfilingFeature.ProfilingFeatureType;
 import org.skife.jdbi.v2.IDBI;
 
 import org.killbill.billing.ObjectType;
@@ -30,33 +36,32 @@ import org.killbill.billing.util.cache.CacheLoaderArgument;
 public class DefaultNonEntityDao implements NonEntityDao {
 
     private final NonEntitySqlDao nonEntitySqlDao;
-    private final WithCaching containedCall;
-
+    private final WithCaching<UUID, Long> withCachingObjectId;
+    private final WithCaching<Long, UUID> withCachingRecordId;
 
     @Inject
     public DefaultNonEntityDao(final IDBI dbi) {
         this.nonEntitySqlDao = dbi.onDemand(NonEntitySqlDao.class);
-        this.containedCall = new WithCaching();
+        this.withCachingObjectId = new WithCaching<UUID, Long>();
+        this.withCachingRecordId = new WithCaching<Long, UUID>();
     }
 
 
     public Long retrieveRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
-
-        return containedCall.withCaching(new OperationRetrieval<Long>() {
+        final TableName tableName = TableName.fromObjectType(objectType);
+        return withCachingObjectId.withCaching(new OperationRetrieval<UUID, Long>() {
             @Override
-            public Long doRetrieve(final UUID objectId, final ObjectType objectType) {
-                final TableName tableName = TableName.fromObjectType(objectType);
+            public Long doRetrieve(final UUID objectOrRecordId, final ObjectType objectType) {
                 return nonEntitySqlDao.getRecordIdFromObject(objectId.toString(), tableName.getTableName());
             }
-        }, objectId, objectType, cache);
-
+        }, objectId, objectType, tableName, cache);
     }
 
     public Long retrieveAccountRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
-        return containedCall.withCaching(new OperationRetrieval<Long>() {
+        final TableName tableName = TableName.fromObjectType(objectType);
+        return withCachingObjectId.withCaching(new OperationRetrieval<UUID, Long>() {
             @Override
             public Long doRetrieve(final UUID objectId, final ObjectType objectType) {
-                final TableName tableName = TableName.fromObjectType(objectType);
                 switch (tableName) {
                     case TENANT:
                     case TAG_DEFINITIONS:
@@ -70,29 +75,42 @@ public class DefaultNonEntityDao implements NonEntityDao {
                         return nonEntitySqlDao.getAccountRecordIdFromObjectOtherThanAccount(objectId.toString(), tableName.getTableName());
                 }
             }
-        }, objectId, objectType, cache);
+        }, objectId, objectType, tableName, cache);
     }
 
     public Long retrieveTenantRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
-
-
-        return containedCall.withCaching(new OperationRetrieval<Long>() {
+        final TableName tableName = TableName.fromObjectType(objectType);
+        return withCachingObjectId.withCaching(new OperationRetrieval<UUID, Long>() {
             @Override
             public Long doRetrieve(final UUID objectId, final ObjectType objectType) {
-                final TableName tableName = TableName.fromObjectType(objectType);
                 switch (tableName) {
                     case TENANT:
-                        return nonEntitySqlDao.getTenantRecordIdFromTenant(objectId.toString());
+                        return objectId == null ? 0L : nonEntitySqlDao.getTenantRecordIdFromTenant(objectId.toString());
 
                     default:
                         return nonEntitySqlDao.getTenantRecordIdFromObjectOtherThanTenant(objectId.toString(), tableName.getTableName());
                 }
 
             }
-        }, objectId, objectType, cache);
+        }, objectId, objectType, tableName, cache);
     }
 
     @Override
+    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
+        if (objectType == ObjectType.TENANT && recordId == InternalCallContextFactory.INTERNAL_TENANT_RECORD_ID) {
+            return null;
+        }
+        final TableName tableName = TableName.fromObjectType(objectType);
+        return withCachingRecordId.withCaching(new OperationRetrieval<Long, UUID>() {
+            @Override
+            public UUID doRetrieve(final Long objectOrRecordId, final ObjectType objectType) {
+                return nonEntitySqlDao.getIdFromObject(recordId, tableName.getTableName());
+            }
+        }, recordId, objectType, tableName, cache);
+    }
+
+
+    @Override
     public Long retrieveLastHistoryRecordIdFromTransaction(@Nullable final Long targetRecordId, final TableName tableName, final NonEntitySqlDao transactional) {
         // There is no caching here because the value returned changes as we add more history records, and so we would need some cache invalidation
         return transactional.getLastHistoryRecordId(targetRecordId, tableName.getTableName());
@@ -103,28 +121,39 @@ public class DefaultNonEntityDao implements NonEntityDao {
         return nonEntitySqlDao.getHistoryTargetRecordId(recordId, tableName.getTableName());
     }
 
-    @Override
-    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType) {
-        final TableName tableName = TableName.fromObjectType(objectType);
-        return nonEntitySqlDao.getIdFromObject(recordId, tableName.getTableName());
-    }
 
-    private interface OperationRetrieval<T> {
-        public T doRetrieve(final UUID objectId, final ObjectType objectType);
+    private interface OperationRetrieval<TypeIn, TypeOut> {
+        public TypeOut doRetrieve(final TypeIn objectOrRecordId, final ObjectType objectType);
     }
 
     // 'cache' will be null for the CacheLoader classes -- or if cache is not configured.
-    private class WithCaching {
+    private class WithCaching<TypeIn, TypeOut> {
+
+        private TypeOut withCaching(final OperationRetrieval<TypeIn, TypeOut> op, @Nullable final TypeIn objectOrRecordId, final ObjectType objectType, final TableName tableName, @Nullable final CacheController<Object, Object> cache) {
 
-        private Long withCaching(final OperationRetrieval<Long> op, @Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
-            if (objectId == null) {
+            final Profiling<TypeOut> prof = new Profiling<TypeOut>();
+            if (objectOrRecordId == null) {
                 return null;
             }
-
             if (cache != null) {
-                return (Long) cache.get(objectId.toString(), new CacheLoaderArgument(objectType));
+                final String key = (cache.getCacheType().isKeyPrefixedWithTableName()) ?
+                                   tableName + CacheControllerDispatcher.CACHE_KEY_SEPARATOR + objectOrRecordId.toString() :
+                                   objectOrRecordId.toString();
+                return (TypeOut) cache.get(key, new CacheLoaderArgument(objectType));
+            }
+            final TypeOut result;
+            try {
+                result = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS,  "NonEntityDao (type = " +  objectType + ") cache miss", new WithProfilingCallback<TypeOut>() {
+                    @Override
+                    public <ExceptionType extends Throwable> TypeOut execute() throws ExceptionType {
+                        return op.doRetrieve(objectOrRecordId, objectType);
+                    }
+                });
+                return result;
+            } catch (Throwable throwable) {
+                // This is only because WithProfilingCallback throws a Throwable...
+                throw new RuntimeException(throwable);
             }
-            return op.doRetrieve(objectId, objectType);
         }
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/dao/EntityAudit.java b/util/src/main/java/org/killbill/billing/util/dao/EntityAudit.java
index 0cd4568..e48775f 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/EntityAudit.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/EntityAudit.java
@@ -22,8 +22,9 @@ import org.joda.time.DateTime;
 
 import org.killbill.billing.util.audit.ChangeType;
 import org.killbill.billing.entity.EntityBase;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 
-public class EntityAudit extends EntityBase {
+public class EntityAudit extends EntityModelDaoBase {
     
     private final TableName tableName;
     private final Long targetRecordId;
diff --git a/util/src/main/java/org/killbill/billing/util/dao/LowerToCamelBeanMapper.java b/util/src/main/java/org/killbill/billing/util/dao/LowerToCamelBeanMapper.java
index 6616de8..e4079cd 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/LowerToCamelBeanMapper.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/LowerToCamelBeanMapper.java
@@ -24,6 +24,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
+import java.sql.Blob;
 import java.sql.Date;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
@@ -56,7 +57,7 @@ public class LowerToCamelBeanMapper<T> implements ResultSetMapper<T> {
             for (final PropertyDescriptor descriptor : info.getPropertyDescriptors()) {
                 properties.put(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, descriptor.getName()).toLowerCase(), descriptor);
             }
-        } catch (IntrospectionException e) {
+        } catch (final IntrospectionException e) {
             throw new IllegalArgumentException(e);
         }
     }
@@ -65,7 +66,7 @@ public class LowerToCamelBeanMapper<T> implements ResultSetMapper<T> {
         final T bean;
         try {
             bean = type.newInstance();
-        } catch (Exception e) {
+        } catch (final Exception e) {
             throw new IllegalArgumentException(String.format("A bean, %s, was mapped " +
                                                              "which was not instantiable", type.getName()),
                                                e);
@@ -124,6 +125,12 @@ public class LowerToCamelBeanMapper<T> implements ResultSetMapper<T> {
                     value = rs.getObject(i);
                 }
 
+                // For h2, transform a JdbcBlob into a byte[]
+                if (value instanceof Blob) {
+                    final Blob blob = (Blob) value;
+                    value = blob.getBytes(0, (int) blob.length());
+                }
+
                 if (rs.wasNull() && !type.isPrimitive()) {
                     value = null;
                 }
@@ -138,16 +145,16 @@ public class LowerToCamelBeanMapper<T> implements ResultSetMapper<T> {
                         field.setAccessible(true); // Often private...
                         field.set(bean, value);
                     }
-                } catch (NoSuchFieldException e) {
+                } catch (final NoSuchFieldException e) {
                     throw new IllegalArgumentException(String.format("Unable to find field for " +
                                                                      "property, %s", name), e);
-                } catch (IllegalAccessException e) {
+                } catch (final IllegalAccessException e) {
                     throw new IllegalArgumentException(String.format("Unable to access setter for " +
                                                                      "property, %s", name), e);
-                } catch (InvocationTargetException e) {
+                } catch (final InvocationTargetException e) {
                     throw new IllegalArgumentException(String.format("Invocation target exception trying to " +
                                                                      "invoker setter for the %s property", name), e);
-                } catch (NullPointerException e) {
+                } catch (final NullPointerException e) {
                     throw new IllegalArgumentException(String.format("No appropriate method to " +
                                                                      "write value %s ", value.toString()), e);
                 }
@@ -160,7 +167,7 @@ public class LowerToCamelBeanMapper<T> implements ResultSetMapper<T> {
     private static Field getField(final Class clazz, final String fieldName) throws NoSuchFieldException {
         try {
             return clazz.getDeclaredField(fieldName);
-        } catch (NoSuchFieldException e) {
+        } catch (final NoSuchFieldException e) {
             // Go up in the hierarchy
             final Class superClass = clazz.getSuperclass();
             if (superClass == null) {
diff --git a/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java b/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java
index dcfee7a..e3dec90 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/NonEntityDao.java
@@ -26,21 +26,17 @@ import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 
 public interface NonEntityDao {
 
-    //
-    // TODO should we check for InternalCallContext?
-    // That seems difficult because those APIs are called when creating a callcontext or from the cache loaders which also dpn't know anything about callcontext
-    //
     public Long retrieveRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
 
     public Long retrieveAccountRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
 
     public Long retrieveTenantRecordIdFromObject(final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
 
+    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache);
+
     // This retrieves from the history table the latest record for which targetId matches the one we are passing
     public Long retrieveLastHistoryRecordIdFromTransaction(final Long targetRecordId, final TableName tableName, final NonEntitySqlDao transactional);
 
     // This is the reverse from retrieveLastHistoryRecordIdFromTransaction; this retrieves the record_id of the object matching a given history row
     public Long retrieveHistoryTargetRecordId(final Long recordId, final TableName tableName);
-
-    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType);
 }
diff --git a/util/src/main/java/org/killbill/billing/util/dao/TableName.java b/util/src/main/java/org/killbill/billing/util/dao/TableName.java
index 9157f35..bb75a51 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/TableName.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/TableName.java
@@ -41,6 +41,8 @@ public enum TableName {
     PAYMENTS("payments", ObjectType.PAYMENT, PAYMENT_HISTORY),
     PAYMENT_METHOD_HISTORY("payment_method_history"),
     PAYMENT_METHODS("payment_methods", ObjectType.PAYMENT_METHOD, PAYMENT_METHOD_HISTORY),
+    TRANSACTION_HISTORY("payment_transaction_history"),
+    TRANSACTIONS("payment_transactions", ObjectType.TRANSACTION, TRANSACTION_HISTORY),
     SUBSCRIPTIONS("subscriptions", ObjectType.SUBSCRIPTION),
     SUBSCRIPTION_EVENTS("subscription_events", ObjectType.SUBSCRIPTION_EVENT),
     REFUND_HISTORY("refund_history"),
diff --git a/util/src/main/java/org/killbill/billing/util/email/EmailModule.java b/util/src/main/java/org/killbill/billing/util/email/EmailModule.java
index 60facd3..3d162af 100644
--- a/util/src/main/java/org/killbill/billing/util/email/EmailModule.java
+++ b/util/src/main/java/org/killbill/billing/util/email/EmailModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,21 +18,18 @@
 
 package org.killbill.billing.util.email;
 
-import org.skife.config.ConfigSource;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.AbstractModule;
+public class EmailModule extends KillBillModule {
 
-public class EmailModule extends AbstractModule {
-
-    protected final ConfigSource configSource;
-
-    public EmailModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public EmailModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     protected void installEmailConfig() {
-        final EmailConfig config = new ConfigurationObjectFactory(configSource).build(EmailConfig.class);
+        final EmailConfig config = new ConfigurationObjectFactory(skifeConfigSource).build(EmailConfig.class);
         bind(EmailConfig.class).toInstance(config);
     }
 
diff --git a/util/src/main/java/org/killbill/billing/util/email/templates/MustacheTemplateEngine.java b/util/src/main/java/org/killbill/billing/util/email/templates/MustacheTemplateEngine.java
index 3f785e5..10960b3 100644
--- a/util/src/main/java/org/killbill/billing/util/email/templates/MustacheTemplateEngine.java
+++ b/util/src/main/java/org/killbill/billing/util/email/templates/MustacheTemplateEngine.java
@@ -20,9 +20,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URISyntaxException;
 import java.util.Map;
-
-import org.killbill.billing.util.config.catalog.UriAccessor;
 import org.killbill.billing.util.io.IOUtils;
+import org.killbill.xmlloader.UriAccessor;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.samskivert.mustache.Mustache;
diff --git a/util/src/main/java/org/killbill/billing/util/email/templates/TemplateModule.java b/util/src/main/java/org/killbill/billing/util/email/templates/TemplateModule.java
index 96c40d3..1fc04ae 100644
--- a/util/src/main/java/org/killbill/billing/util/email/templates/TemplateModule.java
+++ b/util/src/main/java/org/killbill/billing/util/email/templates/TemplateModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,9 +18,14 @@
 
 package org.killbill.billing.util.email.templates;
 
-import com.google.inject.AbstractModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 
-public class TemplateModule extends AbstractModule {
+public class TemplateModule extends KillBillModule {
+
+    public TemplateModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDao.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDao.java
index fff329f..1880644 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDao.java
@@ -28,6 +28,12 @@ import org.killbill.billing.util.entity.Entity;
 @SuppressWarnings("UnusedDeclaration")
 public interface EntityModelDao<E extends Entity> extends Entity {
 
+    public Long getRecordId();
+
+    public Long getAccountRecordId();
+
+    public Long getTenantRecordId();
+
     /**
      * Retrieve the TableName associated with this entity. This is used in
      * EntitySqlDaoWrapperInvocationHandler for history and auditing purposes.
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDaoBase.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDaoBase.java
new file mode 100644
index 0000000..824935f
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntityModelDaoBase.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.entity.dao;
+
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.killbill.billing.entity.EntityBase;
+
+public class EntityModelDaoBase extends EntityBase {
+
+    private Long recordId;
+    private Long accountRecordId;
+    private Long tenantRecordId;
+
+    public EntityModelDaoBase(final UUID id) {
+        super(id);
+    }
+
+    public EntityModelDaoBase() {
+    }
+
+    public EntityModelDaoBase(final UUID id, final DateTime createdDate, final DateTime updatedDate) {
+        super(id, createdDate, updatedDate);
+    }
+
+    public EntityModelDaoBase(final EntityBase target) {
+        super(target);
+    }
+
+    public Long getRecordId() {
+        return recordId;
+    }
+
+    public void setRecordId(final Long recordId) {
+        this.recordId = recordId;
+    }
+
+    public Long getAccountRecordId() {
+        return accountRecordId;
+    }
+
+    public void setAccountRecordId(final Long accountRecordId) {
+        this.accountRecordId = accountRecordId;
+    }
+
+    public Long getTenantRecordId() {
+        return tenantRecordId;
+    }
+
+    public void setTenantRecordId(final Long tenantRecordId) {
+        this.tenantRecordId = tenantRecordId;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
index 65ed8b8..b6d6996 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -19,18 +21,8 @@ package org.killbill.billing.util.entity.dao;
 import java.util.Iterator;
 import java.util.List;
 
-import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
-import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.SqlUpdate;
-import org.skife.jdbi.v2.sqlobject.customizers.Define;
-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.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.commons.jdbi.statement.SmartFetchSize;
 import org.killbill.billing.entity.EntityPersistenceException;
 import org.killbill.billing.util.audit.ChangeType;
 import org.killbill.billing.util.cache.Cachable;
@@ -39,6 +31,15 @@ import org.killbill.billing.util.cache.CachableKey;
 import org.killbill.billing.util.dao.AuditSqlDao;
 import org.killbill.billing.util.dao.HistorySqlDao;
 import org.killbill.billing.util.entity.Entity;
+import org.killbill.commons.jdbi.statement.SmartFetchSize;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.Define;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 // TODO get rid of Transmogrifier, but code does not compile even if we create the
 // method  public <T> T become(Class<T> typeToBecome); ?
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
index f1a0153..1432259 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
@@ -18,42 +18,46 @@ package org.killbill.billing.util.entity.dao;
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.skife.jdbi.v2.Binding;
-import org.skife.jdbi.v2.StatementContext;
-import org.skife.jdbi.v2.exceptions.DBIException;
-import org.skife.jdbi.v2.exceptions.StatementException;
-import org.skife.jdbi.v2.sqlobject.Bind;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.killbill.billing.ObjectType;
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.cache.BaseCacheLoader;
 import org.killbill.billing.util.cache.Cachable;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.cache.CachableKey;
 import org.killbill.billing.util.cache.CacheController;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.cache.CacheLoaderArgument;
-import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.billing.callcontext.InternalTenantContext;
-import org.killbill.clock.Clock;
 import org.killbill.billing.util.dao.EntityAudit;
 import org.killbill.billing.util.dao.EntityHistoryModelDao;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.dao.NonEntitySqlDao;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.util.entity.Entity;
+import org.killbill.billing.util.tag.dao.UUIDCollectionBinder;
+import org.killbill.clock.Clock;
+import org.killbill.commons.profiling.Profiling;
+import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
+import org.killbill.commons.profiling.ProfilingFeature.ProfilingFeatureType;
+import org.skife.jdbi.v2.Binding;
+import org.skife.jdbi.v2.StatementContext;
+import org.skife.jdbi.v2.exceptions.DBIException;
+import org.skife.jdbi.v2.exceptions.StatementException;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
@@ -71,7 +75,6 @@ import com.google.common.collect.Iterables;
  */
 public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, M extends EntityModelDao<E>, E extends Entity> implements InvocationHandler {
 
-    public static final String CACHE_KEY_SEPARATOR = "::";
 
     private final Logger logger = LoggerFactory.getLogger(EntitySqlDaoWrapperInvocationHandler.class);
 
@@ -81,6 +84,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
     private final CacheControllerDispatcher cacheControllerDispatcher;
     private final Clock clock;
     private final NonEntityDao nonEntityDao;
+    private final Profiling prof;
 
     public EntitySqlDaoWrapperInvocationHandler(final Class<S> sqlDaoClass, final S sqlDao, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
         this.sqlDaoClass = sqlDaoClass;
@@ -88,12 +92,18 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         this.clock = clock;
         this.cacheControllerDispatcher = cacheControllerDispatcher;
         this.nonEntityDao = nonEntityDao;
+        this.prof = new Profiling<Object>();
     }
 
     @Override
     public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
         try {
-            return invokeSafely(proxy, method, args);
+            return prof.executeWithProfiling(ProfilingFeatureType.DAO, sqlDaoClass.getSimpleName() + ":" + method.getName(), new WithProfilingCallback() {
+                @Override
+                public Object execute() throws Throwable {
+                    return invokeSafely(proxy, method, args);
+                }
+            });
         } catch (Throwable t) {
             if (t.getCause() != null && t.getCause().getCause() != null && DBIException.class.isAssignableFrom(t.getCause().getClass())) {
                 // Likely a JDBC error, try to extract the SQL statement and JDBI bindings
@@ -135,7 +145,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         final StringBuilder errorMessageBuilder = new StringBuilder("Error during transaction for sql entity {} and method {}");
         if (t instanceof SQLException) {
             final SQLException sqlException = (SQLException) t;
-            errorMessageBuilder.append(" [SQL State: ")
+            errorMessageBuilder.append(" [SQL DefaultState: ")
                                .append(sqlException.getSQLState())
                                .append(", Vendor Error Code: ")
                                .append(sqlException.getErrorCode())
@@ -170,12 +180,30 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         } else if (cachableAnnotation != null) {
             return invokeWithCaching(cachableAnnotation, method, args);
         } else {
-            return method.invoke(sqlDao, args);
+            return invokeRaw(method, args);
         }
     }
 
+    private Object invokeRaw(final Method method, final Object[] args) throws Throwable  {
+        return prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, sqlDaoClass.getSimpleName() + " (raw):" + method.getName(), new WithProfilingCallback() {
+            @Override
+            public Object execute() throws Throwable {
+                Object result = method.invoke(sqlDao, args);
+                // This is *almost* the default invocation except that we want to intercept getById calls to populate the caches; the pattern is to always fetch
+                // the object after it was created, which means this method is (by pattern) first called right after object creation and contains all the goodies we care
+                // about (record_id, account_record_id, object_id, tenant_record_id)
+                //
+                if (result != null && method.getName().equals("getById")) {
+                    populateCacheOnGetByIdInvocation((M) result);
+                }
+                return result;
+            }
+        });
+    }
+
+
     private Object invokeWithCaching(final Cachable cachableAnnotation, final Method method, final Object[] args)
-            throws IllegalAccessException, InvocationTargetException, ClassNotFoundException, InstantiationException {
+            throws Throwable {
         final ObjectType objectType = getObjectType();
         final CacheType cacheType = cachableAnnotation.value();
         final CacheController<Object, Object> cache = cacheControllerDispatcher.getCacheController(cacheType);
@@ -205,10 +233,15 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
                 }
             }, null);
             final CacheLoaderArgument cacheLoaderArgument = new CacheLoaderArgument(objectType, args, internalTenantContext);
-            result = cache.get(cacheKey, cacheLoaderArgument);
+            return cache.get(cacheKey, cacheLoaderArgument);
         }
         if (result == null) {
-            result = method.invoke(sqlDao, args);
+            result = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, sqlDaoClass.getSimpleName() + "(raw) :" + method.getName(), new WithProfilingCallback() {
+                @Override
+                public Object execute() throws Throwable {
+                    return method.invoke(sqlDao, args);
+                }
+            });
         }
         return result;
     }
@@ -262,8 +295,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         return null;
     }
 
-
-    private Object invokeWithAuditAndHistory(final Audited auditedAnnotation, final Method method, final Object[] args) throws IllegalAccessException, InvocationTargetException {
+    private Object invokeWithAuditAndHistory(final Audited auditedAnnotation, final Method method, final Object[] args) throws Throwable {
         InternalCallContext context = null;
         List<String> entityIds = null;
         final Map<String, M> entities = new HashMap<String, M>();
@@ -280,7 +312,12 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         }
 
         // Real jdbc call
-        final Object obj = method.invoke(sqlDao, args);
+        final Object obj = prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, sqlDaoClass.getSimpleName() + " (raw) :", new WithProfilingCallback() {
+            @Override
+            public Object execute() throws Throwable {
+                return method.invoke(sqlDao, args);
+            }
+        });
 
         final ChangeType changeType = auditedAnnotation.value();
 
@@ -290,24 +327,56 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         return obj;
     }
 
-    private void updateHistoryAndAudit(final String entityId, final Map<String, M> entities, final Map<String, Long> entityRecordIds,
-                                       final ChangeType changeType, final InternalCallContext context) {
-        // Make sure to re-hydrate the object (especially needed for create calls)
-        final M reHydratedEntity = sqlDao.getById(entityId, context);
-        final Long reHydratedEntityRecordId = sqlDao.getRecordId(entityId, context);
-        final M entity = Objects.firstNonNull(reHydratedEntity, entities.get(entityId));
-        final Long entityRecordId = Objects.firstNonNull(reHydratedEntityRecordId, entityRecordIds.get(entityId));
-        final TableName tableName = entity.getTableName();
-
-        // Note: audit entries point to the history record id
-        final Long historyRecordId;
-        if (tableName.getHistoryTableName() != null) {
-            historyRecordId = insertHistory(entityRecordId, entity, changeType, context);
-        } else {
-            historyRecordId = entityRecordId;
+    private void populateCacheOnGetByIdInvocation(M model) {
+
+        final CacheController<Object, Object> cacheRecordId = cacheControllerDispatcher.getCacheController(CacheType.RECORD_ID);
+        cacheRecordId.add(getKey(model.getId().toString(), CacheType.RECORD_ID, model.getTableName()), model.getRecordId());
+
+        final CacheController<Object, Object> cacheObjectId = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
+        cacheObjectId.add(getKey(model.getRecordId().toString(), CacheType.OBJECT_ID, model.getTableName()), model.getId());
+
+        if (model.getTenantRecordId() != null) {
+            final CacheController<Object, Object> cacheTenantRecordId = cacheControllerDispatcher.getCacheController(CacheType.TENANT_RECORD_ID);
+            cacheTenantRecordId.add(getKey(model.getId().toString(), CacheType.TENANT_RECORD_ID, model.getTableName()), model.getTenantRecordId());
+        }
+
+        if (model.getAccountRecordId() != null) {
+            final CacheController<Object, Object> cacheAccountRecordId = cacheControllerDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID);
+            cacheAccountRecordId.add(getKey(model.getId().toString(), CacheType.ACCOUNT_RECORD_ID, model.getTableName()), model.getAccountRecordId());
         }
+    }
 
-        insertAudits(tableName, entityRecordId, historyRecordId, changeType, context);
+    private String getKey(final String rawKey, final CacheType cacheType, final TableName tableName) {
+        return cacheType.isKeyPrefixedWithTableName() ?
+               tableName + CacheControllerDispatcher.CACHE_KEY_SEPARATOR + rawKey :
+               rawKey;
+    }
+
+    private void updateHistoryAndAudit(final String entityId, final Map<String, M> entities, final Map<String, Long> entityRecordIds,
+                                       final ChangeType changeType, final InternalCallContext context) throws Throwable {
+
+        prof.executeWithProfiling(ProfilingFeatureType.DAO_DETAILS, sqlDaoClass.getSimpleName() + " (history/audit) :", new WithProfilingCallback() {
+            @Override
+            public Object execute() {
+                final M reHydratedEntity = sqlDao.getById(entityId, context);
+                final Long reHydratedEntityRecordId = sqlDao.getRecordId(entityId, context);
+                final M entity = Objects.firstNonNull(reHydratedEntity, entities.get(entityId));
+                final Long entityRecordId = Objects.firstNonNull(reHydratedEntityRecordId, entityRecordIds.get(entityId));
+                final TableName tableName = entity.getTableName();
+
+                // Note: audit entries point to the history record id
+                final Long historyRecordId;
+                if (tableName.getHistoryTableName() != null) {
+                    historyRecordId = insertHistory(entityRecordId, entity, changeType, context);
+                } else {
+                    historyRecordId = entityRecordId;
+                }
+
+                // Make sure to re-hydrate the object (especially needed for create calls)
+                insertAudits(tableName, entityRecordId, historyRecordId, changeType, context);
+                return null;
+            }
+        });
     }
 
     private List<String> retrieveEntityIdsFromArguments(final Method method, final Object[] args) {
@@ -330,19 +399,14 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
                 }
             }
 
-            // Otherwise, use the first String argument, annotated with @Bind("id")
-            // This is true for e.g. update calls
-            if (!(arg instanceof String)) {
-                continue;
-            }
-
             for (final Annotation annotation : parameterAnnotations[i]) {
-                if (Bind.class.equals(annotation.annotationType()) && ("id").equals(((Bind) annotation).value())) {
+                if (arg instanceof String && Bind.class.equals(annotation.annotationType()) && ("id").equals(((Bind) annotation).value())) {
                     return ImmutableList.<String>of((String) arg);
+                } else if (arg instanceof Collection && UUIDCollectionBinder.class.equals(annotation.annotationType())) {
+                    return ImmutableList.<String>copyOf((Collection) arg);
                 }
             }
         }
-
         return null;
     }
 
@@ -362,7 +426,6 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         return entityIds;
     }
 
-
     private InternalCallContext retrieveContextFromArguments(final Object[] args) {
         for (final Object arg : args) {
             if (!(arg instanceof InternalCallContext)) {
@@ -423,7 +486,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
             final String str = String.valueOf(keyPieces.get(i)).toUpperCase();
             cacheKey.append(str);
             if (i < keyPieces.size() - 1) {
-                cacheKey.append(CACHE_KEY_SEPARATOR);
+                cacheKey.append(CacheControllerDispatcher.CACHE_KEY_SEPARATOR);
             }
         }
         return cacheKey.toString();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/AuditModule.java b/util/src/main/java/org/killbill/billing/util/glue/AuditModule.java
index 27fcc81..91d879b 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/AuditModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/AuditModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,17 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.audit.api.DefaultAuditUserApi;
 import org.killbill.billing.util.audit.dao.AuditDao;
 import org.killbill.billing.util.audit.dao.DefaultAuditDao;
 
-import com.google.inject.AbstractModule;
+public class AuditModule extends KillBillModule {
 
-public class AuditModule extends AbstractModule {
+    public AuditModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     protected void installDaos() {
         bind(AuditDao.class).to(DefaultAuditDao.class).asEagerSingleton();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
index fbcff27..5b94fb4 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CacheModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,28 +18,24 @@
 
 package org.killbill.billing.util.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.cache.CacheControllerDispatcherProvider;
 import org.killbill.billing.util.cache.EhCacheCacheManagerProvider;
 import org.killbill.billing.util.config.CacheConfig;
+import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.AbstractModule;
 import net.sf.ehcache.CacheManager;
 
-public class CacheModule extends AbstractModule {
-
-    private final ConfigSource configSource;
+public class CacheModule extends KillBillModule {
 
-    public CacheModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public CacheModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     @Override
     protected void configure() {
-        final CacheConfig config = new ConfigurationObjectFactory(configSource).build(CacheConfig.class);
+        final CacheConfig config = new ConfigurationObjectFactory(skifeConfigSource).build(CacheConfig.class);
         bind(CacheConfig.class).toInstance(config);
 
         // EhCache specifics
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CallContextModule.java b/util/src/main/java/org/killbill/billing/util/glue/CallContextModule.java
index 7267e0b..5a3af38 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CallContextModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CallContextModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,17 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.callcontext.CallContextFactory;
 import org.killbill.billing.util.callcontext.DefaultCallContextFactory;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 
-import com.google.inject.AbstractModule;
+public class CallContextModule extends KillBillModule {
+
+    public CallContextModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
-public class CallContextModule extends AbstractModule {
     @Override
     protected void configure() {
         bind(CallContextFactory.class).to(DefaultCallContextFactory.class).asEagerSingleton();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/ClockModule.java b/util/src/main/java/org/killbill/billing/util/glue/ClockModule.java
index bf90f7f..f939451 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/ClockModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/ClockModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,12 +18,15 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.clock.Clock;
 import org.killbill.clock.DefaultClock;
 
-import com.google.inject.AbstractModule;
+public class ClockModule extends KillBillModule {
 
-public class ClockModule extends AbstractModule {
+    public ClockModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/CustomFieldModule.java b/util/src/main/java/org/killbill/billing/util/glue/CustomFieldModule.java
index f689e9a..4a302ce 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/CustomFieldModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/CustomFieldModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,18 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.api.CustomFieldUserApi;
 import org.killbill.billing.util.customfield.api.DefaultCustomFieldUserApi;
-import org.killbill.billing.util.customfield.dao.DefaultCustomFieldDao;
 import org.killbill.billing.util.customfield.dao.CustomFieldDao;
+import org.killbill.billing.util.customfield.dao.DefaultCustomFieldDao;
 
-import com.google.inject.AbstractModule;
+public class CustomFieldModule extends KillBillModule {
+
+    public CustomFieldModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
-public class CustomFieldModule extends AbstractModule {
     @Override
     protected void configure() {
         installCustomFieldDao();
@@ -37,5 +43,4 @@ public class CustomFieldModule extends AbstractModule {
     protected void installCustomFieldDao() {
         bind(CustomFieldDao.class).to(DefaultCustomFieldDao.class).asEagerSingleton();
     }
-
 }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/EhCacheManagerProvider.java b/util/src/main/java/org/killbill/billing/util/glue/EhCacheManagerProvider.java
index bd742f8..3577271 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/EhCacheManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/EhCacheManagerProvider.java
@@ -22,6 +22,7 @@ import javax.inject.Provider;
 import org.apache.shiro.cache.ehcache.EhCacheManager;
 import org.apache.shiro.mgt.DefaultSecurityManager;
 import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
 
 import net.sf.ehcache.CacheManager;
 
@@ -42,6 +43,11 @@ public class EhCacheManagerProvider implements Provider<EhCacheManager> {
         // Same EhCache manager instance as the rest of the system
         shiroEhCacheManager.setCacheManager(ehCacheCacheManager);
 
+        // It looks like Shiro's cache manager is not thread safe. Concurrent requests on startup
+        // can throw org.apache.shiro.cache.CacheException: net.sf.ehcache.ObjectExistsException: Cache shiro-activeSessionCache already exists
+        // As a workaround, create the cache manually here
+        shiroEhCacheManager.getCache(CachingSessionDAO.ACTIVE_SESSION_CACHE_NAME);
+
         if (securityManager instanceof DefaultSecurityManager) {
             ((DefaultSecurityManager) securityManager).setCacheManager(shiroEhCacheManager);
         }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/ExportModule.java b/util/src/main/java/org/killbill/billing/util/glue/ExportModule.java
index 3280dcd..6cecb4c 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/ExportModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/ExportModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,12 +18,15 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.api.ExportUserApi;
 import org.killbill.billing.util.export.api.DefaultExportUserApi;
 
-import com.google.inject.AbstractModule;
+public class ExportModule extends KillBillModule {
 
-public class ExportModule extends AbstractModule {
+    public ExportModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     protected void installUserApi() {
         bind(ExportUserApi.class).to(DefaultExportUserApi.class).asEagerSingleton();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/GlobalLockerModule.java b/util/src/main/java/org/killbill/billing/util/glue/GlobalLockerModule.java
index 3bcd3e8..9642aeb 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/GlobalLockerModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/GlobalLockerModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,25 +18,25 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.commons.embeddeddb.EmbeddedDB;
 import org.killbill.commons.embeddeddb.EmbeddedDB.DBEngine;
 
-import com.google.inject.AbstractModule;
-
-public class GlobalLockerModule extends AbstractModule {
+public class GlobalLockerModule extends KillBillModule {
 
     private final DBEngine engine;
 
-    public GlobalLockerModule(final DBEngine engine) {
+    public GlobalLockerModule(final DBEngine engine, final KillbillConfigSource configSource) {
+        super(configSource);
         this.engine = engine;
     }
 
     @Override
     protected void configure() {
         if (EmbeddedDB.DBEngine.MYSQL.equals(engine)) {
-            install(new MySqlGlobalLockerModule());
+            install(new MySqlGlobalLockerModule(configSource));
         } else {
-            install(new MemoryGlobalLockerModule());
+            install(new MemoryGlobalLockerModule(configSource));
         }
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java b/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
index 3bbf599..cb1c9f0 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
@@ -16,17 +16,24 @@
 
 package org.killbill.billing.util.glue;
 
+import java.util.Collection;
+
 import javax.inject.Inject;
 import javax.inject.Provider;
 
 import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
 import org.apache.shiro.realm.text.IniRealm;
+import org.apache.shiro.util.Factory;
+import org.killbill.billing.util.config.SecurityConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.killbill.billing.util.config.SecurityConfig;
-
-public class IniRealmProvider implements Provider<IniRealm> {
+// Really Provider<IniRealm>, but avoid an extra cast below
+public class IniRealmProvider implements Provider<Realm> {
 
     private static final Logger log = LoggerFactory.getLogger(IniRealmProvider.class);
 
@@ -38,10 +45,17 @@ public class IniRealmProvider implements Provider<IniRealm> {
     }
 
     @Override
-    public IniRealm get() {
+    public Realm get() {
         try {
-            return new IniRealm(securityConfig.getShiroResourcePath());
-        } catch (ConfigurationException e) {
+            final Factory<SecurityManager> factory = new IniSecurityManagerFactory(securityConfig.getShiroResourcePath());
+            // TODO Pierre hack - lame cast here, but we need to have Shiro go through its reflection magic
+            // to parse the [main] section of the ini file. Without duplicating code, this seems to be possible only
+            // by going through IniSecurityManagerFactory.
+            final DefaultSecurityManager securityManager = (DefaultSecurityManager) factory.getInstance();
+            final Collection<Realm> realms = securityManager.getRealms();
+            // Null check mainly for testing
+            return realms == null ? new IniRealm(securityConfig.getShiroResourcePath()) : realms.iterator().next();
+        } catch (final ConfigurationException e) {
             log.warn("Unable to configure RBAC", e);
             return new IniRealm();
         }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillbillApiAopModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillbillApiAopModule.java
new file mode 100644
index 0000000..cb3d5a6
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillbillApiAopModule.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.glue;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.killbill.billing.KillbillApi;
+import org.killbill.commons.profiling.Profiling;
+import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
+import org.killbill.commons.profiling.ProfilingFeature.ProfilingFeatureType;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.matcher.Matchers;
+
+public class KillbillApiAopModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+
+        bindInterceptor(Matchers.subclassesOf(KillbillApi.class),
+                        Matchers.any(),
+                        new ProfilingMethodInterceptor());
+    }
+
+    public static class ProfilingMethodInterceptor implements MethodInterceptor {
+
+        private final Profiling prof = new Profiling<Object>();
+
+        @Override
+        public Object invoke(final MethodInvocation invocation) throws Throwable {
+            return prof.executeWithProfiling(ProfilingFeatureType.API, invocation.getMethod().getName(), new WithProfilingCallback() {
+                @Override
+                public Object execute() throws Throwable {
+                    return invocation.proceed();
+                }
+            });
+        }
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index cdb5561..f74ffa0 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -21,12 +23,12 @@ import org.apache.shiro.guice.ShiroModule;
 import org.apache.shiro.mgt.SecurityManager;
 import org.apache.shiro.session.mgt.DefaultSessionManager;
 import org.apache.shiro.session.mgt.SessionManager;
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.config.RbacConfig;
 import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
 import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
+import org.skife.config.ConfigSource;
+import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.binder.AnnotatedBindingBuilder;
 
@@ -45,14 +47,19 @@ public class KillBillShiroModule extends ShiroModule {
         return Boolean.parseBoolean(System.getProperty(KILLBILL_RBAC_PROPERTY, "true"));
     }
 
-    private final ConfigSource configSource;
+    private final KillbillConfigSource configSource;
 
-    public KillBillShiroModule(final ConfigSource configSource) {
+    public KillBillShiroModule(final KillbillConfigSource configSource) {
         this.configSource = configSource;
     }
 
     protected void configureShiro() {
-        final RbacConfig config = new ConfigurationObjectFactory(configSource).build(RbacConfig.class);
+        final RbacConfig config = new ConfigurationObjectFactory(new ConfigSource() {
+            @Override
+            public String getString(final String propertyName) {
+                return configSource.getString(propertyName);
+            }
+        }).build(RbacConfig.class);
         bind(RbacConfig.class).toInstance(config);
 
         bindRealm().toProvider(IniRealmProvider.class).asEagerSingleton();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/MemoryGlobalLockerModule.java b/util/src/main/java/org/killbill/billing/util/glue/MemoryGlobalLockerModule.java
index bdb84cf..e345216 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/MemoryGlobalLockerModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/MemoryGlobalLockerModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,12 +18,15 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.memory.MemoryGlobalLocker;
 
-import com.google.inject.AbstractModule;
+public class MemoryGlobalLockerModule extends KillBillModule {
 
-public class MemoryGlobalLockerModule extends AbstractModule {
+    public MemoryGlobalLockerModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/MySqlGlobalLockerModule.java b/util/src/main/java/org/killbill/billing/util/glue/MySqlGlobalLockerModule.java
index 5c2d426..3b5935c 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/MySqlGlobalLockerModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/MySqlGlobalLockerModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,11 +18,14 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.commons.locker.GlobalLocker;
 
-import com.google.inject.AbstractModule;
+public class MySqlGlobalLockerModule extends KillBillModule {
 
-public class MySqlGlobalLockerModule extends AbstractModule {
+    public MySqlGlobalLockerModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/NonEntityDaoModule.java b/util/src/main/java/org/killbill/billing/util/glue/NonEntityDaoModule.java
index e815878..45f3afa 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/NonEntityDaoModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/NonEntityDaoModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,15 @@
 
 package org.killbill.billing.util.glue;
 
-import org.killbill.billing.util.callcontext.InternalCallContextFactory;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.dao.DefaultNonEntityDao;
 import org.killbill.billing.util.dao.NonEntityDao;
 
-import com.google.inject.AbstractModule;
-
-public class NonEntityDaoModule extends AbstractModule {
+public class NonEntityDaoModule extends KillBillModule {
 
+    public NonEntityDaoModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/RecordIdModule.java b/util/src/main/java/org/killbill/billing/util/glue/RecordIdModule.java
index a1b32b9..04e8024 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/RecordIdModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/RecordIdModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,13 +18,15 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.api.RecordIdApi;
 import org.killbill.billing.util.recordid.DefaultRecordIdApi;
 
-import com.google.inject.AbstractModule;
-
-public class RecordIdModule extends AbstractModule {
+public class RecordIdModule extends KillBillModule {
 
+    public RecordIdModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java b/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
index 7a352ba..7ad1e93 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,23 +18,18 @@
 
 package org.killbill.billing.util.glue;
 
-import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
-
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.security.api.SecurityApi;
 import org.killbill.billing.util.config.SecurityConfig;
 import org.killbill.billing.util.security.api.DefaultSecurityApi;
 import org.killbill.billing.util.security.api.DefaultSecurityService;
 import org.killbill.billing.util.security.api.SecurityService;
+import org.skife.config.ConfigurationObjectFactory;
 
-import com.google.inject.AbstractModule;
-
-public class SecurityModule extends AbstractModule {
-
-    private final ConfigSource configSource;
+public class SecurityModule extends KillBillModule {
 
-    public SecurityModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public SecurityModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     public void configure() {
@@ -42,7 +39,7 @@ public class SecurityModule extends AbstractModule {
     }
 
     private void installConfig() {
-        final SecurityConfig securityConfig = new ConfigurationObjectFactory(configSource).build(SecurityConfig.class);
+        final SecurityConfig securityConfig = new ConfigurationObjectFactory(skifeConfigSource).build(SecurityConfig.class);
         bind(SecurityConfig.class).toInstance(securityConfig);
     }
 
diff --git a/util/src/main/java/org/killbill/billing/util/glue/TagStoreModule.java b/util/src/main/java/org/killbill/billing/util/glue/TagStoreModule.java
index f5b97a1..d82538a 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/TagStoreModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/TagStoreModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,18 +18,21 @@
 
 package org.killbill.billing.util.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.api.TagUserApi;
 import org.killbill.billing.util.tag.DefaultTagInternalApi;
-import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.tag.api.DefaultTagUserApi;
 import org.killbill.billing.util.tag.dao.DefaultTagDao;
 import org.killbill.billing.util.tag.dao.DefaultTagDefinitionDao;
 import org.killbill.billing.util.tag.dao.TagDao;
 import org.killbill.billing.util.tag.dao.TagDefinitionDao;
 
-import com.google.inject.AbstractModule;
+public class TagStoreModule extends KillBillModule {
 
-public class TagStoreModule extends AbstractModule {
+    public TagStoreModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
index c601350..5d0bc2b 100644
--- a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
+++ b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
@@ -21,9 +21,10 @@ import java.util.List;
 import java.util.Set;
 
 import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
 import org.apache.shiro.authz.AuthorizationException;
 import org.apache.shiro.subject.Subject;
-
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.security.Logical;
 import org.killbill.billing.security.Permission;
@@ -39,6 +40,43 @@ public class DefaultSecurityApi implements SecurityApi {
     private static final String[] allPermissions = new String[Permission.values().length];
 
     @Override
+    public synchronized void login(final Object principal, final Object credentials) {
+        final Subject currentUser = SecurityUtils.getSubject();
+
+        // Workaround for https://issues.apache.org/jira/browse/SHIRO-510
+        // TODO Not sure if it's a good fix?
+        if (principal.equals(currentUser.getPrincipal()) &&
+            currentUser.isAuthenticated()) {
+            return;
+        }
+
+        // UsernamePasswordToken is hardcoded in AuthenticatingRealm
+        if (principal instanceof String && credentials instanceof String) {
+            currentUser.login(new UsernamePasswordToken((String) principal, (String) credentials));
+        } else if (principal instanceof String && credentials instanceof char[]) {
+            currentUser.login(new UsernamePasswordToken((String) principal, (char[]) credentials));
+        } else {
+            currentUser.login(new AuthenticationToken() {
+                @Override
+                public Object getPrincipal() {
+                    return principal;
+                }
+
+                @Override
+                public Object getCredentials() {
+                    return credentials;
+                }
+            });
+        }
+    }
+
+    @Override
+    public void logout() {
+        final Subject currentUser = SecurityUtils.getSubject();
+        currentUser.logout();
+    }
+
+    @Override
     public Set<Permission> getCurrentUserPermissions(final TenantContext context) {
         final Permission[] killbillPermissions = Permission.values();
         final String[] killbillPermissionsString = getAllPermissionsAsStrings();
diff --git a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityService.java b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityService.java
index 14fa2a0..5227be1 100644
--- a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityService.java
+++ b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -20,9 +22,8 @@ import javax.inject.Inject;
 
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.mgt.SecurityManager;
-
-import org.killbill.billing.lifecycle.LifecycleHandlerType;
-import org.killbill.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
+import org.killbill.billing.platform.api.LifecycleHandlerType;
+import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
 
 public class DefaultSecurityService implements SecurityService {
 
diff --git a/util/src/main/java/org/killbill/billing/util/security/api/SecurityService.java b/util/src/main/java/org/killbill/billing/util/security/api/SecurityService.java
index 96c4905..a2d8d2b 100644
--- a/util/src/main/java/org/killbill/billing/util/security/api/SecurityService.java
+++ b/util/src/main/java/org/killbill/billing/util/security/api/SecurityService.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,7 +18,7 @@
 
 package org.killbill.billing.util.security.api;
 
-import org.killbill.billing.lifecycle.KillbillService;
+import org.killbill.billing.platform.api.KillbillService;
 
 public interface SecurityService extends KillbillService {
 }
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
index dae7fc2..c389bd7 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
@@ -23,6 +23,7 @@ import javax.inject.Inject;
 
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
 import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.Transaction;
@@ -30,8 +31,6 @@ import org.skife.jdbi.v2.TransactionStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
-
 public class JDBCSessionDao extends CachingSessionDAO {
 
     private static final Logger log = LoggerFactory.getLogger(JDBCSessionDao.class);
@@ -72,14 +71,27 @@ public class JDBCSessionDao extends CachingSessionDAO {
 
     @Override
     protected Session doReadSession(final Serializable sessionId) {
-        final SessionModelDao sessionModelDao = jdbcSessionSqlDao.read(sessionId);
+        // Shiro should not pass us a null sessionId, but be safe...
+        if (sessionId == null) {
+            return null;
+        }
+
+        // Ignore unsupported JSESSIONID cookies
+        final Long recordId;
+        try {
+            recordId = Long.parseLong(sessionId.toString().trim());
+        } catch (final NumberFormatException e) {
+            return null;
+        }
+
+        final SessionModelDao sessionModelDao = jdbcSessionSqlDao.read(recordId);
         if (sessionModelDao == null) {
             return null;
         }
 
         try {
             return sessionModelDao.toSimpleSession();
-        } catch (IOException e) {
+        } catch (final IOException e) {
             log.warn("Corrupted cookie", e);
             return null;
         }
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
index 5987b92..676d082 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
@@ -16,21 +16,18 @@
 
 package org.killbill.billing.util.security.shiro.dao;
 
-import java.io.Serializable;
-
+import org.killbill.commons.jdbi.binder.SmartBindBean;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
 
-import org.killbill.commons.jdbi.binder.SmartBindBean;
-
 @UseStringTemplate3StatementLocator
 public interface JDBCSessionSqlDao extends Transactional<JDBCSessionSqlDao> {
 
     @SqlQuery
-    public SessionModelDao read(@Bind("recordId") final Serializable sessionId);
+    public SessionModelDao read(@Bind("recordId") final Long sessionId);
 
     @SqlUpdate
     public void create(@SmartBindBean final SessionModelDao sessionModelDao);
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionModelDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionModelDao.java
index 85d9f49..dbde12c 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/TagDefinitionModelDao.java
@@ -23,10 +23,11 @@ import org.joda.time.DateTime;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 import org.killbill.billing.util.tag.ControlTagType;
 import org.killbill.billing.util.tag.TagDefinition;
 
-public class TagDefinitionModelDao extends EntityBase implements EntityModelDao<TagDefinition> {
+public class TagDefinitionModelDao extends EntityModelDaoBase implements EntityModelDao<TagDefinition> {
 
     private String name;
     private String description;
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/TagModelDao.java b/util/src/main/java/org/killbill/billing/util/tag/dao/TagModelDao.java
index a3b5353..2b80de8 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/TagModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/TagModelDao.java
@@ -24,9 +24,10 @@ import org.killbill.billing.ObjectType;
 import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.entity.EntityBase;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
+import org.killbill.billing.util.entity.dao.EntityModelDaoBase;
 import org.killbill.billing.util.tag.Tag;
 
-public class TagModelDao extends EntityBase implements EntityModelDao<Tag> {
+public class TagModelDao extends EntityModelDaoBase implements EntityModelDao<Tag> {
 
     private UUID tagDefinitionId;
     private UUID objectId;
diff --git a/util/src/main/java/org/killbill/billing/util/tag/dao/UUIDCollectionBinder.java b/util/src/main/java/org/killbill/billing/util/tag/dao/UUIDCollectionBinder.java
index db4df65..b5f431d 100644
--- a/util/src/main/java/org/killbill/billing/util/tag/dao/UUIDCollectionBinder.java
+++ b/util/src/main/java/org/killbill/billing/util/tag/dao/UUIDCollectionBinder.java
@@ -39,7 +39,7 @@ public @interface UUIDCollectionBinder {
 
                 @Override
                 public void bind(SQLStatement<?> query, UUIDCollectionBinder bind, Collection<String> ids) {
-                    query.define("tag_definition_ids", ids);
+                    query.define("ids", ids);
 
                     int idx = 0;
                     for (String id : ids) {
diff --git a/util/src/main/java/org/killbill/billing/util/template/translation/DefaultTranslatorBase.java b/util/src/main/java/org/killbill/billing/util/template/translation/DefaultTranslatorBase.java
index 93dce57..4516415 100644
--- a/util/src/main/java/org/killbill/billing/util/template/translation/DefaultTranslatorBase.java
+++ b/util/src/main/java/org/killbill/billing/util/template/translation/DefaultTranslatorBase.java
@@ -24,11 +24,11 @@ import java.util.MissingResourceException;
 import java.util.PropertyResourceBundle;
 import java.util.ResourceBundle;
 
+import org.killbill.xmlloader.UriAccessor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.killbill.billing.util.LocaleUtils;
-import org.killbill.billing.util.config.catalog.UriAccessor;
 
 import com.google.inject.Inject;
 
diff --git a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequest.java b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequest.java
index 679e09d..7691ba6 100644
--- a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequest.java
+++ b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequest.java
@@ -1,7 +1,9 @@
-/* 
+/*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -13,6 +15,7 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package org.killbill.billing.util.userrequest;
 
 public interface CompletionUserRequest extends CompletionUserRequestNotifier, CompletionUserRequestWaiter {
diff --git a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestBase.java b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestBase.java
index 3f99515..da85734 100644
--- a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestBase.java
+++ b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestBase.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -43,7 +45,6 @@ public class CompletionUserRequestBase implements CompletionUserRequest {
     private boolean isCompleted;
     private long initialTimeMilliSec;
 
-
     public CompletionUserRequestBase(final UUID userToken) {
         this.events = new LinkedList<BusInternalEvent>();
         this.userToken = userToken;
@@ -79,7 +80,6 @@ public class CompletionUserRequestBase implements CompletionUserRequest {
         }
     }
 
-
     private long currentTimeMillis() {
         return System.nanoTime() / NANO_TO_MILLI_SEC;
     }
@@ -132,7 +132,6 @@ public class CompletionUserRequestBase implements CompletionUserRequest {
         }
     }
 
-
     @Override
     public void onAccountCreation(final AccountCreationInternalEvent curEvent) {
     }
diff --git a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestNotifier.java b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestNotifier.java
index 2bad350..86a708c 100644
--- a/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestNotifier.java
+++ b/util/src/main/java/org/killbill/billing/util/userrequest/CompletionUserRequestNotifier.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,10 +18,8 @@
 
 package org.killbill.billing.util.userrequest;
 
-
 import org.killbill.billing.events.BusInternalEvent;
 
-
 public interface CompletionUserRequestNotifier {
 
     public void notifyForCompletion();
diff --git a/util/src/main/resources/ehcache.xml b/util/src/main/resources/ehcache.xml
index fb82f50..ed79009 100644
--- a/util/src/main/resources/ehcache.xml
+++ b/util/src/main/resources/ehcache.xml
@@ -71,6 +71,21 @@
                 properties=""/>
     </cache>
 
+    <cache name="object-id"
+           maxElementsInMemory="100000"
+           maxElementsOnDisk="0"
+           eternal="true"
+           overflowToDisk="false"
+           diskPersistent="false"
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
+        <cacheEventListenerFactory
+                class="org.killbill.billing.util.cache.ExpirationListenerFactory"
+                properties=""/>
+    </cache>
+
+
     <cache name="audit-log"
            maxElementsInMemory="500000"
            maxElementsOnDisk="0"
diff --git a/util/src/main/resources/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
index 08e0800..e313b15 100644
--- a/util/src/main/resources/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/tag/dao/TagDefinitionSqlDao.sql.stg
@@ -58,12 +58,12 @@ and t.is_active
 ;
 >>
 
-getByIds(tag_definition_ids) ::= <<
+getByIds(ids) ::= <<
 select
   <allTableFields("t.")>
 from <tableName()> t
 where t.is_active
-and <idField("t.")> in (<tag_definition_ids: {id | :id_<i0>}; separator="," >)
+and <idField("t.")> in (<ids: {id | :id_<i0>}; separator="," >)
 <AND_CHECK_TENANT("t.")>
 ;
 >>
diff --git a/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java b/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java
index da53649..997ee41 100644
--- a/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java
+++ b/util/src/test/java/org/killbill/billing/dao/MockNonEntityDao.java
@@ -54,7 +54,7 @@ public class MockNonEntityDao implements NonEntityDao {
     }
 
     @Override
-    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType) {
+    public UUID retrieveIdFromObject(final Long recordId, final ObjectType objectType, final CacheController<Object, Object> cache) {
         return null;
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/DBTestingHelper.java b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
index 3221a72..d61feee 100644
--- a/util/src/test/java/org/killbill/billing/DBTestingHelper.java
+++ b/util/src/test/java/org/killbill/billing/DBTestingHelper.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -18,59 +20,39 @@ package org.killbill.billing;
 
 import java.io.IOException;
 
-import org.skife.jdbi.v2.IDBI;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.killbill.commons.embeddeddb.EmbeddedDB;
-import org.killbill.commons.embeddeddb.h2.H2EmbeddedDB;
-import org.killbill.commons.embeddeddb.mysql.MySQLEmbeddedDB;
-import org.killbill.commons.embeddeddb.mysql.MySQLStandaloneDB;
-import org.killbill.billing.dbi.DBIProvider;
+import org.killbill.billing.platform.test.PlatformDBTestingHelper;
+import org.killbill.billing.util.dao.AuditLogModelDaoMapper;
+import org.killbill.billing.util.dao.RecordIdIdMappingsMapper;
 import org.killbill.billing.util.io.IOUtils;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.IDBI;
 
 import com.google.common.io.Resources;
 
-public class DBTestingHelper {
+public class DBTestingHelper extends PlatformDBTestingHelper {
 
-    private static final Logger log = LoggerFactory.getLogger(DBTestingHelper.class);
+    private static DBTestingHelper dbTestingHelper = null;
 
-    protected static EmbeddedDB instance;
-
-    public static synchronized EmbeddedDB get() {
-        if (instance == null) {
-            if ("true".equals(System.getProperty("org.killbill.billing.dbi.test.h2"))) {
-                log.info("Using h2 as the embedded database");
-                instance = new H2EmbeddedDB();
-            } else {
-                if (isUsingLocalInstance()) {
-                    log.info("Using MySQL local database");
-                    final String databaseName = System.getProperty("org.killbill.billing.dbi.test.localDb.database", "killbill");
-                    final String username = System.getProperty("org.killbill.billing.dbi.test.localDb.password", "root");
-                    final String password = System.getProperty("org.killbill.billing.dbi.test.localDb.username", "root");
-                    instance = new MySQLStandaloneDB(databaseName, username, password);
-                } else {
-                    log.info("Using MySQL as the embedded database");
-                    instance = new MySQLEmbeddedDB();
-                }
-            }
+    public static synchronized DBTestingHelper get() {
+        if (dbTestingHelper == null) {
+            dbTestingHelper = new DBTestingHelper();
         }
-        return instance;
+        return dbTestingHelper;
     }
 
-    public static synchronized IDBI getDBI() throws IOException {
-        return new DBIProvider(get().getDataSource()).get();
+    protected DBTestingHelper() {
+        super();
     }
 
-    public static synchronized void start() throws IOException {
-        final EmbeddedDB instance = get();
-        instance.initialize();
-        instance.start();
-
-        if (isUsingLocalInstance()) {
-            return;
-        }
+    @Override
+    public synchronized IDBI getDBI() throws IOException {
+        final DBI dbi = (DBI) super.getDBI();
+        dbi.registerMapper(new AuditLogModelDaoMapper());
+        dbi.registerMapper(new RecordIdIdMappingsMapper());
+        return dbi;
+    }
 
+    protected synchronized void executePostStartupScripts() throws IOException {
         // We always want the accounts and tenants table
         instance.executeScript("drop table if exists accounts;" +
                                "CREATE TABLE accounts (\n" +
@@ -187,10 +169,5 @@ public class DBTestingHelper {
                 instance.executeScript(ddl);
             }
         }
-        instance.refreshTableNames();
-    }
-
-    private static boolean isUsingLocalInstance() {
-        return (System.getProperty("org.killbill.billing.dbi.test.useLocalDb") != null);
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestModule.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestModule.java
index 00c37ea..7cda860 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestModule.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -18,17 +20,17 @@ package org.killbill.billing;
 
 import java.util.UUID;
 
+import org.killbill.billing.callcontext.InternalCallContext;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.CallOrigin;
-import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.UserType;
+import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.clock.Clock;
 import org.killbill.clock.ClockMock;
 
-import com.google.inject.AbstractModule;
-
-public class GuicyKillbillTestModule extends AbstractModule {
+public class GuicyKillbillTestModule extends KillBillModule {
 
     //
     // CreatedFontTracker references that will later be injected through Guices.
@@ -42,7 +44,9 @@ public class GuicyKillbillTestModule extends AbstractModule {
 
     private final CallContext callContext = internalCallContext.toCallContext(null);
 
-
+    public GuicyKillbillTestModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestNoDBModule.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestNoDBModule.java
index 82319ef..9decb42 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestNoDBModule.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestNoDBModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,19 +18,19 @@
 
 package org.killbill.billing;
 
-import org.mockito.Mockito;
-import org.skife.jdbi.v2.IDBI;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.platform.test.glue.TestPlatformModuleNoDB;
 
 public class GuicyKillbillTestNoDBModule extends GuicyKillbillTestModule {
 
-    private void installDBI() {
-        final IDBI idbi = Mockito.mock(IDBI.class);
-        bind(IDBI.class).toInstance(idbi);
+    public GuicyKillbillTestNoDBModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     @Override
     protected void configure() {
         super.configure();
-        installDBI();
+
+        install(new TestPlatformModuleNoDB(configSource));
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java
index 4eba0b3..7debdc2 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuite.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -21,15 +23,19 @@ import java.lang.reflect.Method;
 import javax.inject.Inject;
 
 import org.killbill.billing.callcontext.InternalCallContext;
-import org.killbill.billing.util.KillbillConfigSource;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.platform.test.config.TestKillbillConfigSource;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.clock.ClockMock;
+import org.skife.config.ConfigSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.ITestResult;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 
+import com.google.common.collect.ImmutableMap;
+
 public class GuicyKillbillTestSuite {
 
     // Use the simple name here to save screen real estate
@@ -49,10 +55,21 @@ public class GuicyKillbillTestSuite {
     private static final ClockMock theStaticClock = new ClockMock();
 
     protected final KillbillConfigSource configSource;
+    protected final ConfigSource skifeConfigSource;
 
     public GuicyKillbillTestSuite() {
+        this.configSource = getConfigSource();
+        this.skifeConfigSource = new ConfigSource() {
+            @Override
+            public String getString(final String propertyName) {
+                return configSource.getString(propertyName);
+            }
+        };
+    }
+
+    protected KillbillConfigSource getConfigSource() {
         try {
-            this.configSource = getConfigSource();
+            return new TestKillbillConfigSource(DBTestingHelper.class);
         } catch (final Exception e) {
             final AssertionError assertionError = new AssertionError("Initialization error");
             assertionError.initCause(e);
@@ -60,8 +77,18 @@ public class GuicyKillbillTestSuite {
         }
     }
 
-    protected KillbillConfigSource getConfigSource() throws Exception {
-        return new TestKillbillConfigSource();
+    protected KillbillConfigSource getConfigSource(final String file) {
+        return getConfigSource(file, ImmutableMap.<String, String>of());
+    }
+
+    protected KillbillConfigSource getConfigSource(final String file, final ImmutableMap<String, String> extraProperties) {
+        try {
+            return new TestKillbillConfigSource(file, DBTestingHelper.class, extraProperties);
+        } catch (final Exception e) {
+            final AssertionError assertionError = new AssertionError("Initialization error");
+            assertionError.initCause(e);
+            throw assertionError;
+        }
     }
 
     public static ClockMock getClock() {
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
index 29516c0..eac77a7 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
@@ -19,6 +19,7 @@ package org.killbill.billing;
 import javax.inject.Inject;
 import javax.sql.DataSource;
 
+import org.killbill.commons.embeddeddb.EmbeddedDB;
 import org.skife.jdbi.v2.IDBI;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -26,8 +27,6 @@ import org.testng.annotations.AfterSuite;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.BeforeSuite;
 
-import org.killbill.commons.embeddeddb.EmbeddedDB;
-
 public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite {
 
     private static final Logger log = LoggerFactory.getLogger(GuicyKillbillTestSuiteWithEmbeddedDB.class);
@@ -43,13 +42,13 @@ public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
 
     @BeforeSuite(groups = "slow")
     public void beforeSuite() throws Exception {
-        DBTestingHelper.start();
+        DBTestingHelper.get().start();
     }
 
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         try {
-            DBTestingHelper.get().cleanupAllTables();
+            DBTestingHelper.get().getInstance().cleanupAllTables();
         } catch (final Exception ignored) {
         }
     }
@@ -59,13 +58,13 @@ public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
         if (hasFailed()) {
             log.error("**********************************************************************************************");
             log.error("*** TESTS HAVE FAILED - LEAVING DB RUNNING FOR DEBUGGING - MAKE SURE TO KILL IT ONCE DONE ****");
-            log.error(DBTestingHelper.get().getCmdLineConnectionString());
+            log.error(DBTestingHelper.get().getInstance().getCmdLineConnectionString());
             log.error("**********************************************************************************************");
             return;
         }
 
         try {
-            DBTestingHelper.get().stop();
+            DBTestingHelper.get().getInstance().stop();
         } catch (final Exception ignored) {
         }
     }
diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestWithEmbeddedDBModule.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestWithEmbeddedDBModule.java
index d459d87..bb6516e 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestWithEmbeddedDBModule.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestWithEmbeddedDBModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,29 +18,39 @@
 
 package org.killbill.billing;
 
-import java.io.IOException;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.platform.test.config.TestKillbillConfigSource;
+import org.killbill.billing.platform.test.glue.TestPlatformModuleWithEmbeddedDB;
 
-import javax.sql.DataSource;
+public class GuicyKillbillTestWithEmbeddedDBModule extends GuicyKillbillTestModule {
 
-import org.skife.jdbi.v2.IDBI;
-import org.testng.Assert;
+    private final boolean withOSGI;
 
-import org.killbill.commons.embeddeddb.EmbeddedDB;
+    public GuicyKillbillTestWithEmbeddedDBModule(final KillbillConfigSource configSource) {
+        this(false, configSource);
+    }
 
-public class GuicyKillbillTestWithEmbeddedDBModule extends GuicyKillbillTestModule {
+    public GuicyKillbillTestWithEmbeddedDBModule(final boolean withOSGI, final KillbillConfigSource configSource) {
+        super(configSource);
+        this.withOSGI = withOSGI;
+    }
 
     @Override
     protected void configure() {
         super.configure();
 
-        final EmbeddedDB instance = DBTestingHelper.get();
-        bind(EmbeddedDB.class).toInstance(instance);
+        install(new KillbillTestPlatformModuleWithEmbeddedDB(configSource));
+    }
+
+    private final class KillbillTestPlatformModuleWithEmbeddedDB extends TestPlatformModuleWithEmbeddedDB {
+
+        public KillbillTestPlatformModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
+            super(configSource, withOSGI, (TestKillbillConfigSource) configSource);
+        }
 
-        try {
-            bind(DataSource.class).toInstance(DBTestingHelper.get().getDataSource());
-            bind(IDBI.class).toInstance(DBTestingHelper.getDBI());
-        } catch (final IOException e) {
-            Assert.fail(e.toString());
+        protected void configureEmbeddedDB() {
+            final DBTestingHelper dbTestingHelper = DBTestingHelper.get();
+            configureEmbeddedDB(dbTestingHelper);
         }
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/KillbillTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/KillbillTestSuiteWithEmbeddedDB.java
index 14a2123..f169091 100644
--- a/util/src/test/java/org/killbill/billing/KillbillTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/KillbillTestSuiteWithEmbeddedDB.java
@@ -20,7 +20,6 @@ import java.io.IOException;
 import java.net.URISyntaxException;
 import java.sql.SQLException;
 
-import org.skife.jdbi.v2.IDBI;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.annotations.AfterSuite;
@@ -33,13 +32,13 @@ public class KillbillTestSuiteWithEmbeddedDB extends KillbillTestSuite {
 
     @BeforeSuite(groups = "slow")
     public void startMysqlBeforeTestSuite() throws IOException, ClassNotFoundException, SQLException, URISyntaxException {
-        DBTestingHelper.start();
+        DBTestingHelper.get().start();
     }
 
     @BeforeMethod(groups = "slow")
     public void cleanupTablesBetweenMethods() {
         try {
-            DBTestingHelper.get().cleanupAllTables();
+            DBTestingHelper.get().getInstance().cleanupAllTables();
         } catch (final Exception ignored) {
         }
     }
@@ -49,13 +48,13 @@ public class KillbillTestSuiteWithEmbeddedDB extends KillbillTestSuite {
         if (hasFailed()) {
             log.error("**********************************************************************************************");
             log.error("*** TESTS HAVE FAILED - LEAVING DB RUNNING FOR DEBUGGING - MAKE SURE TO KILL IT ONCE DONE ****");
-            log.error(DBTestingHelper.get().getCmdLineConnectionString());
+            log.error(DBTestingHelper.get().getInstance().getCmdLineConnectionString());
             log.error("**********************************************************************************************");
             return;
         }
 
         try {
-            DBTestingHelper.get().stop();
+            DBTestingHelper.get().getInstance().stop();
         } catch (final Exception ignored) {
         }
     }
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockAccountModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockAccountModule.java
index 919760e..5efae87 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockAccountModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockAccountModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,15 +18,18 @@
 
 package org.killbill.billing.mock.glue;
 
-import org.mockito.Mockito;
-
+import org.killbill.billing.account.api.AccountInternalApi;
 import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.glue.AccountModule;
-import org.killbill.billing.account.api.AccountInternalApi;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.mockito.Mockito;
 
-import com.google.inject.AbstractModule;
+public class MockAccountModule extends KillBillModule implements AccountModule {
 
-public class MockAccountModule extends AbstractModule implements AccountModule {
+    public MockAccountModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
@@ -32,7 +37,6 @@ public class MockAccountModule extends AbstractModule implements AccountModule {
         installInternalApi();
     }
 
-
     @Override
     public void installAccountUserApi() {
         bind(AccountUserApi.class).toInstance(Mockito.mock(AccountUserApi.class));
@@ -42,5 +46,4 @@ public class MockAccountModule extends AbstractModule implements AccountModule {
     public void installInternalApi() {
         bind(AccountInternalApi.class).toInstance(Mockito.mock(AccountInternalApi.class));
     }
-
 }
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockClockModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockClockModule.java
index a0a3f32..4da8e10 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockClockModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockClockModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,18 +18,20 @@
 
 package org.killbill.billing.mock.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.clock.Clock;
 import org.killbill.clock.ClockMock;
 
-import com.google.inject.AbstractModule;
+public class MockClockModule extends KillBillModule {
 
-
-public class MockClockModule extends AbstractModule {
+    public MockClockModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
         bind(Clock.class).to(ClockMock.class).asEagerSingleton();
         bind(ClockMock.class).asEagerSingleton();
     }
-
 }
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java
index 6c7c927..7387ab3 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockEntitlementModule.java
@@ -1,11 +1,13 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
- *   http://www.apache.org/licenses/LICENSE-2.0
+ *    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
@@ -16,23 +18,26 @@
 
 package org.killbill.billing.mock.glue;
 
-import org.mockito.Mockito;
-
 import org.killbill.billing.entitlement.EntitlementInternalApi;
 import org.killbill.billing.entitlement.api.EntitlementApi;
 import org.killbill.billing.entitlement.api.SubscriptionApi;
 import org.killbill.billing.glue.EntitlementModule;
 import org.killbill.billing.junction.BlockingInternalApi;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.mockito.Mockito;
 
-import com.google.inject.AbstractModule;
-
-public class MockEntitlementModule extends AbstractModule implements EntitlementModule {
+public class MockEntitlementModule extends KillBillModule implements EntitlementModule {
 
     private final BlockingInternalApi blockingApi = Mockito.mock(BlockingInternalApi.class);
     private final EntitlementApi entitlementApi = Mockito.mock(EntitlementApi.class);
     private final EntitlementInternalApi entitlementInternalApi = Mockito.mock(EntitlementInternalApi.class);
     private final SubscriptionApi subscriptionApi = Mockito.mock(SubscriptionApi.class);
 
+    public MockEntitlementModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
+
     @Override
     protected void configure() {
         installBlockingStateDao();
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockGlobalLockerModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockGlobalLockerModule.java
index ca9ec95..c0401af 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockGlobalLockerModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockGlobalLockerModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,12 +18,16 @@
 
 package org.killbill.billing.mock.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.memory.MemoryGlobalLocker;
 
-import com.google.inject.AbstractModule;
+public class MockGlobalLockerModule extends KillBillModule {
 
-public class MockGlobalLockerModule extends AbstractModule {
+    public MockGlobalLockerModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockInvoiceModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockInvoiceModule.java
index f1b25ef..755817c 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockInvoiceModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockInvoiceModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,17 +18,20 @@
 
 package org.killbill.billing.mock.glue;
 
-import org.mockito.Mockito;
-
 import org.killbill.billing.glue.InvoiceModule;
+import org.killbill.billing.invoice.api.InvoiceInternalApi;
 import org.killbill.billing.invoice.api.InvoiceMigrationApi;
 import org.killbill.billing.invoice.api.InvoicePaymentApi;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
-import org.killbill.billing.invoice.api.InvoiceInternalApi;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.mockito.Mockito;
 
-import com.google.inject.AbstractModule;
+public class MockInvoiceModule extends KillBillModule implements InvoiceModule {
 
-public class MockInvoiceModule extends AbstractModule implements InvoiceModule {
+    public MockInvoiceModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     public void installInvoiceUserApi() {
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockJunctionModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockJunctionModule.java
index fac5a03..813c8d2 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockJunctionModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockJunctionModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,20 @@
 
 package org.killbill.billing.mock.glue;
 
-import com.google.inject.AbstractModule;
 import org.killbill.billing.glue.JunctionModule;
 import org.killbill.billing.junction.BillingInternalApi;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
 import org.mockito.Mockito;
 
-public class MockJunctionModule extends AbstractModule implements JunctionModule {
+public class MockJunctionModule extends KillBillModule implements JunctionModule {
+
     private final BillingInternalApi billingApi = Mockito.mock(BillingInternalApi.class);
 
+    public MockJunctionModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
+
     @Override
     protected void configure() {
         installBillingApi();
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockNonEntityDaoModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockNonEntityDaoModule.java
index d71574a..6616770 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockNonEntityDaoModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockNonEntityDaoModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,19 +18,16 @@
 
 package org.killbill.billing.mock.glue;
 
-import java.util.UUID;
-
-import javax.annotation.Nullable;
-
-import org.killbill.billing.ObjectType;
 import org.killbill.billing.dao.MockNonEntityDao;
-import org.killbill.billing.util.cache.CacheController;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.dao.NonEntityDao;
-import org.killbill.billing.util.dao.TableName;
+import org.killbill.billing.util.glue.KillBillModule;
 
-import com.google.inject.AbstractModule;
+public class MockNonEntityDaoModule extends KillBillModule {
 
-public class MockNonEntityDaoModule extends AbstractModule {
+    public MockNonEntityDaoModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java
index 91fc45f..a2085a9 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockOverdueModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,18 @@
 
 package org.killbill.billing.mock.glue;
 
-import org.mockito.Mockito;
-
 import org.killbill.billing.glue.OverdueModule;
 import org.killbill.billing.overdue.OverdueUserApi;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.mockito.Mockito;
 
-import com.google.inject.AbstractModule;
+public class MockOverdueModule extends KillBillModule implements OverdueModule {
+
+    public MockOverdueModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
-public class MockOverdueModule extends AbstractModule implements OverdueModule {
     @Override
     public void installOverdueUserApi() {
         bind(OverdueUserApi.class).toInstance(Mockito.mock(OverdueUserApi.class));
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockPaymentModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockPaymentModule.java
index e4af15f..1175270 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockPaymentModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockPaymentModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,14 +18,17 @@
 
 package org.killbill.billing.mock.glue;
 
-import org.mockito.Mockito;
-
 import org.killbill.billing.payment.api.PaymentApi;
 import org.killbill.billing.payment.api.PaymentInternalApi;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.mockito.Mockito;
 
-import com.google.inject.AbstractModule;
+public class MockPaymentModule extends KillBillModule {
 
-public class MockPaymentModule extends AbstractModule {
+    public MockPaymentModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     protected void configure() {
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockSubscriptionModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockSubscriptionModule.java
index ddf4617..78bab07 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockSubscriptionModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockSubscriptionModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2012 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,25 +18,27 @@
 
 package org.killbill.billing.mock.glue;
 
-import org.mockito.Mockito;
-
 import org.killbill.billing.glue.SubscriptionModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseService;
 import org.killbill.billing.subscription.api.migration.SubscriptionBaseMigrationApi;
 import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimelineApi;
 import org.killbill.billing.subscription.api.transfer.SubscriptionBaseTransferApi;
-import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
+import org.killbill.billing.util.glue.KillBillModule;
+import org.mockito.Mockito;
 
-import com.google.inject.AbstractModule;
+public class MockSubscriptionModule extends KillBillModule implements SubscriptionModule {
 
-public class MockSubscriptionModule extends AbstractModule implements SubscriptionModule {
+    public MockSubscriptionModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
 
     @Override
     public void installSubscriptionService() {
         bind(SubscriptionBaseService.class).toInstance(Mockito.mock(SubscriptionBaseService.class));
     }
 
-
     @Override
     public void installSubscriptionMigrationApi() {
         bind(SubscriptionBaseMigrationApi.class).toInstance(Mockito.mock(SubscriptionBaseMigrationApi.class));
@@ -62,6 +66,5 @@ public class MockSubscriptionModule extends AbstractModule implements Subscripti
     @Override
     public void installSubscriptionTransferApi() {
         bind(SubscriptionBaseTransferApi.class).toInstance(Mockito.mock(SubscriptionBaseTransferApi.class));
-
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/mock/glue/MockTagModule.java b/util/src/test/java/org/killbill/billing/mock/glue/MockTagModule.java
index b9e7e01..6f77943 100644
--- a/util/src/test/java/org/killbill/billing/mock/glue/MockTagModule.java
+++ b/util/src/test/java/org/killbill/billing/mock/glue/MockTagModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,6 +18,7 @@
 
 package org.killbill.billing.mock.glue;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.glue.TagStoreModule;
 import org.killbill.billing.util.tag.dao.MockTagDao;
 import org.killbill.billing.util.tag.dao.MockTagDefinitionDao;
@@ -24,6 +27,10 @@ import org.killbill.billing.util.tag.dao.TagDefinitionDao;
 
 public class MockTagModule extends TagStoreModule {
 
+    public MockTagModule(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
+
     @Override
     protected void installDaos() {
         bind(TagDefinitionDao.class).to(MockTagDefinitionDao.class).asEagerSingleton();
diff --git a/util/src/test/java/org/killbill/billing/payment/api/TestPaymentMethodPluginBase.java b/util/src/test/java/org/killbill/billing/payment/api/TestPaymentMethodPluginBase.java
index 8e96363..37948af 100644
--- a/util/src/test/java/org/killbill/billing/payment/api/TestPaymentMethodPluginBase.java
+++ b/util/src/test/java/org/killbill/billing/payment/api/TestPaymentMethodPluginBase.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -39,67 +41,7 @@ public class TestPaymentMethodPluginBase implements PaymentMethodPlugin {
     }
 
     @Override
-    public String getType() {
-        return "CreditCard";
-    }
-
-    @Override
-    public String getCCName() {
-        return "Bozo";
-    }
-
-    @Override
-    public String getCCType() {
-        return "Visa";
-    }
-
-    @Override
-    public String getCCExpirationMonth() {
-        return "12";
-    }
-
-    @Override
-    public String getCCExpirationYear() {
-        return "2013";
-    }
-
-    @Override
-    public String getCCLast4() {
-        return "4365";
-    }
-
-    @Override
-    public String getAddress1() {
-        return "34, street Foo";
-    }
-
-    @Override
-    public String getAddress2() {
-        return null;
-    }
-
-    @Override
-    public String getCity() {
-        return "SF";
-    }
-
-    @Override
-    public String getState() {
-        return "CA";
-    }
-
-    @Override
-    public String getZip() {
-        return "95321";
-    }
-
-    @Override
-    public String getCountry() {
-        return "Zimbawe";
-    }
-
-    @Override
-    public List<PaymentMethodKVInfo> getProperties() {
-        return ImmutableList.<PaymentMethodKVInfo>of();
+    public List<PluginProperty> getProperties() {
+        return ImmutableList.<PluginProperty>of();
     }
 }
diff --git a/util/src/test/java/org/killbill/billing/util/cache/TestCache.java b/util/src/test/java/org/killbill/billing/util/cache/TestCache.java
index 0a797a9..a65cf7e 100644
--- a/util/src/test/java/org/killbill/billing/util/cache/TestCache.java
+++ b/util/src/test/java/org/killbill/billing/util/cache/TestCache.java
@@ -18,33 +18,23 @@ package org.killbill.billing.util.cache;
 
 import java.util.UUID;
 
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.ObjectType;
 import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
 import org.killbill.billing.util.cache.Cachable.CacheType;
+import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
 import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 import org.killbill.billing.util.tag.dao.TagModelDao;
 import org.killbill.billing.util.tag.dao.TagSqlDao;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestCache extends UtilTestSuiteWithEmbeddedDB {
 
     private EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
 
-    private void insertTag(final TagModelDao modelDao) {
-        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
-            @Override
-            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
-                entitySqlDaoWrapperFactory.become(TagSqlDao.class).create(modelDao, internalCallContext);
-                return null;
-            }
-        });
-    }
-
     private Long getTagRecordId(final UUID tagId) {
         return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Long>() {
             @Override
@@ -54,8 +44,8 @@ public class TestCache extends UtilTestSuiteWithEmbeddedDB {
         });
     }
 
-    private int getCacheSize() {
-        final CacheController<Object, Object> cache = controlCacheDispatcher.getCacheController(CacheType.RECORD_ID);
+    private int getCacheSize(CacheType cacheType) {
+        final CacheController<Object, Object> cache = controlCacheDispatcher.getCacheController(cacheType);
         return cache != null ? cache.size() : 0;
     }
 
@@ -75,22 +65,79 @@ public class TestCache extends UtilTestSuiteWithEmbeddedDB {
         final TagModelDao tag = new TagModelDao(clock.getUTCNow(), UUID.randomUUID(), UUID.randomUUID(), ObjectType.TAG);
 
         // Verify we start with nothing in the cache
-        Assert.assertEquals(getCacheSize(), 0);
+        Assert.assertEquals(getCacheSize(CacheType.RECORD_ID), 0);
         insertTag(tag);
 
         // Verify we still have nothing after insert in the cache
-        Assert.assertEquals(getCacheSize(), 0);
+        Assert.assertEquals(getCacheSize(CacheType.RECORD_ID), 0);
 
         final Long tagRecordId = getTagRecordId(tag.getId());
         // Verify we now have something  in the cache
-        Assert.assertEquals(getCacheSize(), 1);
+        Assert.assertEquals(getCacheSize(CacheType.RECORD_ID), 1);
 
         final Long recordIdFromCache = retrieveRecordIdFromCache(tag.getId());
         Assert.assertNotNull(recordIdFromCache);
 
-        Assert.assertEquals(recordIdFromCache, new Long(1));
-        Assert.assertEquals(tagRecordId, new Long(1));
+        Assert.assertEquals(recordIdFromCache, tagRecordId);
+        // We cannot assume the number to be 1 here as the auto_increment implementation
+        // depends on the database.
+        // See also http://h2database.com/html/grammar.html#create_sequence
+        Assert.assertTrue(recordIdFromCache > 0);
+
+        Assert.assertEquals(getCacheSize(CacheType.RECORD_ID), 1);
+    }
+
+    @Test(groups = "slow")
+    public void testAllCachesAfterGetById() throws Exception {
+        this.transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, controlCacheDispatcher, nonEntityDao);
+        final TagModelDao tag = new TagModelDao(clock.getUTCNow(), UUID.randomUUID(), UUID.randomUUID(), ObjectType.TAG);
+
+        insertTag(tag);
+
+        // Verify we start with nothing in the cache
+        Assert.assertEquals(getCacheSize(CacheType.RECORD_ID), 0);
+        Assert.assertEquals(getCacheSize(CacheType.ACCOUNT_RECORD_ID), 0);
+        Assert.assertEquals(getCacheSize(CacheType.TENANT_RECORD_ID), 0);
+        Assert.assertEquals(getCacheSize(CacheType.OBJECT_ID), 0);
+
+        final TagModelDao result = getById(tag.getId());
+
+        Assert.assertEquals(getCacheSize(CacheType.RECORD_ID), 1);
+        Assert.assertEquals(getCacheSize(CacheType.ACCOUNT_RECORD_ID), 1);
+        Assert.assertEquals(getCacheSize(CacheType.TENANT_RECORD_ID), 1);
+        Assert.assertEquals(getCacheSize(CacheType.OBJECT_ID), 1);
+
+        final Long recordId = (Long) controlCacheDispatcher.getCacheController(CacheType.RECORD_ID).get(tag.getId().toString(), new CacheLoaderArgument(ObjectType.TAG));
+        Assert.assertEquals(recordId, result.getRecordId());
+
+        final Long tenantRecordId = (Long) controlCacheDispatcher.getCacheController(CacheType.TENANT_RECORD_ID).get(tag.getId().toString(), new CacheLoaderArgument(ObjectType.TAG));
+        Assert.assertEquals(tenantRecordId, result.getTenantRecordId());
+
+        final UUID objectId = (UUID) controlCacheDispatcher.getCacheController(CacheType.OBJECT_ID).get(TableName.TAG + CacheControllerDispatcher.CACHE_KEY_SEPARATOR  + recordId, new CacheLoaderArgument(ObjectType.TAG));
+        Assert.assertEquals(objectId, result.getId());
+
+        final Long accountRecordId = (Long) controlCacheDispatcher.getCacheController(CacheType.ACCOUNT_RECORD_ID).get(tag.getId().toString(), new CacheLoaderArgument(ObjectType.TAG));
+        Assert.assertEquals(accountRecordId, result.getAccountRecordId());
+
+    }
+
+    private void insertTag(final TagModelDao modelDao) {
+        transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<Void>() {
+            @Override
+            public Void inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                entitySqlDaoWrapperFactory.become(TagSqlDao.class).create(modelDao, internalCallContext);
+                return null;
+            }
+        });
+    }
 
-        Assert.assertEquals(getCacheSize(), 1);
+    private TagModelDao getById(final UUID id) {
+        return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<TagModelDao>() {
+            @Override
+            public TagModelDao inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(TagSqlDao.class).getById(id.toString(), internalCallContext);
+            }
+        });
     }
+
 }
diff --git a/util/src/test/java/org/killbill/billing/util/customfield/MockCustomFieldModuleMemory.java b/util/src/test/java/org/killbill/billing/util/customfield/MockCustomFieldModuleMemory.java
index 5a6ed0a..026948a 100644
--- a/util/src/test/java/org/killbill/billing/util/customfield/MockCustomFieldModuleMemory.java
+++ b/util/src/test/java/org/killbill/billing/util/customfield/MockCustomFieldModuleMemory.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,11 +18,17 @@
 
 package org.killbill.billing.util.customfield;
 
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.customfield.dao.CustomFieldDao;
 import org.killbill.billing.util.customfield.dao.MockCustomFieldDao;
 import org.killbill.billing.util.glue.CustomFieldModule;
 
 public class MockCustomFieldModuleMemory extends CustomFieldModule {
+
+    public MockCustomFieldModuleMemory(final KillbillConfigSource configSource) {
+        super(configSource);
+    }
+
     @Override
     protected void installCustomFieldDao() {
         bind(CustomFieldDao.class).to(MockCustomFieldDao.class).asEagerSingleton();
diff --git a/util/src/test/java/org/killbill/billing/util/email/EmailSenderTest.java b/util/src/test/java/org/killbill/billing/util/email/EmailSenderTest.java
index d706daa..0a3f4c7 100644
--- a/util/src/test/java/org/killbill/billing/util/email/EmailSenderTest.java
+++ b/util/src/test/java/org/killbill/billing/util/email/EmailSenderTest.java
@@ -1,7 +1,9 @@
-package org.killbill.billing.util.email;/*
+/*
  * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -14,6 +16,8 @@ package org.killbill.billing.util.email;/*
  * under the License.
  */
 
+package org.killbill.billing.util.email;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -30,7 +34,7 @@ public class EmailSenderTest extends UtilTestSuiteNoDB {
     @BeforeClass
     public void beforeClass() throws Exception {
         super.beforeClass();
-        config = new ConfigurationObjectFactory(configSource).build(EmailConfig.class);
+        config = new ConfigurationObjectFactory(skifeConfigSource).build(EmailConfig.class);
     }
 
     @Test(enabled = false)
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModule.java b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModule.java
index 78593de..08c2b2e 100644
--- a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModule.java
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModule.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,19 +18,14 @@
 
 package org.killbill.billing.util.glue;
 
-import org.mockito.Mockito;
-import org.skife.config.ConfigSource;
-
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.subscription.api.timeline.SubscriptionBaseTimelineApi;
+import org.mockito.Mockito;
 
-import com.google.inject.AbstractModule;
-
-public class TestUtilModule extends AbstractModule {
-
-    protected final ConfigSource configSource;
+public class TestUtilModule extends KillBillModule {
 
-    public TestUtilModule(final ConfigSource configSource) {
-        this.configSource = configSource;
+    public TestUtilModule(final KillbillConfigSource configSource) {
+        super(configSource);
     }
 
     // TODO STEPH this is bad-- because DefaultAuditUserApi is using SubscriptionBaseTimeline API
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java
index adb872f..bb32ac1 100644
--- a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,21 +18,18 @@
 
 package org.killbill.billing.util.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.GuicyKillbillTestNoDBModule;
 import org.killbill.billing.mock.glue.MockGlobalLockerModule;
 import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
-import org.killbill.billing.mock.glue.MockNotificationQueueModule;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.audit.api.DefaultAuditUserApi;
 import org.killbill.billing.util.audit.dao.AuditDao;
 import org.killbill.billing.util.audit.dao.MockAuditDao;
-import org.killbill.billing.util.bus.InMemoryBusModule;
 
 public class TestUtilModuleNoDB extends TestUtilModule {
 
-    public TestUtilModuleNoDB(final ConfigSource configSource) {
+    public TestUtilModuleNoDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
@@ -42,12 +41,10 @@ public class TestUtilModuleNoDB extends TestUtilModule {
     @Override
     protected void configure() {
         super.configure();
-        install(new GuicyKillbillTestNoDBModule());
+        install(new GuicyKillbillTestNoDBModule(configSource));
 
-        install(new MockNonEntityDaoModule());
-        install(new MockGlobalLockerModule());
-        install(new InMemoryBusModule(configSource));
-        install(new MockNotificationQueueModule(configSource));
+        install(new MockNonEntityDaoModule(configSource));
+        install(new MockGlobalLockerModule(configSource));
 
         installAuditMock();
 
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java
index 76c37f8..9d3837b 100644
--- a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
  * License.  You may obtain a copy of the License at:
  *
@@ -16,31 +18,27 @@
 
 package org.killbill.billing.util.glue;
 
-import org.skife.config.ConfigSource;
-
 import org.killbill.billing.DBTestingHelper;
 import org.killbill.billing.GuicyKillbillTestWithEmbeddedDBModule;
 import org.killbill.billing.api.TestApiListener;
+import org.killbill.billing.platform.api.KillbillConfigSource;
 
 public class TestUtilModuleWithEmbeddedDB extends TestUtilModule {
 
-    public TestUtilModuleWithEmbeddedDB(final ConfigSource configSource) {
+    public TestUtilModuleWithEmbeddedDB(final KillbillConfigSource configSource) {
         super(configSource);
     }
 
     @Override
     protected void configure() {
         super.configure();
-        install(new GuicyKillbillTestWithEmbeddedDBModule());
+        install(new GuicyKillbillTestWithEmbeddedDBModule(configSource));
 
-        install(new AuditModule());
-        install(new TagStoreModule());
-        install(new CustomFieldModule());
-        install(new MetricsModule());
-        install(new BusModule(configSource));
-        install(new NotificationQueueModule(configSource));
-        install(new NonEntityDaoModule());
-        install(new GlobalLockerModule(DBTestingHelper.get().getDBEngine()));
+        install(new AuditModule(configSource));
+        install(new TagStoreModule(configSource));
+        install(new CustomFieldModule(configSource));
+        install(new NonEntityDaoModule(configSource));
+        install(new GlobalLockerModule(DBTestingHelper.get().getInstance().getDBEngine(), configSource));
 
         bind(TestApiListener.class).asEagerSingleton();
     }
diff --git a/util/src/test/java/org/killbill/billing/util/security/shiro/dao/TestJDBCSessionDao.java b/util/src/test/java/org/killbill/billing/util/security/shiro/dao/TestJDBCSessionDao.java
index 45a424f..c8da341 100644
--- a/util/src/test/java/org/killbill/billing/util/security/shiro/dao/TestJDBCSessionDao.java
+++ b/util/src/test/java/org/killbill/billing/util/security/shiro/dao/TestJDBCSessionDao.java
@@ -22,15 +22,26 @@ import java.util.UUID;
 
 import org.apache.shiro.session.Session;
 import org.apache.shiro.session.mgt.SimpleSession;
-import org.skife.jdbi.v2.DBI;
+import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
-import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB;
-
 public class TestJDBCSessionDao extends UtilTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
+    public void testH2AndInvalidSessionId() {
+        final JDBCSessionDao jdbcSessionDao = new JDBCSessionDao(dbi);
+
+        // We need to create some data to force H2 to build the query
+        // (otherwise, the read path is optimized and the bug is not triggered)
+        final SimpleSession session = createSession();
+        jdbcSessionDao.doCreate(session);
+
+        // Make sure this doesn't throw any exception on H2
+        Assert.assertNull(jdbcSessionDao.doReadSession(UUID.randomUUID()));
+    }
+
+    @Test(groups = "slow")
     public void testCRUD() throws Exception {
         // Note! We are testing the do* methods here to bypass the caching layer
         final JDBCSessionDao jdbcSessionDao = new JDBCSessionDao(dbi);