killbill-memoizeit

Details

.gitignore 1(+1 -0)

diff --git a/.gitignore b/.gitignore
index 395361e..9e14eea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .idea/workspace.xml
+.idea/libraries
 *.ipr
 *.iws
 *.DS_Store
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index eb26739..65848da 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -19,6 +19,7 @@ package com.ning.billing.jaxrs.resources;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
@@ -433,6 +434,7 @@ public class AccountResource extends JaxRsResourceBase {
     public Response createPaymentMethod(final PaymentMethodJson json,
                                         @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,
                                         @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                         @HeaderParam(HDR_REASON) final String reason,
                                         @HeaderParam(HDR_COMMENT) final String comment,
@@ -443,7 +445,19 @@ public class AccountResource extends JaxRsResourceBase {
         final PaymentMethod data = json.toPaymentMethod(accountId);
         final Account account = accountUserApi.getAccountById(data.getAccountId(), callContext);
 
+        final boolean hasDefaultPaymentMethod = account.getPaymentMethodId() != null || isDefault;
+        final Collection<Invoice> unpaidInvoices = payAllUnpaidInvoices ? invoiceApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext) :
+                                                   Collections.<Invoice>emptyList();
+        if (payAllUnpaidInvoices && unpaidInvoices.size() > 0 && !hasDefaultPaymentMethod) {
+            return Response.status(Status.BAD_REQUEST).build();
+        }
+
         final UUID paymentMethodId = paymentApi.addPaymentMethod(data.getPluginName(), account, isDefault, data.getPluginDetail(), callContext);
+        if (payAllUnpaidInvoices && unpaidInvoices.size() > 0) {
+            for (final Invoice invoice : unpaidInvoices) {
+                paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+            }
+        }
         return uriBuilder.buildResponse(PaymentMethodResource.class, "getPaymentMethod", paymentMethodId, uriInfo.getBaseUri().toString());
     }
 
@@ -473,6 +487,7 @@ public class AccountResource extends JaxRsResourceBase {
     @Path("/{accountId:" + UUID_PATTERN + "}/" + PAYMENT_METHODS + "/{paymentMethodId:" + UUID_PATTERN + "}/" + PAYMENT_METHODS_DEFAULT_PATH_POSTFIX)
     public Response setDefaultPaymentMethod(@PathParam("accountId") final String accountId,
                                             @PathParam("paymentMethodId") final String paymentMethodId,
+                                            @QueryParam(QUERY_PAY_ALL_UNPAID_INVOICES) @DefaultValue("false") final Boolean payAllUnpaidInvoices,
                                             @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                             @HeaderParam(HDR_REASON) final String reason,
                                             @HeaderParam(HDR_COMMENT) final String comment,
@@ -481,6 +496,13 @@ public class AccountResource extends JaxRsResourceBase {
 
         final Account account = accountUserApi.getAccountById(UUID.fromString(accountId), callContext);
         paymentApi.setDefaultPaymentMethod(account, UUID.fromString(paymentMethodId), 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);
+            }
+        }
         return Response.status(Status.OK).build();
     }
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index 9c595e1..6f43c9a 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -253,15 +253,18 @@ public class InvoiceResource extends JaxRsResourceBase {
     @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 {
+                                         @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);
-
+        if (payInvoice && account.getPaymentMethodId() == null) {
+            return Response.status(Status.BAD_REQUEST).build();
+        }
         // Get the effective date of the external charge, in the account timezone
         final LocalDate requestedDate = toLocalDate(account, requestedDateTimeString, callContext);
 
@@ -277,6 +280,10 @@ public class InvoiceResource extends JaxRsResourceBase {
                                                              currency, callContext);
         }
 
+        if (payInvoice) {
+            final Invoice invoice = invoiceApi.getInvoice(externalCharge.getInvoiceId(), callContext);
+            paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+        }
         return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
     }
 
@@ -287,14 +294,18 @@ public class InvoiceResource extends JaxRsResourceBase {
     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 {
+                                                   @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);
+        if (payInvoice && account.getPaymentMethodId() == null) {
+            return Response.status(Status.BAD_REQUEST).build();
+        }
 
         // Get the effective date of the external charge, in the account timezone
         final LocalDate requestedDate = toLocalDate(account, requestedDateTimeString, callContext);
@@ -312,6 +323,11 @@ public class InvoiceResource extends JaxRsResourceBase {
                                                                        requestedDate, currency, callContext);
         }
 
+        if (payInvoice) {
+            final Invoice invoice = invoiceApi.getInvoice(externalCharge.getInvoiceId(), callContext);
+            paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), callContext);
+        }
+
         return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
     }
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
index bf2915a..0f1e2d1 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/JaxrsResource.java
@@ -80,6 +80,9 @@ public interface JaxrsResource {
     public static final String QUERY_PAYMENT_METHOD_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_BUNDLE_TRANSFER_ADDON = "transferAddOn";
     public static final String QUERY_BUNDLE_TRANSFER_CANCEL_IMM = "cancelImmediately";
 
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
index 2d4bb21..ee412f3 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
@@ -118,6 +118,8 @@ public class OverdueStateApplicator {
                 createFutureNotification(overdueable, clock.getUTCNow().plus(reevaluationInterval), context);
 
                 log.debug("OverdueStateApplicator <notificationQ> : inserting notification for time = " + clock.getUTCNow().plus(reevaluationInterval));
+            } else if (nextOverdueState.isClearState()) {
+                clearFutureNotification(overdueable, context);
             }
 
             if (previousOverdueStateName.equals(nextOverdueState.getName())) {
@@ -136,9 +138,6 @@ public class OverdueStateApplicator {
             }
         }
 
-        if (nextOverdueState.isClearState()) {
-            clearFutureNotification(overdueable, context);
-        }
 
         try {
             bus.post(createOverdueEvent(overdueable, previousOverdueStateName, nextOverdueState.getName(), context));
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
index b7d1296..bfc5550 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
@@ -75,6 +75,8 @@ import static com.ning.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
 
 public class PaymentProcessor extends ProcessorBase {
 
+    private static final UUID MISSING_PAYMENT_METHOD_ID = UUID.fromString("99999999-dead-beef-babe-999999999999");
+
     private final PaymentMethodProcessor paymentMethodProcessor;
     private final FailedPaymentRetryServiceScheduler failedPaymentRetryService;
     private final PluginFailureRetryServiceScheduler pluginFailureRetryService;
@@ -258,6 +260,10 @@ public class PaymentProcessor extends ProcessorBase {
                                         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).
@@ -438,6 +444,20 @@ public class PaymentProcessor extends ProcessorBase {
         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);
+
+        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.setScale(2, RoundingMode.HALF_UP), invoice.getCurrency(), clock.getUTCNow());
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
index 7193cb9..1587ee6 100644
--- a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApi.java
@@ -17,93 +17,45 @@
 package com.ning.billing.payment.api;
 
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 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.Assert;
 import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
+import com.ning.billing.bus.api.PersistentBus.EventBusException;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.payment.MockRecurringInvoiceItem;
-import com.ning.billing.payment.PaymentTestSuiteNoDB;
-import com.ning.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
-import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
+import com.ning.billing.payment.PaymentTestSuiteWithEmbeddedDB;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
+public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
-public class TestPaymentApi extends PaymentTestSuiteNoDB {
-
-    private static final Logger log = LoggerFactory.getLogger(TestPaymentApi.class);
 
     private Account account;
 
-    @BeforeClass(groups = "fast")
+    @BeforeClass(groups = "slow")
     public void beforeClass() throws Exception {
         super.beforeClass();
-        account = testHelper.createTestAccount("yoyo.yahoo.com", false);
-    }
-
-    @BeforeMethod(groups = "fast")
-    public void beforeMethod() throws Exception {
-        super.beforeMethod();
-        final PaymentMethodPlugin paymentMethodInfo = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), true, null);
-        testHelper.addTestPaymentMethod(account, paymentMethodInfo);
-    }
-
-    @Test(groups = "fast")
-    public void testSimplePaymentWithNoAmount() throws Exception {
-        final BigDecimal invoiceAmount = new BigDecimal("10.0011");
-        final BigDecimal requestedAmount = null;
-        final BigDecimal expectedAmount = invoiceAmount;
-
-        testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
+        account = testHelper.createTestAccount("bobo@gmail.com", false);
     }
 
-    @Test(groups = "fast")
-    public void testSimplePaymentWithInvoiceAmount() throws Exception {
-        final BigDecimal invoiceAmount = new BigDecimal("10.0011");
-        final BigDecimal requestedAmount = invoiceAmount;
-        final BigDecimal expectedAmount = invoiceAmount;
 
-        testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
-    }
-
-    @Test(groups = "fast")
-    public void testSimplePaymentWithLowerAmount() throws Exception {
-        final BigDecimal invoiceAmount = new BigDecimal("10.0011");
-        final BigDecimal requestedAmount = new BigDecimal("8.0091");
-        final BigDecimal expectedAmount = requestedAmount;
-
-        testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
-    }
-
-    @Test(groups = "fast")
-    public void testSimplePaymentWithInvalidAmount() throws Exception {
-        final BigDecimal invoiceAmount = new BigDecimal("10.0011");
-        final BigDecimal requestedAmount = new BigDecimal("80.0091");
-        final BigDecimal expectedAmount = null;
+    @Test(groups = "slow")
+    public void testCreatePaymentWithNoDefaultPaymentMethod() throws InvoiceApiException, EventBusException, PaymentApiException {
 
-        testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
-    }
 
-    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 UUID subscriptionId = UUID.randomUUID();
         final UUID bundleId = UUID.randomUUID();
+        final BigDecimal requestedAmount = BigDecimal.TEN;
 
         invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
                                                             subscriptionId,
@@ -111,69 +63,20 @@ public class TestPaymentApi extends PaymentTestSuiteNoDB {
                                                             "test plan", "test phase",
                                                             now,
                                                             now.plusMonths(1),
-                                                            invoiceAmount,
+                                                            requestedAmount,
                                                             new BigDecimal("1.0"),
                                                             Currency.USD));
 
         try {
-            final Payment paymentInfo = paymentApi.createPayment(account, invoice.getId(), requestedAmount, callContext);
-            if (expectedAmount == null) {
-                fail("Expected to fail because requested amount > invoice amount");
-            }
-            assertNotNull(paymentInfo.getId());
-            assertTrue(paymentInfo.getAmount().compareTo(expectedAmount.setScale(2, RoundingMode.HALF_EVEN)) == 0);
-            assertNotNull(paymentInfo.getPaymentNumber());
-            assertEquals(paymentInfo.getPaymentStatus(), PaymentStatus.SUCCESS);
-            assertEquals(paymentInfo.getAttempts().size(), 1);
-            assertEquals(paymentInfo.getInvoiceId(), invoice.getId());
-            assertEquals(paymentInfo.getCurrency(), Currency.USD);
-
-            final PaymentAttempt paymentAttempt = paymentInfo.getAttempts().get(0);
-            assertNotNull(paymentAttempt);
-            assertNotNull(paymentAttempt.getId());
+            paymentApi.createPayment(account, invoice.getId(), requestedAmount, callContext);
         } catch (PaymentApiException e) {
-            if (expectedAmount != null) {
-                fail("Failed to create payment", e);
-            } else {
-                log.info(e.getMessage());
-                assertEquals(e.getCode(), ErrorCode.PAYMENT_AMOUNT_DENIED.getCode());
-            }
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_NO_DEFAULT_PAYMENT_METHOD.getCode());
         }
-    }
-
-    @Test(groups = "fast")
-    public void testPaymentMethods() throws Exception {
-        List<PaymentMethod> methods = paymentApi.getPaymentMethods(account, false, 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);
-        Mockito.when(account.getPaymentMethodId()).thenReturn(newPaymentMethodId);
-
-        methods = paymentApi.getPaymentMethods(account, false, callContext);
-        assertEquals(methods.size(), 2);
-
-        assertEquals(newPaymentMethodId, account.getPaymentMethodId());
-
-        boolean failed = false;
-        try {
-            paymentApi.deletedPaymentMethod(account, newPaymentMethodId, false, callContext);
-        } catch (PaymentApiException e) {
-            failed = true;
-        }
-        assertTrue(failed);
-
-        paymentApi.deletedPaymentMethod(account, initDefaultMethod.getId(), true,  callContext);
-        methods = paymentApi.getPaymentMethods(account, false, callContext);
-        assertEquals(methods.size(), 1);
 
-        // NOW retry with default payment method with special flag
-        paymentApi.deletedPaymentMethod(account, newPaymentMethodId, true, callContext);
+        final List<Payment> payments = paymentApi.getAccountPayments(account.getId(), callContext);
+        Assert.assertEquals(payments.size(), 1);
 
-        methods = paymentApi.getPaymentMethods(account, false, callContext);
-        assertEquals(methods.size(), 0);
+        final Payment payment = payments.get(0);
+        Assert.assertEquals(payment.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE_ABORTED);
     }
 }
diff --git a/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApiNoDB.java b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApiNoDB.java
new file mode 100644
index 0000000..32938da
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/api/TestPaymentApiNoDB.java
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+package com.ning.billing.payment.api;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+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 com.ning.billing.ErrorCode;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.MockRecurringInvoiceItem;
+import com.ning.billing.payment.PaymentTestSuiteNoDB;
+import com.ning.billing.payment.provider.DefaultNoOpPaymentMethodPlugin;
+import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class TestPaymentApiNoDB extends PaymentTestSuiteNoDB {
+
+    private static final Logger log = LoggerFactory.getLogger(TestPaymentApiNoDB.class);
+
+    private Account account;
+
+    @BeforeClass(groups = "fast")
+    public void beforeClass() throws Exception {
+        super.beforeClass();
+        account = testHelper.createTestAccount("yoyo.yahoo.com", false);
+    }
+
+    @BeforeMethod(groups = "fast")
+    public void beforeMethod() throws Exception {
+        super.beforeMethod();
+        final PaymentMethodPlugin paymentMethodInfo = new DefaultNoOpPaymentMethodPlugin(UUID.randomUUID().toString(), true, null);
+        testHelper.addTestPaymentMethod(account, paymentMethodInfo);
+    }
+
+    @Test(groups = "fast")
+    public void testSimplePaymentWithNoAmount() throws Exception {
+        final BigDecimal invoiceAmount = new BigDecimal("10.0011");
+        final BigDecimal requestedAmount = null;
+        final BigDecimal expectedAmount = invoiceAmount;
+
+        testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
+    }
+
+    @Test(groups = "fast")
+    public void testSimplePaymentWithInvoiceAmount() throws Exception {
+        final BigDecimal invoiceAmount = new BigDecimal("10.0011");
+        final BigDecimal requestedAmount = invoiceAmount;
+        final BigDecimal expectedAmount = invoiceAmount;
+
+        testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
+    }
+
+    @Test(groups = "fast")
+    public void testSimplePaymentWithLowerAmount() throws Exception {
+        final BigDecimal invoiceAmount = new BigDecimal("10.0011");
+        final BigDecimal requestedAmount = new BigDecimal("8.0091");
+        final BigDecimal expectedAmount = requestedAmount;
+
+        testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
+    }
+
+    @Test(groups = "fast")
+    public void testSimplePaymentWithInvalidAmount() throws Exception {
+        final BigDecimal invoiceAmount = new BigDecimal("10.0011");
+        final BigDecimal requestedAmount = new BigDecimal("80.0091");
+        final BigDecimal expectedAmount = null;
+
+        testSimplePayment(invoiceAmount, requestedAmount, expectedAmount);
+    }
+
+    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 UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+
+        invoice.addInvoiceItem(new MockRecurringInvoiceItem(invoice.getId(), account.getId(),
+                                                            subscriptionId,
+                                                            bundleId,
+                                                            "test plan", "test phase",
+                                                            now,
+                                                            now.plusMonths(1),
+                                                            invoiceAmount,
+                                                            new BigDecimal("1.0"),
+                                                            Currency.USD));
+
+        try {
+            final Payment paymentInfo = paymentApi.createPayment(account, invoice.getId(), requestedAmount, callContext);
+            if (expectedAmount == null) {
+                fail("Expected to fail because requested amount > invoice amount");
+            }
+            assertNotNull(paymentInfo.getId());
+            assertTrue(paymentInfo.getAmount().compareTo(expectedAmount.setScale(2, RoundingMode.HALF_EVEN)) == 0);
+            assertNotNull(paymentInfo.getPaymentNumber());
+            assertEquals(paymentInfo.getPaymentStatus(), PaymentStatus.SUCCESS);
+            assertEquals(paymentInfo.getAttempts().size(), 1);
+            assertEquals(paymentInfo.getInvoiceId(), invoice.getId());
+            assertEquals(paymentInfo.getCurrency(), Currency.USD);
+
+            final PaymentAttempt paymentAttempt = paymentInfo.getAttempts().get(0);
+            assertNotNull(paymentAttempt);
+            assertNotNull(paymentAttempt.getId());
+        } catch (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);
+        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);
+        Mockito.when(account.getPaymentMethodId()).thenReturn(newPaymentMethodId);
+
+        methods = paymentApi.getPaymentMethods(account, false, callContext);
+        assertEquals(methods.size(), 2);
+
+        assertEquals(newPaymentMethodId, account.getPaymentMethodId());
+
+        boolean failed = false;
+        try {
+            paymentApi.deletedPaymentMethod(account, newPaymentMethodId, false, callContext);
+        } catch (PaymentApiException e) {
+            failed = true;
+        }
+        assertTrue(failed);
+
+        paymentApi.deletedPaymentMethod(account, initDefaultMethod.getId(), true,  callContext);
+        methods = paymentApi.getPaymentMethods(account, false, callContext);
+        assertEquals(methods.size(), 1);
+
+        // NOW retry with default payment method with special flag
+        paymentApi.deletedPaymentMethod(account, newPaymentMethodId, true, callContext);
+
+        methods = paymentApi.getPaymentMethods(account, false, callContext);
+        assertEquals(methods.size(), 0);
+    }
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java b/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java
index d56eba2..193dbb1 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/KillbillClient.java
@@ -549,22 +549,25 @@ public abstract class KillbillClient extends GuicyKillbillTestSuiteWithEmbeddedD
     }
 
     protected InvoiceJson createExternalCharge(final String accountId, final BigDecimal amount, @Nullable final String bundleId,
-                                                        @Nullable final Currency currency, @Nullable final DateTime requestedDate) throws Exception {
-        return doCreateExternalCharge(accountId, null, bundleId, amount, currency, requestedDate, JaxrsResource.CHARGES_PATH);
+                                                        @Nullable final Currency currency, @Nullable final DateTime requestedDate, final Boolean autoPay) throws Exception {
+        return doCreateExternalCharge(accountId, null, bundleId, amount, currency, requestedDate, autoPay, JaxrsResource.CHARGES_PATH);
     }
 
     protected InvoiceJson createExternalChargeForInvoice(final String accountId, final String invoiceId, @Nullable final String bundleId, final BigDecimal amount,
-                                                                  @Nullable final Currency currency, @Nullable final DateTime requestedDate) throws Exception {
+                                                                  @Nullable final Currency currency, @Nullable final DateTime requestedDate, final Boolean autoPay) throws Exception {
         final String uri = JaxrsResource.INVOICES_PATH + "/" + invoiceId + "/" + JaxrsResource.CHARGES;
-        return doCreateExternalCharge(accountId, invoiceId, bundleId, amount, currency, requestedDate, uri);
+        return doCreateExternalCharge(accountId, invoiceId, bundleId, amount, currency, requestedDate, autoPay, uri);
     }
 
     private InvoiceJson doCreateExternalCharge(final String accountId, @Nullable final String invoiceId, @Nullable final String bundleId, @Nullable final BigDecimal amount,
-                                                        @Nullable final Currency currency, final DateTime requestedDate, final String uri) throws IOException {
+                                                        @Nullable final Currency currency, final DateTime requestedDate, final Boolean autoPay, final String uri) throws IOException {
         final Map<String, String> queryParams = new HashMap<String, String>();
         if (requestedDate != null) {
             queryParams.put(JaxrsResource.QUERY_REQUESTED_DT, requestedDate.toDateTimeISO().toString());
         }
+        if (autoPay) {
+            queryParams.put(JaxrsResource.QUERY_PAY_INVOICE, "true");
+        }
 
         final InvoiceItemJson externalCharge = new InvoiceItemJson(null, invoiceId, null, accountId, bundleId, null, null, null,
                                                                                null, null, null, null, amount, currency, null);
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
index 6b6721f..34a661e 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -35,6 +35,7 @@ import com.ning.billing.payment.provider.ExternalPaymentProviderPlugin;
 import com.ning.billing.util.api.AuditLevel;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 
 public class TestInvoice extends TestJaxrsBase {
@@ -109,7 +110,7 @@ public class TestInvoice extends TestJaxrsBase {
         final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
 
         // Check there was no payment made
-        assertEquals(getPaymentsForAccount(accountJson.getAccountId()).size(), 0);
+        assertEquals(getPaymentsForAccount(accountJson.getAccountId()).size(), 1);
 
         // Get the invoices
         final List<InvoiceJson> invoices = getInvoicesForAccount(accountJson.getAccountId());
@@ -122,7 +123,7 @@ public class TestInvoice extends TestJaxrsBase {
         for (final InvoiceJson invoice : getInvoicesForAccount(accountJson.getAccountId())) {
             assertEquals(invoice.getBalance().compareTo(BigDecimal.ZERO), 0);
         }
-        assertEquals(getPaymentsForAccount(accountJson.getAccountId()).size(), 1);
+        assertEquals(getPaymentsForAccount(accountJson.getAccountId()).size(), 2);
     }
 
     @Test(groups = "slow")
@@ -154,7 +155,8 @@ public class TestInvoice extends TestJaxrsBase {
 
         // Verify we didn't get any payment
         final List<PaymentJson> noPaymentsFromJson = getPaymentsForAccount(accountJson.getAccountId());
-        assertEquals(noPaymentsFromJson.size(), 0);
+        assertEquals(noPaymentsFromJson.size(), 1);
+        final String initialPaymentId = noPaymentsFromJson.get(0).getPaymentId();
 
         // Get the invoices
         final List<InvoiceJson> invoices = getInvoicesForAccount(accountJson.getAccountId());
@@ -168,11 +170,20 @@ public class TestInvoice extends TestJaxrsBase {
 
         // Verify we indeed got the payment
         final List<PaymentJson> paymentsFromJson = getPaymentsForAccount(accountJson.getAccountId());
-        assertEquals(paymentsFromJson.size(), 1);
-        assertEquals(paymentsFromJson.get(0).getPaidAmount().compareTo(paidAmount), 0);
+        assertEquals(paymentsFromJson.size(), 2);
+        PaymentJson secondPayment = null;
+        for (PaymentJson cur : paymentsFromJson) {
+            if (! cur.getPaymentId().equals(initialPaymentId)) {
+                secondPayment = cur;
+                break;
+            }
+        }
+        assertNotNull(secondPayment);
+
+        assertEquals(secondPayment.getPaidAmount().compareTo(paidAmount), 0);
 
         // Check the PaymentMethod from paymentMethodId returned in the Payment object
-        final String paymentMethodId = paymentsFromJson.get(0).getPaymentMethodId();
+        final String paymentMethodId = secondPayment.getPaymentMethodId();
         final PaymentMethodJson paymentMethodJson = getPaymentMethod(paymentMethodId);
         assertEquals(paymentMethodJson.getPaymentMethodId(), paymentMethodId);
         assertEquals(paymentMethodJson.getAccountId(), accountJson.getAccountId());
@@ -271,7 +282,7 @@ public class TestInvoice extends TestJaxrsBase {
 
         // Post an external charge
         final BigDecimal chargeAmount = BigDecimal.TEN;
-        final InvoiceJson invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, null, null, null);
+        final InvoiceJson invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, null, null, null, false);
         assertEquals(invoiceWithItems.getBalance().compareTo(chargeAmount), 0);
         assertEquals(invoiceWithItems.getItems().size(), 1);
         assertNull(invoiceWithItems.getItems().get(0).getBundleId());
@@ -280,6 +291,26 @@ public class TestInvoice extends TestJaxrsBase {
         assertEquals(getInvoicesForAccount(accountJson.getAccountId()).size(), 3);
     }
 
+
+    @Test(groups = "slow")
+    public void testExternalChargeOnNewInvoiceWithAutomaticPayment() throws Exception {
+        final AccountJson accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        // Get the invoices
+        assertEquals(getInvoicesForAccount(accountJson.getAccountId()).size(), 2);
+
+        // Post an external charge
+        final BigDecimal chargeAmount = BigDecimal.TEN;
+        final InvoiceJson invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, null, null, null, true);
+        assertEquals(invoiceWithItems.getBalance().compareTo(BigDecimal.ZERO), 0);
+        assertEquals(invoiceWithItems.getItems().size(), 1);
+        assertNull(invoiceWithItems.getItems().get(0).getBundleId());
+
+        // Verify the total number of invoices
+        assertEquals(getInvoicesForAccount(accountJson.getAccountId()).size(), 3);
+
+    }
+
     @Test(groups = "slow")
     public void testExternalChargeForBundleOnNewInvoice() throws Exception {
         final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
@@ -290,7 +321,7 @@ public class TestInvoice extends TestJaxrsBase {
         // Post an external charge
         final BigDecimal chargeAmount = BigDecimal.TEN;
         final String bundleId = UUID.randomUUID().toString();
-        final InvoiceJson invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, bundleId, null, null);
+        final InvoiceJson invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, bundleId, null, null, false);
         assertEquals(invoiceWithItems.getBalance().compareTo(chargeAmount), 0);
         assertEquals(invoiceWithItems.getItems().size(), 1);
         assertEquals(invoiceWithItems.getItems().get(0).getBundleId(), bundleId);
@@ -314,7 +345,7 @@ public class TestInvoice extends TestJaxrsBase {
         // Post an external charge
         final BigDecimal chargeAmount = BigDecimal.TEN;
         final InvoiceJson invoiceWithItems = createExternalChargeForInvoice(accountJson.getAccountId(), invoiceId,
-                                                                                     null, chargeAmount, null, null);
+                                                                                     null, chargeAmount, null, null, false);
         assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
         assertNull(invoiceWithItems.getItems().get(originalNumberOfItemsForInvoice).getBundleId());
 
@@ -325,6 +356,31 @@ public class TestInvoice extends TestJaxrsBase {
     }
 
     @Test(groups = "slow")
+    public void testExternalChargeOnExistingInvoiceWithAutomaticPayment() throws Exception {
+        final AccountJson accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        // Get the invoices
+        final List<InvoiceJson> invoices = getInvoicesWithItemsForAccount(accountJson.getAccountId());
+        // 2 invoices but look for the non zero dollar one
+        assertEquals(invoices.size(), 2);
+        final String invoiceId = invoices.get(1).getInvoiceId();
+        final BigDecimal originalInvoiceAmount = invoices.get(1).getAmount();
+        final int originalNumberOfItemsForInvoice = invoices.get(1).getItems().size();
+
+        // Post an external charge
+        final BigDecimal chargeAmount = BigDecimal.TEN;
+        final InvoiceJson invoiceWithItems = createExternalChargeForInvoice(accountJson.getAccountId(), invoiceId,
+                                                                            null, chargeAmount, null, null, true);
+        assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
+        assertNull(invoiceWithItems.getItems().get(originalNumberOfItemsForInvoice).getBundleId());
+
+        // Verify the new invoice balance
+        final InvoiceJson adjustedInvoice = getInvoice(invoiceId);
+        assertEquals(adjustedInvoice.getBalance().compareTo(BigDecimal.ZERO), 0);
+    }
+
+
+    @Test(groups = "slow")
     public void testExternalChargeForBundleOnExistingInvoice() throws Exception {
         final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
 
@@ -340,7 +396,7 @@ public class TestInvoice extends TestJaxrsBase {
         final BigDecimal chargeAmount = BigDecimal.TEN;
         final String bundleId = UUID.randomUUID().toString();
         final InvoiceJson invoiceWithItems = createExternalChargeForInvoice(accountJson.getAccountId(), invoiceId,
-                                                                                     bundleId, chargeAmount, null, null);
+                                                                                     bundleId, chargeAmount, null, null, false);
         assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
         assertEquals(invoiceWithItems.getItems().get(originalNumberOfItemsForInvoice).getBundleId(), bundleId);
 
diff --git a/util/src/main/resources/accountRecordIdSanity.sql b/util/src/main/resources/accountRecordIdSanity.sql
new file mode 100644
index 0000000..02f8029
--- /dev/null
+++ b/util/src/main/resources/accountRecordIdSanity.sql
@@ -0,0 +1,667 @@
+select
+  'AUDIT_LOG' as table_name
+, sum(count) count
+from (
+  select
+   'ACCOUNT_EMAIL_HISTORY' table_name
+  , count(1) count
+  from audit_log al
+  join account_email_history t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'ACCOUNT_EMAIL_HISTORY'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'ACCOUNT_HISTORY' table_name
+  , count(1) count
+  from audit_log al
+  join account_history t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'ACCOUNT_HISTORY'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'BLOCKING_STATES' table_name
+  , count(1) count
+  from audit_log al
+  join blocking_states t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'BLOCKING_STATES'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'BUNDLES' table_name
+  , count(1) count
+  from audit_log al
+  join bundles t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'BUNDLES'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'CUSTOM_FIELD_HISTORY' table_name
+  , count(1) count
+  from audit_log al
+  join custom_field_history t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'CUSTOM_FIELD_HISTORY'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'INVOICES' table_name
+  , count(1) count
+  from audit_log al
+  join invoices t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'INVOICES'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'INVOICE_ITEMS' table_name
+  , count(1) count
+  from audit_log al
+  join invoice_items t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'INVOICE_ITEMS'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'INVOICE_PAYMENTS' table_name
+  , count(1) count
+  from audit_log al
+  join invoice_payments t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'INVOICE_PAYMENTS'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'PAYMENTS' table_name
+  , count(1) count
+  from audit_log al
+  join payments t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'PAYMENTS'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'PAYMENT_ATTEMPTS' table_name
+  , count(1) count
+  from audit_log al
+  join payment_attempts t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'PAYMENT_ATTEMPTS'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'PAYMENT_ATTEMPT_HISTORY' table_name
+  , count(1) count
+  from audit_log al
+  join payment_attempt_history t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'PAYMENT_ATTEMPT_HISTORY'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'PAYMENT_HISTORY' table_name
+  , count(1) count
+  from audit_log al
+  join payment_history t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'PAYMENT_HISTORY'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'PAYMENT_METHODS' table_name
+  , count(1) count
+  from audit_log al
+  join payment_methods t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'PAYMENT_METHODS'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'PAYMENT_METHOD_HISTORY' table_name
+  , count(1) count
+  from audit_log al
+  join payment_method_history t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'PAYMENT_METHOD_HISTORY'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'REFUNDS' table_name
+  , count(1) count
+  from audit_log al
+  join refunds t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'REFUNDS'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'REFUND_HISTORY' table_name
+  , count(1) count
+  from audit_log al
+  join refund_history t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'REFUND_HISTORY'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'SUBSCRIPTIONS' table_name
+  , count(1) count
+  from audit_log al
+  join subscriptions t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'SUBSCRIPTIONS'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'SUBSCRIPTION_EVENTS' table_name
+  , count(1) count
+  from audit_log al
+  join subscription_events t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'SUBSCRIPTION_EVENTS'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+  union
+  select
+    'TAG_HISTORY' table_name
+  , count(1) count
+  from audit_log al
+  join tag_history t on al.target_record_id = t.record_id
+  where 1 = 1
+  and al.table_name = 'TAG_HISTORY'
+  and (
+       al.account_record_id != t.account_record_id
+    or al.account_record_id is null
+  )
+) audit
+union
+select
+  'ACCOUNT_EMAILS' as table_name
+, count(1) count
+from account_emails ae
+left outer join accounts a on ae.account_id = a.id
+where 1 = 1
+and (
+     ae.account_record_id != a.record_id
+  or ae.account_record_id is null
+)
+union
+select
+  'ACCOUNT_EMAIL_HISTORY' as table_name
+, count(1) count
+from account_email_history aeh
+left outer join accounts a on aeh.account_id = a.id
+where 1 = 1
+and (
+     aeh.account_record_id != a.record_id
+  or aeh.account_record_id is null
+)
+union
+select
+  'ACCOUNT_HISTORY' as table_name
+, count(1) count
+from account_history ah
+left outer join accounts a on ah.id = a.id
+where 1 = 1
+and (
+     ah.target_record_id != a.record_id
+  or ah.target_record_id is null
+)
+union
+select
+  'BLOCKING_STATES' as table_name
+, count(1) count
+from blocking_states bs
+left outer join bundles b on bs.blockable_id = b.id and bs.type = 'SUBSCRIPTION_BUNDLE'
+left outer join accounts a on b.account_id = a.id
+where 1 = 1
+and (
+     bs.account_record_id != a.record_id
+  or bs.account_record_id is null
+)
+union
+select
+  'BUNDLES' as table_name
+, count(1) count
+from bundles b
+left outer join accounts a on b.account_id = a.id
+where 1 = 1
+and (
+     b.account_record_id != a.record_id
+  or b.account_record_id is null
+)
+union
+select
+  'BUS_EVENTS' as table_name
+, ifnull(sum(count), 0) count
+from (
+  select
+    class_name
+  , count(1) count
+  from (
+    select
+      substr(event_json, position('accountId' in event_json) + 12, 36) id
+    , search_key1  account_record_id
+    , class_name
+    from bus_events
+    where 1 = 1
+    and class_name in ('com.ning.billing.account.api.user.DefaultAccountChangeEvent', 'com.ning.billing.invoice.api.user.DefaultNullInvoiceEvent', 'com.ning.billing.payment.api.DefaultPaymentInfoEvent', 'com.ning.billing.invoice.api.user.DefaultInvoiceAdjustmentEvent', 'com.ning.billing.payment.api.DefaultPaymentErrorEvent')
+    union all
+    select
+      substr(event_json,position('objectId' in event_json) + 11, 36) id
+    , search_key1 account_record_id
+    , class_name
+    from bus_events
+    where 1 = 1
+    and class_name in ('com.ning.billing.util.tag.api.user.DefaultUserTagCreationEvent', 'com.ning.billing.util.tag.api.user.DefaultControlTagCreationEvent', 'com.ning.billing.util.tag.api.user.DefaultControlTagDeletionEvent', 'com.ning.billing.util.tag.api.user.DefaultUserTagDeletionEvent')
+  ) be
+  left outer join accounts a using (id)
+  where 1 = 1
+  and (
+       be.account_record_id is null
+    or be.account_record_id != a.record_id
+  )
+  group by class_name
+  union all
+  select
+    class_name
+  , count(1) count
+  from (
+    select
+      substr(event_json, position('subscriptionId' in event_json) + 17, 36) id
+    , search_key1 account_record_id
+    , class_name
+    from bus_events
+    where 1 = 1
+    and class_name in ('com.ning.billing.entitlement.api.user.DefaultRequestedSubscriptionEvent', 'com.ning.billing.entitlement.api.user.DefaultEffectiveSubscriptionEvent')
+  ) be
+  left outer join subscriptions s using (id)
+  where 1 = 1
+  and (
+       be.account_record_id is null
+    or be.account_record_id != s.account_record_id
+  )
+  group by class_name
+  union all
+  select
+    class_name
+  , count(1) count
+  from (
+    select
+      substr(event_json, position('invoiceId' in event_json) + 12, 36) id
+    , search_key1 account_record_id
+    , class_name
+    from bus_events
+    where 1 = 1
+    and class_name in ('com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent')
+  ) be
+  left outer join invoices i using (id)
+  where 1 = 1
+  and (
+       be.account_record_id is null
+    or be.account_record_id != i.account_record_id
+  )
+  group by class_name
+  union all
+  select
+    class_name
+  , count(1) count
+  from (
+    select
+      substr(event_json, position('overdueObjectId' in event_json) + 18, 36) id
+    , search_key1 account_record_id
+    , class_name
+    from bus_events
+    where 1 = 1
+    and class_name in ('com.ning.billing.overdue.applicator.DefaultOverdueChangeEvent')
+  ) be
+  left outer join bundles b using (id)
+  where 1 = 1
+  and (
+       be.account_record_id is null
+    or be.account_record_id != b.account_record_id
+  )
+  group by class_name
+) bus
+union
+select
+  'CUSTOM_FIELD_HISTORY' as table_name
+, count(1) count
+from custom_field_history cfh
+left outer join accounts a on cfh.object_id = a.id and cfh.object_type = 'ACCOUNT'
+where 1 = 1
+and (
+     cfh.account_record_id != a.record_id
+  or cfh.account_record_id is null
+)
+union
+select
+  'CUSTOM_FIELDS' as table_name
+, count(1) count
+from custom_fields cf
+left outer join accounts a on cf.object_id = a.id and cf.object_type = 'ACCOUNT'
+where 1 = 1
+and (
+     cf.account_record_id != a.record_id
+  or cf.account_record_id is null
+)
+union
+select
+  'INVOICE_ITEMS' as table_name
+, count(1) count
+from invoice_items it
+left outer join invoices i on it.invoice_id = i.id
+left outer join accounts a on i.account_id = a.id
+where 1 = 1
+and (
+     it.account_record_id != a.record_id
+  or it.account_record_id is null
+)
+union
+select
+  'INVOICE_PAYMENTS' as table_name
+, count(1) count
+from invoice_payments ip
+left outer join invoices i on ip.invoice_id = i.id
+left outer join accounts a on i.account_id = a.id
+where 1 = 1
+and (
+     ip.account_record_id != a.record_id
+  or ip.account_record_id is null
+)
+union
+select
+  'INVOICES' as table_name
+, count(1) count
+from invoices i
+left outer join accounts a on i.account_id = a.id
+where 1 = 1
+and (
+     i.account_record_id != a.record_id
+  or i.account_record_id is null
+)
+union
+select
+  'NOTIFICATIONS' as table_name
+, ifnull(sum(count), 0) count
+from (
+  select
+    class_name
+  , count(1) count
+  from (
+    select
+      substr(event_json, 13, 36) id
+    , search_key1 account_record_id
+    , class_name
+    from notifications
+    where 1 = 1
+    and class_name = 'com.ning.billing.invoice.notification.NextBillingDateNotificationKey'
+  ) n
+  left outer join subscriptions s using (id)
+  where 1 = 1
+  and (
+       n.account_record_id is null
+    or n.account_record_id != s.account_record_id
+  )
+  group by class_name
+  union all
+  select
+    class_name
+  , count(1) count
+  from (
+    select
+      substr(event_json, 13, 36) id
+    , search_key1 account_record_id
+    , class_name
+    from notifications
+    where 1 = 1
+    and class_name in ('com.ning.billing.ovedue.notification.OverdueCheckNotificationKey', 'com.ning.billing.irs.callbacks.CallbackNotificationKey')
+  ) n
+  left outer join bundles b using (id)
+  where 1 = 1
+  and (
+       n.account_record_id is null
+    or n.account_record_id != b.account_record_id
+  )
+  group by class_name
+  union all
+  select
+    class_name
+  , count(1) count
+  from (
+    select
+      substr(event_json, 13, 36) id
+    , search_key1 account_record_id
+    , class_name
+    from notifications
+    where 1 = 1
+    and class_name = 'com.ning.billing.payment.retry.PaymentRetryNotificationKey'
+  ) n
+  left outer join payments p using (id)
+  where 1 = 1
+  and (
+       n.account_record_id is null
+    or n.account_record_id != p.account_record_id
+  )
+  group by class_name
+  union all
+  select
+    class_name
+  , count(1) count
+  from (
+    select
+      substr(event_json, 13, 36) id
+    , search_key1 account_record_id
+    , class_name
+    from notifications
+    where 1 = 1
+    and class_name = 'com.ning.billing.entitlement.engine.core.EntitlementNotificationKey'
+  ) n
+  left outer join subscription_events se using (id)
+  where 1 = 1
+  and (
+       n.account_record_id is null
+    or n.account_record_id != se.account_record_id
+  )
+  group by class_name
+) notifications
+union
+select
+  'PAYMENT_ATTEMPTS' as table_name
+, count(1) count
+from payment_attempts pa
+left outer join payments p on pa.payment_id = p.id
+left outer join accounts a on p.account_id = a.id
+where 1 = 1
+and (
+     pa.account_record_id != a.record_id
+  or pa.account_record_id is null
+)
+union
+select
+  'PAYMENT_ATTEMPT_HISTORY' as table_name
+, count(1) from payment_attempt_history pah
+left outer join payment_attempts pa on pah.target_record_id = pa.record_id
+left outer join payments p on pa.payment_id = p.id
+left outer join accounts a on p.account_id = a.id
+where 1 = 1
+and (
+     pah.account_record_id != a.record_id
+  or pah.account_record_id is null
+)
+union
+select
+  'PAYMENT_METHODS' as table_name
+, sum(count) count
+from (
+  select
+    count(1) count
+  from payment_methods pm
+  left outer join accounts a on pm.account_id = a.id
+  where 1 = 1
+  and (
+       pm.account_record_id != a.record_id
+    or pm.account_record_id is null
+  )
+  and pm.is_active = 0
+  union all
+  select
+    count(1) count
+  from payment_methods pm
+  left outer join accounts a on pm.account_id = a.id
+  where 1 = 1
+  and (
+       pm.account_record_id != a.record_id
+    or pm.account_record_id is null
+  )
+  and pm.is_active = 1
+) pms
+union
+select
+  'PAYMENTS' as table_name
+, count(1) count
+from payments p
+left outer join accounts a on p.account_id = a.id
+where 1 = 1
+and (
+     p.account_record_id != a.record_id
+  or p.account_record_id is null
+)
+union
+select
+  'PAYMENT_HISTORY' as table_name
+, count(1) count
+from payment_history ph
+left outer join payments p on ph.target_record_id = p.record_id
+where 1 = 1
+and (
+     ph.account_record_id != p.account_record_id
+  or ph.account_record_id is null
+)
+union
+select
+  'REFUND_HISTORY' as table_name
+, count(1) count
+from refund_history rh
+left outer join refunds r on rh.target_record_id = r.record_id
+left outer join accounts a on r.account_id = a.id
+where 1 = 1
+and (
+     rh.account_record_id != a.record_id
+  or rh.account_record_id is null
+)
+union
+select
+  'REFUNDS' as table_name
+, count(1) count
+from refunds r
+left outer join accounts a on r.account_id = a.id
+where 1 = 1
+and (
+     r.account_record_id != a.record_id
+  or r.account_record_id is null
+)
+union
+select
+  'SUBSCRIPTIONS' as table_name
+, count(1) count
+from subscriptions s
+left outer join bundles b on s.bundle_id = b.id
+left outer join accounts a on b.account_id = a.id
+where 1 = 1
+and (
+     s.account_record_id != a.record_id
+  or s.account_record_id is null
+)
+union
+select
+  'SUBSCRIPTION_EVENTS' as table_name
+, count(1) count
+from subscription_events e
+left outer join subscriptions s on e.subscription_id = s.id
+left outer join bundles b on s.bundle_id = b.id
+left outer join accounts a on b.account_id = a.id
+where 1 = 1
+and (
+     e.account_record_id != a.record_id
+  or e.account_record_id is null
+)
+union
+select
+  'TAG_HISTORY' as table_name
+, count(1) count
+from tag_history th
+left outer join accounts a on th.object_id = a.id and th.object_type = 'ACCOUNT'
+where 1 = 1
+and (
+     th.account_record_id != a.record_id
+  or th.account_record_id is null
+)
+union
+select
+  'TAGS' as table_name
+, count(1) count
+from tags t
+left outer join accounts a on t.object_id = a.id and t.object_type = 'ACCOUNT'
+where 1 = 1
+and (
+     t.account_record_id != a.record_id
+  or t.account_record_id is null
+)
+;
\ No newline at end of file