killbill-aplcache

Merge pull request #1105 from killbill/fix-for-1076 Add

2/25/2019 3:53:30 PM

Changes

pom.xml 2(+1 -1)

Details

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 e6534c8..90893b7 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
@@ -80,6 +80,8 @@ public interface InvoiceInternalApi {
 
     public void commitInvoice(UUID invoiceId, InternalCallContext context) throws InvoiceApiException;
 
+    public InvoicePayment getInvoicePayment(UUID invoicePaymentId, TenantContext context);
+
     public List<InvoicePayment> getInvoicePayments(UUID paymentId, TenantContext context);
 
     public List<InvoicePayment> getInvoicePaymentsByAccount(UUID accountId, TenantContext 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 82b590b..619c255 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
@@ -156,12 +156,12 @@ public class AuditChecker {
     public void checkInvoiceCreated(final Invoice invoice, final CallContext context) {
         final List<AuditLog> invoiceLogs = getAuditLogForInvoice(invoice, context);
         //Assert.assertEquals(invoiceLogs.size(), 1);
-        checkAuditLog(ChangeType.INSERT, context, invoiceLogs.get(0), invoice.getId(), InvoiceSqlDao.class, false, false);
+        checkAuditLog(ChangeType.INSERT, context, invoiceLogs.get(0), invoice.getId(), InvoiceSqlDao.class, true, false);
 
         for (InvoiceItem cur : invoice.getInvoiceItems()) {
             final List<AuditLog> auditLogs = getAuditLogForInvoiceItem(cur, context);
             Assert.assertTrue(auditLogs.size() >= 1);
-            checkAuditLog(ChangeType.INSERT, context, auditLogs.get(0), cur.getId(), InvoiceItemSqlDao.class, false, false);
+            checkAuditLog(ChangeType.INSERT, context, auditLogs.get(0), cur.getId(), InvoiceItemSqlDao.class, true, false);
         }
     }
 
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 c0c5dc8..48f194a 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/EntitlementTestSuiteNoDB.java
@@ -29,6 +29,7 @@ import org.killbill.billing.entitlement.glue.TestEntitlementModuleNoDB;
 import org.killbill.billing.junction.BlockingInternalApi;
 import org.killbill.billing.subscription.api.SubscriptionBaseInternalApi;
 import org.killbill.billing.tag.TagInternalApi;
+import org.killbill.billing.util.audit.dao.AuditDao;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.billing.util.tag.dao.TagDao;
 import org.killbill.bus.api.PersistentBus;
@@ -66,6 +67,8 @@ public abstract class EntitlementTestSuiteNoDB extends GuicyKillbillTestSuiteNoD
     protected BlockingChecker blockingChecker;
     @Inject
     protected NonEntityDao nonEntityDao;
+    @Inject
+    protected AuditDao auditDao;
 
     @BeforeClass(groups = "fast")
     protected void beforeClass() throws Exception {
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 669e2ca..4285edb 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
@@ -27,6 +27,8 @@ import org.killbill.billing.mock.glue.MockNonEntityDaoModule;
 import org.killbill.billing.mock.glue.MockSubscriptionModule;
 import org.killbill.billing.mock.glue.MockTagModule;
 import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.audit.dao.AuditDao;
+import org.killbill.billing.util.audit.dao.MockAuditDao;
 import org.killbill.clock.ClockMock;
 
 public class TestEntitlementModuleNoDB extends TestEntitlementModule {
@@ -47,6 +49,7 @@ public class TestEntitlementModuleNoDB extends TestEntitlementModule {
         install(new MockSubscriptionModule(configSource));
         install(new MockCatalogModule(configSource));
         install(new MockAccountModule(configSource));
+        bind(AuditDao.class).toInstance(new MockAuditDao());
     }
 
     @Override
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 1b9e082..7d9a572 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
@@ -194,6 +194,12 @@ public class DefaultInvoiceInternalApi implements InvoiceInternalApi {
     }
 
     @Override
+    public InvoicePayment getInvoicePayment(final UUID invoicePaymentId, final TenantContext context) {
+        final InvoicePaymentModelDao result =  dao.getInvoicePayment(invoicePaymentId, internalCallContextFactory.createInternalTenantContext(invoicePaymentId, ObjectType.INVOICE_PAYMENT, context));
+        return result != null ? new DefaultInvoicePayment(result) : null;
+    }
+
+    @Override
     public List<InvoicePayment> getInvoicePayments(final UUID paymentId, final TenantContext context) {
         return ImmutableList.<InvoicePayment>copyOf(Collections2.transform(dao.getInvoicePaymentsByPaymentId(paymentId, internalCallContextFactory.createInternalTenantContext(paymentId, ObjectType.PAYMENT, context)),
                                                                            new Function<InvoicePaymentModelDao, InvoicePayment>() {
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 30aa18d..0788a94 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
@@ -66,7 +66,9 @@ import org.killbill.billing.invoice.template.HtmlInvoiceGenerator;
 import org.killbill.billing.payment.api.PluginProperty;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.api.AuditLevel;
 import org.killbill.billing.util.api.TagApiException;
+import org.killbill.billing.util.audit.AuditLogWithHistory;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.callcontext.TenantContext;
@@ -692,6 +694,21 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
         dao.changeInvoiceStatus(invoiceId, InvoiceStatus.VOID, internalCallContext);
     }
 
+    @Override
+    public List<AuditLogWithHistory> getInvoiceAuditLogsWithHistoryForId(final UUID invoiceId, final AuditLevel auditLevel, final TenantContext tenantContext) {
+        return dao.getInvoiceAuditLogsWithHistoryForId(invoiceId, auditLevel, internalCallContextFactory.createInternalTenantContext(invoiceId, ObjectType.INVOICE, tenantContext));
+    }
+
+    @Override
+    public List<AuditLogWithHistory> getInvoiceItemAuditLogsWithHistoryForId(final UUID invoiceItemId, final AuditLevel auditLevel, final TenantContext tenantContext) {
+        return dao.getInvoiceItemAuditLogsWithHistoryForId(invoiceItemId, auditLevel, internalCallContextFactory.createInternalTenantContext(invoiceItemId, ObjectType.INVOICE_ITEM, tenantContext));
+    }
+
+    @Override
+    public List<AuditLogWithHistory> getInvoicePaymentAuditLogsWithHistoryForId(final UUID invoicePaymentId, final AuditLevel auditLevel, final TenantContext tenantContext) {
+        return dao.getInvoicePaymentAuditLogsWithHistoryForId(invoicePaymentId, auditLevel, internalCallContextFactory.createInternalTenantContext(invoicePaymentId, ObjectType.INVOICE_PAYMENT, tenantContext));
+    }
+
     private void canInvoiceBeVoided(final Invoice invoice) throws InvoiceApiException {
         final List<InvoicePayment> invoicePayments = invoice.getPayments();
         final BigDecimal amountPaid = InvoiceCalculatorUtils.computeInvoiceAmountPaid(invoice.getCurrency(), invoicePayments)
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 07f22f0..198ea35 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
@@ -61,12 +61,16 @@ import org.killbill.billing.invoice.notification.NextBillingDatePoster;
 import org.killbill.billing.invoice.notification.ParentInvoiceCommitmentPoster;
 import org.killbill.billing.tag.TagInternalApi;
 import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.api.AuditLevel;
+import org.killbill.billing.util.audit.AuditLogWithHistory;
+import org.killbill.billing.util.audit.dao.AuditDao;
 import org.killbill.billing.util.cache.Cachable.CacheType;
 import org.killbill.billing.util.cache.CacheController;
 import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.callcontext.InternalCallContextFactory;
 import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
+import org.killbill.billing.util.dao.TableName;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper;
 import org.killbill.billing.util.entity.dao.DefaultPaginationSqlDaoHelper.PaginationIteratorBuilder;
@@ -125,6 +129,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     private final NonEntityDao nonEntityDao;
     private final ParentInvoiceCommitmentPoster parentInvoiceCommitmentPoster;
     private final TagInternalApi tagInternalApi;
+    private final AuditDao auditDao;
 
     @Inject
     public DefaultInvoiceDao(final TagInternalApi tagInternalApi,
@@ -139,6 +144,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                              final InvoiceDaoHelper invoiceDaoHelper,
                              final CBADao cbaDao,
                              final ParentInvoiceCommitmentPoster parentInvoiceCommitmentPoster,
+                             final AuditDao auditDao,
                              final InternalCallContextFactory internalCallContextFactory) {
         super(nonEntityDao, cacheControllerDispatcher, new EntitySqlDaoTransactionalJdbiWrapper(dbi, roDbi, clock, cacheControllerDispatcher, nonEntityDao, internalCallContextFactory), InvoiceSqlDao.class);
         this.tagInternalApi = tagInternalApi;
@@ -148,6 +154,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         this.internalCallContextFactory = internalCallContextFactory;
         this.invoiceDaoHelper = invoiceDaoHelper;
         this.cbaDao = cbaDao;
+        this.auditDao = auditDao;
         this.clock = clock;
         this.objectIdCacheController = cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID);
         this.nonEntityDao = nonEntityDao;
@@ -617,6 +624,16 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
     }
 
     @Override
+    public InvoicePaymentModelDao getInvoicePayment(final UUID invoicePaymentId, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<InvoicePaymentModelDao>() {
+            @Override
+            public InvoicePaymentModelDao inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class).getById(invoicePaymentId.toString(), context);
+            }
+        });
+    }
+
+    @Override
     public InvoicePaymentModelDao createRefund(final UUID paymentId, final BigDecimal requestedRefundAmount, final boolean isInvoiceAdjusted,
                                                final Map<UUID, BigDecimal> invoiceItemIdsWithNullAmounts, final String transactionExternalKey,
                                                final InternalCallContext context) throws InvoiceApiException {
@@ -1434,6 +1451,40 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
         });
     }
 
+
+    @Override
+    public List<AuditLogWithHistory> getInvoiceAuditLogsWithHistoryForId(final UUID invoiceId, final AuditLevel auditLevel, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<List<AuditLogWithHistory>>() {
+            @Override
+            public List<AuditLogWithHistory> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) {
+                final InvoiceSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceSqlDao.class);
+                return auditDao.getAuditLogsWithHistoryForId(transactional, TableName.INVOICES, invoiceId, auditLevel, context);
+            }
+        });
+    }
+
+    @Override
+    public List<AuditLogWithHistory> getInvoiceItemAuditLogsWithHistoryForId(final UUID invoiceItemId, final AuditLevel auditLevel, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<List<AuditLogWithHistory>>() {
+            @Override
+            public List<AuditLogWithHistory> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) {
+                final InvoiceItemSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoiceItemSqlDao.class);
+                return auditDao.getAuditLogsWithHistoryForId(transactional, TableName.INVOICE_ITEMS, invoiceItemId, auditLevel, context);
+            }
+        });
+    }
+
+    @Override
+    public List<AuditLogWithHistory> getInvoicePaymentAuditLogsWithHistoryForId(final UUID invoicePaymentId, final AuditLevel auditLevel, final InternalTenantContext context) {
+        return transactionalSqlDao.execute(true, new EntitySqlDaoTransactionWrapper<List<AuditLogWithHistory>>() {
+            @Override
+            public List<AuditLogWithHistory> inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) {
+                final InvoicePaymentSqlDao transactional = entitySqlDaoWrapperFactory.become(InvoicePaymentSqlDao.class);
+                return auditDao.getAuditLogsWithHistoryForId(transactional, TableName.INVOICE_PAYMENTS, invoicePaymentId, auditLevel, context);
+            }
+        });
+    }
+
     // PERF: fetch tags once. See also https://github.com/killbill/killbill/issues/720.
     private List<Tag> getInvoicesTags(final InternalTenantContext context) {
         return tagInternalApi.getTagsForAccountType(ObjectType.INVOICE, false, 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 b989312..c0bcf26 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
@@ -35,6 +35,8 @@ import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceStatus;
+import org.killbill.billing.util.api.AuditLevel;
+import org.killbill.billing.util.audit.AuditLogWithHistory;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.EntityDao;
 
@@ -77,6 +79,8 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
 
     InvoicePaymentModelDao getInvoicePaymentByCookieId(String cookieId, InternalTenantContext internalTenantContext);
 
+    InvoicePaymentModelDao getInvoicePayment(UUID invoicePaymentId, InternalTenantContext internalTenantContext);
+
     BigDecimal getAccountBalance(UUID accountId, InternalTenantContext context);
 
     BigDecimal getAccountCBA(UUID accountId, InternalTenantContext context);
@@ -228,4 +232,10 @@ public interface InvoiceDao extends EntityDao<InvoiceModelDao, Invoice, InvoiceA
 
     List<InvoiceTrackingModelDao> getTrackingsByDateRange(LocalDate startDate, LocalDate endDate, InternalCallContext context);
 
+    public List<AuditLogWithHistory> getInvoiceAuditLogsWithHistoryForId(final UUID invoiceId, final AuditLevel auditLevel, final InternalTenantContext context);
+
+    public List<AuditLogWithHistory> getInvoiceItemAuditLogsWithHistoryForId(final UUID invoiceItemId, final AuditLevel auditLevel, final InternalTenantContext context);
+
+    public List<AuditLogWithHistory> getInvoicePaymentAuditLogsWithHistoryForId(final UUID invoicePaymentId, final AuditLevel auditLevel, final InternalTenantContext context);
+
 }
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 c036a76..07c5bc8 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
@@ -398,6 +398,6 @@ public class InvoiceItemModelDao extends EntityModelDaoBase implements EntityMod
 
     @Override
     public TableName getHistoryTableName() {
-        return null;
+        return TableName.INVOICE_ITEM_HISTORY;
     }
 }
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 b3f5602..ad021c5 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
@@ -183,6 +183,11 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
         return isWrittenOff;
     }
 
+    // Make BeanInspector happy when invoked from EntityHistoryBinder
+    public boolean getIsWrittenOff() {
+        return isWrittenOff;
+    }
+
     public void setIsWrittenOff(final boolean isWrittenOff) {
         this.isWrittenOff = isWrittenOff;
     }
@@ -303,7 +308,7 @@ public class InvoiceModelDao extends EntityModelDaoBase implements EntityModelDa
 
     @Override
     public TableName getHistoryTableName() {
-        return null;
+        return TableName.INVOICE_HISTORY;
     }
 
 }
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 53cbf16..2b76d19 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
@@ -228,6 +228,6 @@ public class InvoicePaymentModelDao extends EntityModelDaoBase implements Entity
 
     @Override
     public TableName getHistoryTableName() {
-        return null;
+        return TableName.INVOICE_PAYMENT_HISTORY;
     }
 }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingModelDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingModelDao.java
index 178b464..59ac342 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingModelDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingModelDao.java
@@ -156,7 +156,7 @@ public class InvoiceTrackingModelDao extends EntityModelDaoBase implements Entit
 
     @Override
     public TableName getHistoryTableName() {
-        return null;
+        return TableName.INVOICE_TRACKING_ID_HISTORY;
     }
 
 }
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 1a25b85..0aa5427 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
@@ -1,6 +1,7 @@
 import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
 
 tableName() ::= "invoice_items"
+historyTableName() ::= "invoice_item_history"
 
 tableFields(prefix) ::= <<
   <prefix>type
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 23776fa..728c847 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
@@ -1,6 +1,7 @@
 import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
 
 tableName() ::= "invoice_payments"
+historyTableName() ::= "invoice_payment_history"
 
 tableFields(prefix) ::= <<
   <prefix>type
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg
index 304ef0e..12e578a 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceSqlDao.sql.stg
@@ -1,6 +1,7 @@
 import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
 
 tableName() ::= "invoices"
+historyTableName() ::= "invoice_history"
 
 tableFields(prefix) ::= <<
   <prefix>account_id
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
index b20293a..b67b442 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
@@ -1,6 +1,7 @@
 import "org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg"
 
 tableName() ::= "invoice_tracking_ids"
+historyTableName() ::= "invoice_tracking_id_history"
 
 tableFields(prefix) ::= <<
   <prefix>tracking_id
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 a9f6763..68ab207 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/ddl.sql
@@ -22,6 +22,29 @@ CREATE INDEX invoice_tracking_tenant_account_date_idx ON invoice_tracking_ids(te
 CREATE INDEX invoice_tracking_invoice_id_idx ON invoice_tracking_ids(invoice_id);
 
 
+DROP TABLE IF EXISTS invoice_tracking_id_history;
+CREATE TABLE invoice_tracking_id_history (
+    record_id serial unique,
+    id varchar(36) NOT NULL,
+    target_record_id bigint /*! unsigned */ not null,
+    tracking_id varchar(128) NOT NULL,
+    invoice_id varchar(36) NOT NULL,
+    subscription_id varchar(36),
+    unit_type varchar(255) NOT NULL,
+    record_date date NOT NULL,
+    is_active boolean default true,
+    change_type varchar(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 bigint /*! unsigned */ not null,
+    tenant_record_id bigint /*! unsigned */ not null default 0,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_tracking_id_history_target_record_id ON invoice_tracking_id_history(target_record_id);
+CREATE INDEX invoice_tracking_id_history_tenant_record_id ON invoice_tracking_id_history(tenant_record_id);
+
 DROP TABLE IF EXISTS invoice_items;
 CREATE TABLE invoice_items (
     record_id serial unique,
@@ -58,6 +81,43 @@ CREATE INDEX invoice_items_account_id ON invoice_items(account_id ASC);
 CREATE INDEX invoice_items_linked_item_id ON invoice_items(linked_item_id ASC);
 CREATE INDEX invoice_items_tenant_account_record_id ON invoice_items(tenant_record_id, account_record_id);
 
+
+DROP TABLE IF EXISTS invoice_item_history;
+CREATE TABLE invoice_item_history (
+    record_id serial unique,
+    id varchar(36) NOT NULL,
+    target_record_id bigint /*! unsigned */ not null,
+    type varchar(24) NOT NULL,
+    invoice_id varchar(36) NOT NULL,
+    account_id varchar(36) NOT NULL,
+    child_account_id varchar(36),
+    bundle_id varchar(36),
+    subscription_id varchar(36),
+    description varchar(255),
+    product_name varchar(255),
+    plan_name varchar(255),
+    phase_name varchar(255),
+    usage_name varchar(255),
+    start_date date,
+    end_date date,
+    amount numeric(15,9) NOT NULL,
+    rate numeric(15,9) NULL,
+    currency varchar(3) NOT NULL,
+    linked_item_id varchar(36),
+    quantity int,
+    item_details text,
+    change_type varchar(6) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    account_record_id bigint /*! unsigned */ not null,
+    tenant_record_id bigint /*! unsigned */ not null default 0,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_item_history_target_record_id ON invoice_item_history(target_record_id);
+CREATE INDEX invoice_item_history_tenant_record_id ON invoice_item_history(tenant_record_id);
+
+
+
 DROP TABLE IF EXISTS invoices;
 CREATE TABLE invoices (
     record_id serial unique,
@@ -79,6 +139,30 @@ CREATE UNIQUE INDEX invoices_id ON invoices(id);
 CREATE INDEX invoices_account ON invoices(account_id ASC);
 CREATE INDEX invoices_tenant_account_record_id ON invoices(tenant_record_id, account_record_id);
 
+
+DROP TABLE IF EXISTS invoice_history;
+CREATE TABLE invoice_history (
+    record_id serial unique,
+    id varchar(36) NOT NULL,
+    target_record_id bigint /*! unsigned */ not null,
+    account_id varchar(36) NOT NULL,
+    invoice_date date NOT NULL,
+    target_date date,
+    currency varchar(3) NOT NULL,
+    status varchar(15) NOT NULL DEFAULT 'COMMITTED',
+    migrated bool NOT NULL,
+    parent_invoice bool NOT NULL DEFAULT FALSE,
+    change_type varchar(6) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    account_record_id bigint /*! unsigned */ not null,
+    tenant_record_id bigint /*! unsigned */ not null default 0,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_history_target_record_id ON invoice_history(target_record_id);
+CREATE INDEX invoice_history_tenant_record_id ON invoice_history(tenant_record_id);
+
+
 DROP TABLE IF EXISTS invoice_payments;
 CREATE TABLE invoice_payments (
     record_id serial unique,
@@ -106,6 +190,32 @@ CREATE INDEX invoice_payments_payment_id ON invoice_payments(payment_id);
 CREATE INDEX invoice_payments_payment_cookie_id ON invoice_payments(payment_cookie_id);
 CREATE INDEX invoice_payments_tenant_account_record_id ON invoice_payments(tenant_record_id, account_record_id);
 
+DROP TABLE IF EXISTS invoice_payment_history;
+CREATE TABLE invoice_payment_history (
+    record_id serial unique,
+    id varchar(36) NOT NULL,
+    target_record_id bigint /*! unsigned */ not null,
+    type varchar(24) NOT NULL,
+    invoice_id varchar(36) NOT NULL,
+    payment_id varchar(36),
+    payment_date datetime NOT NULL,
+    amount numeric(15,9) NOT NULL,
+    currency varchar(3) NOT NULL,
+    processed_currency varchar(3) NOT NULL,
+    payment_cookie_id varchar(255) DEFAULT NULL,
+    linked_invoice_payment_id varchar(36) DEFAULT NULL,
+    success bool DEFAULT true,
+    change_type varchar(6) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    account_record_id bigint /*! unsigned */ not null,
+    tenant_record_id bigint /*! unsigned */ not null default 0,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_payment_history_target_record_id ON invoice_payment_history(target_record_id);
+CREATE INDEX invoice_payment_history_tenant_record_id ON invoice_payment_history(tenant_record_id);
+
+
 DROP TABLE IF EXISTS invoice_parent_children;
 CREATE TABLE invoice_parent_children (
     record_id serial unique,
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20190222142211__invoice_history_tables.sql b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20190222142211__invoice_history_tables.sql
new file mode 100644
index 0000000..d50647a
--- /dev/null
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/migration/V20190222142211__invoice_history_tables.sql
@@ -0,0 +1,104 @@
+DROP TABLE IF EXISTS invoice_tracking_id_history;
+CREATE TABLE invoice_tracking_id_history (
+    record_id serial unique,
+    id varchar(36) NOT NULL,
+    target_record_id bigint /*! unsigned */ not null,
+    tracking_id varchar(128) NOT NULL,
+    invoice_id varchar(36) NOT NULL,
+    subscription_id varchar(36),
+    unit_type varchar(255) NOT NULL,
+    record_date date NOT NULL,
+    is_active boolean default true,
+    change_type varchar(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 bigint /*! unsigned */ not null,
+    tenant_record_id bigint /*! unsigned */ not null default 0,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_tracking_id_history_target_record_id ON invoice_tracking_id_history(target_record_id);
+CREATE INDEX invoice_tracking_id_history_tenant_record_id ON invoice_tracking_id_history(tenant_record_id);
+
+DROP TABLE IF EXISTS invoice_item_history;
+CREATE TABLE invoice_item_history (
+    record_id serial unique,
+    id varchar(36) NOT NULL,
+    target_record_id bigint /*! unsigned */ not null,
+    type varchar(24) NOT NULL,
+    invoice_id varchar(36) NOT NULL,
+    account_id varchar(36) NOT NULL,
+    child_account_id varchar(36),
+    bundle_id varchar(36),
+    subscription_id varchar(36),
+    description varchar(255),
+    product_name varchar(255),
+    plan_name varchar(255),
+    phase_name varchar(255),
+    usage_name varchar(255),
+    start_date date,
+    end_date date,
+    amount numeric(15,9) NOT NULL,
+    rate numeric(15,9) NULL,
+    currency varchar(3) NOT NULL,
+    linked_item_id varchar(36),
+    quantity int,
+    item_details text,
+    change_type varchar(6) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    account_record_id bigint /*! unsigned */ not null,
+    tenant_record_id bigint /*! unsigned */ not null default 0,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_item_history_target_record_id ON invoice_item_history(target_record_id);
+CREATE INDEX invoice_item_history_tenant_record_id ON invoice_item_history(tenant_record_id);
+
+
+DROP TABLE IF EXISTS invoice_history;
+CREATE TABLE invoice_history (
+    record_id serial unique,
+    id varchar(36) NOT NULL,
+    target_record_id bigint /*! unsigned */ not null,
+    account_id varchar(36) NOT NULL,
+    invoice_date date NOT NULL,
+    target_date date,
+    currency varchar(3) NOT NULL,
+    status varchar(15) NOT NULL DEFAULT 'COMMITTED',
+    migrated bool NOT NULL,
+    parent_invoice bool NOT NULL DEFAULT FALSE,
+    change_type varchar(6) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    account_record_id bigint /*! unsigned */ not null,
+    tenant_record_id bigint /*! unsigned */ not null default 0,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_history_target_record_id ON invoice_history(target_record_id);
+CREATE INDEX invoice_history_tenant_record_id ON invoice_history(tenant_record_id);
+
+DROP TABLE IF EXISTS invoice_payment_history;
+CREATE TABLE invoice_payment_history (
+    record_id serial unique,
+    id varchar(36) NOT NULL,
+    target_record_id bigint /*! unsigned */ not null,
+    type varchar(24) NOT NULL,
+    invoice_id varchar(36) NOT NULL,
+    payment_id varchar(36),
+    payment_date datetime NOT NULL,
+    amount numeric(15,9) NOT NULL,
+    currency varchar(3) NOT NULL,
+    processed_currency varchar(3) NOT NULL,
+    payment_cookie_id varchar(255) DEFAULT NULL,
+    linked_invoice_payment_id varchar(36) DEFAULT NULL,
+    success bool DEFAULT true,
+    change_type varchar(6) NOT NULL,
+    created_by varchar(50) NOT NULL,
+    created_date datetime NOT NULL,
+    account_record_id bigint /*! unsigned */ not null,
+    tenant_record_id bigint /*! unsigned */ not null default 0,
+    PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX invoice_payment_history_target_record_id ON invoice_payment_history(target_record_id);
+CREATE INDEX invoice_payment_history_tenant_record_id ON invoice_payment_history(tenant_record_id);
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 6ca9f97..445a350 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
@@ -38,6 +38,8 @@ import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceStatus;
 import org.killbill.billing.invoice.api.user.DefaultInvoiceCreationEvent;
+import org.killbill.billing.util.api.AuditLevel;
+import org.killbill.billing.util.audit.AuditLogWithHistory;
 import org.killbill.billing.util.entity.DefaultPagination;
 import org.killbill.billing.util.entity.Pagination;
 import org.killbill.billing.util.entity.dao.MockEntityDaoBase;
@@ -275,6 +277,11 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     }
 
     @Override
+    public InvoicePaymentModelDao getInvoicePayment(final UUID invoicePaymentId, final InternalTenantContext internalTenantContext) {
+        return null;
+    }
+
+    @Override
     public void notifyOfPaymentCompletion(final InvoicePaymentModelDao invoicePayment, final InternalCallContext context) {
         synchronized (monitor) {
             payments.put(invoicePayment.getId(), invoicePayment);
@@ -446,4 +453,19 @@ public class MockInvoiceDao extends MockEntityDaoBase<InvoiceModelDao, Invoice, 
     public List<InvoiceTrackingModelDao> getTrackingsByDateRange(final LocalDate startDate, final LocalDate endDate, final InternalCallContext context) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public List<AuditLogWithHistory> getInvoiceAuditLogsWithHistoryForId(final UUID invoiceId, final AuditLevel auditLevel, final InternalTenantContext context) {
+        return null;
+    }
+
+    @Override
+    public List<AuditLogWithHistory> getInvoiceItemAuditLogsWithHistoryForId(final UUID invoiceItemId, final AuditLevel auditLevel, final InternalTenantContext context) {
+        return null;
+    }
+
+    @Override
+    public List<AuditLogWithHistory> getInvoicePaymentAuditLogsWithHistoryForId(final UUID invoicePaymentId, final AuditLevel auditLevel, final InternalTenantContext context) {
+        return null;
+    }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
index 8c42ce5..bef1051 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
@@ -140,7 +140,11 @@ public class TestInvoiceTrackingSqlDao extends InvoiceTestSuiteWithEmbeddedDB {
                                             final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
                                             Assert.assertEquals(result.size(), 4);
 
-                                            final List<AuditLogModelDao> auditLogsPostCreate = ImmutableList.<AuditLogModelDao>copyOf(dao.getAuditLogsForTableNameAndAccountRecordId(TableName.INVOICE_TRACKING_IDS.toString(), internalCallContext));
+                                            final List<AuditLogModelDao> auditLogsPostCreate = new ArrayList<>();
+                                            for (int i = 0; i < 4; i++) {
+                                                List<AuditLogModelDao> tmp = dao.getAuditLogsViaHistoryForTargetRecordId(TableName.INVOICE_TRACKING_ID_HISTORY.name(), TableName.INVOICE_TRACKING_ID_HISTORY.getTableName().toLowerCase(), result.get(i).getRecordId(), internalCallContext);
+                                                auditLogsPostCreate.addAll(tmp);
+                                            }
                                             Assert.assertEquals(auditLogsPostCreate.size(), 4);
                                             for (int i = 0; i < 4; i++) {
                                                 Assert.assertEquals(auditLogsPostCreate.get(i).getChangeType(), ChangeType.INSERT);
@@ -166,16 +170,20 @@ public class TestInvoiceTrackingSqlDao extends InvoiceTestSuiteWithEmbeddedDB {
                                             final List<InvoiceTrackingModelDao> result2 = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
                                             Assert.assertEquals(result2.size(), 1);
 
-                                            final List<AuditLogModelDao> auditLogsPostDelete = ImmutableList.<AuditLogModelDao>copyOf(dao.getAuditLogsForTableNameAndAccountRecordId(TableName.INVOICE_TRACKING_IDS.toString(), internalCallContext));
-                                            Assert.assertEquals(auditLogsPostDelete.size(), 7);
+                                            final List<AuditLogModelDao> auditLogsPostDelete = new ArrayList<>();
                                             for (int i = 0; i < 4; i++) {
-                                                Assert.assertEquals(auditLogsPostDelete.get(i).getChangeType(), ChangeType.INSERT);
-                                                Assert.assertEquals(auditLogsPostDelete.get(i).getTargetRecordId(), result.get(i).getRecordId());
+                                                List<AuditLogModelDao> tmp = dao.getAuditLogsViaHistoryForTargetRecordId(TableName.INVOICE_TRACKING_ID_HISTORY.name(), TableName.INVOICE_TRACKING_ID_HISTORY.getTableName().toLowerCase(), result.get(i).getRecordId(), internalCallContext);
+                                                auditLogsPostDelete.addAll(tmp);
                                             }
-                                            for (int i = 4; i < 7; i++) {
-                                                Assert.assertEquals(auditLogsPostDelete.get(i).getChangeType(), ChangeType.DELETE);
-                                                Assert.assertEquals(auditLogsPostDelete.get(i).getTargetRecordId(), result.get(i - 4).getRecordId());
+
+                                            Assert.assertEquals(auditLogsPostDelete.size(), 7);
+                                            // First 3 records will show an INSERT & DELETE
+                                            for (int i = 0; i < 3; i++) {
+                                                Assert.assertEquals(auditLogsPostDelete.get(2*i).getChangeType(), ChangeType.INSERT);
+                                                Assert.assertEquals(auditLogsPostDelete.get(2*i + 1).getChangeType(), ChangeType.DELETE);
                                             }
+                                            // Last record will only show an INSERT
+                                            Assert.assertEquals(auditLogsPostDelete.get(6).getChangeType(), ChangeType.INSERT);
 
                                             return null;
                                         }
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 c7d08bf..4fcbb79 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
@@ -1133,7 +1133,7 @@ public class AccountResource extends JaxRsResourceBase {
 
     @TimedResource
     @GET
-    @Path("/{blockingId:" + UUID_PATTERN + "}/" + BLOCK + "/" + AUDIT_LOG_WITH_HISTORY)
+    @Path("/" + BLOCK + "/{blockingId:" + UUID_PATTERN + "}/" + AUDIT_LOG_WITH_HISTORY)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Retrieve blocking state audit logs with history by id", response = AuditLogJson.class, responseContainer = "List")
     @ApiResponses(value = {@ApiResponse(code = 404, message = "Blocking state  not found")})
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceItemResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceItemResource.java
index 2b8eb6b..e28cfdd 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceItemResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoiceItemResource.java
@@ -34,6 +34,7 @@ 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;
@@ -41,18 +42,22 @@ 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.entitlement.api.SubscriptionApi;
+import org.killbill.billing.invoice.api.InvoiceUserApi;
+import org.killbill.billing.jaxrs.json.AuditLogJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 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.InvoicePaymentApi;
 import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.util.api.AuditLevel;
 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.AuditLogWithHistory;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.customfield.CustomField;
 import org.killbill.clock.Clock;
@@ -71,15 +76,32 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 public class InvoiceItemResource extends JaxRsResourceBase {
     private static final String ID_PARAM_NAME = "invoiceItemId";
 
+    private final InvoiceUserApi invoiceApi;
+
     @Inject
     public InvoiceItemResource(final JaxrsUriBuilder uriBuilder, final TagUserApi tagUserApi, final CustomFieldUserApi customFieldUserApi,
-                               final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final PaymentApi paymentApi,
+                               final AuditUserApi auditUserApi, final AccountUserApi accountUserApi, final InvoiceUserApi invoiceApi, final PaymentApi paymentApi,
                                final InvoicePaymentApi invoicePaymentApi, final SubscriptionApi subscriptionApi, final Clock clock, final Context context) {
         super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, invoicePaymentApi, subscriptionApi, clock, context);
+        this.invoiceApi = invoiceApi;
     }
 
     @TimedResource
     @GET
+    @Path("/{invoiceItemId:" + UUID_PATTERN + "}/" + AUDIT_LOG_WITH_HISTORY)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve invoice item audit logs with history by id", response = AuditLogJson.class, responseContainer = "List")
+    @ApiResponses(value = {@ApiResponse(code = 404, message = "Invoice item not found")})
+    public Response getInvoiceItemAuditLogsWithHistory(@PathParam("invoiceItemId") final UUID invoiceItemId,
+                                                       @javax.ws.rs.core.Context final HttpServletRequest request) {
+        final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
+        final List<AuditLogWithHistory> auditLogWithHistory = invoiceApi.getInvoiceItemAuditLogsWithHistoryForId(invoiceItemId, AuditLevel.FULL, tenantContext);
+        return Response.status(Status.OK).entity(getAuditLogsWithHistory(auditLogWithHistory)).build();
+    }
+
+
+    @TimedResource
+    @GET
     @Path("/{invoiceItemId:" + UUID_PATTERN + "}/" + CUSTOM_FIELDS)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Retrieve invoice item custom fields", response = CustomFieldJson.class, responseContainer = "List", nickname = "getInvoiceItemCustomFields")
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
index f3d246f..73b00e3 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/InvoicePaymentResource.java
@@ -48,6 +48,8 @@ import org.killbill.billing.account.api.AccountUserApi;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoicePaymentType;
+import org.killbill.billing.invoice.api.InvoiceUserApi;
+import org.killbill.billing.jaxrs.json.AuditLogJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.InvoiceItemJson;
 import org.killbill.billing.jaxrs.json.InvoicePaymentJson;
@@ -65,6 +67,7 @@ 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.util.UUIDs;
+import org.killbill.billing.util.api.AuditLevel;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldApiException;
 import org.killbill.billing.util.api.CustomFieldUserApi;
@@ -72,6 +75,7 @@ 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.AuditLogWithHistory;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.customfield.CustomField;
@@ -96,6 +100,7 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
     private static final String ID_PARAM_NAME = "paymentId";
 
     private final InvoicePaymentApi invoicePaymentApi;
+    private final InvoiceUserApi invoiceApi;
 
     @Inject
     public InvoicePaymentResource(final AccountUserApi accountUserApi,
@@ -105,10 +110,12 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
                                   final CustomFieldUserApi customFieldUserApi,
                                   final AuditUserApi auditUserApi,
                                   final InvoicePaymentApi invoicePaymentApi,
+                                  final InvoiceUserApi invoiceApi,
                                   final Clock clock,
                                   final Context context) {
         super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, invoicePaymentApi, null, clock, context);
         this.invoicePaymentApi = invoicePaymentApi;
+        this.invoiceApi = invoiceApi;
     }
 
     @TimedResource
@@ -144,6 +151,21 @@ public class InvoicePaymentResource extends JaxRsResourceBase {
     }
 
     @TimedResource
+    @GET
+    @Path("/{invoicePaymentId:" + UUID_PATTERN + "}/" + AUDIT_LOG_WITH_HISTORY)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve invoice payment audit logs with history by id", response = AuditLogJson.class, responseContainer = "List")
+    @ApiResponses(value = {@ApiResponse(code = 404, message = "Invoice payment not found")})
+    public Response getInvoicePaymentAuditLogsWithHistory(@PathParam("invoicePaymentId") final UUID invoicePaymentId,
+                                                       @javax.ws.rs.core.Context final HttpServletRequest request) {
+        final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
+        final List<AuditLogWithHistory> auditLogWithHistory = invoiceApi.getInvoicePaymentAuditLogsWithHistoryForId(invoicePaymentId, AuditLevel.FULL, tenantContext);
+        return Response.status(Status.OK).entity(getAuditLogsWithHistory(auditLogWithHistory)).build();
+    }
+
+
+
+    @TimedResource
     @POST
     @Path("/{paymentId:" + UUID_PATTERN + "}/" + REFUNDS)
     @Consumes(APPLICATION_JSON)
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 128a0e0..dd816b7 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
@@ -71,6 +71,7 @@ import org.killbill.billing.invoice.api.InvoiceApiException;
 import org.killbill.billing.invoice.api.InvoiceItem;
 import org.killbill.billing.invoice.api.InvoicePayment;
 import org.killbill.billing.invoice.api.InvoiceUserApi;
+import org.killbill.billing.jaxrs.json.AuditLogJson;
 import org.killbill.billing.jaxrs.json.CustomFieldJson;
 import org.killbill.billing.jaxrs.json.InvoiceDryRunJson;
 import org.killbill.billing.jaxrs.json.InvoiceItemJson;
@@ -88,6 +89,7 @@ import org.killbill.billing.tenant.api.TenantApiException;
 import org.killbill.billing.tenant.api.TenantKV.TenantKey;
 import org.killbill.billing.tenant.api.TenantUserApi;
 import org.killbill.billing.util.LocaleUtils;
+import org.killbill.billing.util.api.AuditLevel;
 import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.api.CustomFieldApiException;
 import org.killbill.billing.util.api.CustomFieldUserApi;
@@ -95,6 +97,7 @@ 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.AuditLogWithHistory;
 import org.killbill.billing.util.callcontext.CallContext;
 import org.killbill.billing.util.callcontext.TenantContext;
 import org.killbill.billing.util.customfield.CustomField;
@@ -186,6 +189,20 @@ public class InvoiceResource extends JaxRsResourceBase {
         return Response.status(Status.OK).entity(json).build();
     }
 
+    @TimedResource
+    @GET
+    @Path("/{invoiceId:" + UUID_PATTERN + "}/" + AUDIT_LOG_WITH_HISTORY)
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve invoice audit logs with history by id", response = AuditLogJson.class, responseContainer = "List")
+    @ApiResponses(value = {@ApiResponse(code = 404, message = "Invoice not found")})
+    public Response getInvoiceAuditLogsWithHistory(@PathParam("invoiceId") final UUID invoiceId,
+                                                   @javax.ws.rs.core.Context final HttpServletRequest request) {
+        final TenantContext tenantContext = context.createTenantContextNoAccountId(request);
+        final List<AuditLogWithHistory> auditLogWithHistory = invoiceApi.getInvoiceAuditLogsWithHistoryForId(invoiceId, AuditLevel.FULL, tenantContext);
+        return Response.status(Status.OK).entity(getAuditLogsWithHistory(auditLogWithHistory)).build();
+    }
+
+
 
     @TimedResource
     @GET
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 f17729f..92ef0cb 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
@@ -183,7 +183,7 @@ public class SubscriptionResource extends JaxRsResourceBase {
 
     @TimedResource
     @GET
-    @Path("/{eventId:" + UUID_PATTERN + "}/" +  EVENTS + "/" + AUDIT_LOG_WITH_HISTORY)
+    @Path("/" + EVENTS + "/{eventId:" + UUID_PATTERN + "}/" + AUDIT_LOG_WITH_HISTORY)
     @Produces(APPLICATION_JSON)
     @ApiOperation(value = "Retrieve subscription event audit logs with history by id", response = AuditLogJson.class, responseContainer = "List")
     @ApiResponses(value = {@ApiResponse(code = 404, message = "Subscription event not found")})
diff --git a/payment/src/main/java/org/killbill/billing/payment/api/DefaultInvoicePaymentApi.java b/payment/src/main/java/org/killbill/billing/payment/api/DefaultInvoicePaymentApi.java
index 037298a..a600267 100644
--- a/payment/src/main/java/org/killbill/billing/payment/api/DefaultInvoicePaymentApi.java
+++ b/payment/src/main/java/org/killbill/billing/payment/api/DefaultInvoicePaymentApi.java
@@ -59,6 +59,11 @@ public class DefaultInvoicePaymentApi implements InvoicePaymentApi {
     }
 
     @Override
+    public InvoicePayment getInvoicePayment(final UUID invoicePaymentId, final TenantContext tenantContext) {
+       return invoiceInternalApi.getInvoicePayment(invoicePaymentId, tenantContext);
+    }
+
+    @Override
     public InvoicePayment createPurchaseForInvoicePayment(final Account account,
                                                           final UUID invoiceId,
                                                           final UUID paymentMethodId,

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 99c9c99..1cebe3f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.143.9-SNAPSHOT</version>
+        <version>0.143.11</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.20.7-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccountTimeline.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccountTimeline.java
index ca64541..c94ca8d 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccountTimeline.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccountTimeline.java
@@ -99,7 +99,7 @@ public class TestAccountTimeline extends TestJaxrsBase {
         final InvoicePaymentTransaction chargeback = new InvoicePaymentTransaction();
         chargeback.setPaymentId(postedPayment.getPaymentId());
         chargeback.setAmount(chargebackAmount);
-        invoicePaymentApi.createChargeback(postedPayment.getPaymentId(), chargeback, requestOptions);
+        invoicePaymentApi.createChargeback(postedPayment.getPaymentId(), chargeback, NULL_PLUGIN_PROPERTIES, requestOptions);
 
         // Verify payments
         verifyPayments(accountJson.getAccountId(), startTime, endTime, refundAmount, chargebackAmount);
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java
index d4b4778..559940c 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestChargeback.java
@@ -64,11 +64,11 @@ public class TestChargeback extends TestJaxrsBase {
         input.setAmount(new BigDecimal("50.00"));
         int count = 4;
         while (count-- > 0) {
-            assertNotNull(invoicePaymentApi.createChargeback(payment.getPaymentId(), input, requestOptions));
+            assertNotNull(invoicePaymentApi.createChargeback(payment.getPaymentId(), input, NULL_PLUGIN_PROPERTIES, requestOptions));
         }
 
         // Last attempt should fail because this is more than the Payment
-        final InvoicePayment foo = invoicePaymentApi.createChargeback(payment.getPaymentId(), input, requestOptions);
+        final InvoicePayment foo = invoicePaymentApi.createChargeback(payment.getPaymentId(), input, NULL_PLUGIN_PROPERTIES, requestOptions);
         final InvoicePayments payments = accountApi.getInvoicePayments(payment.getAccountId(), NULL_PLUGIN_PROPERTIES, requestOptions);
         final List<PaymentTransaction> transactions = getInvoicePaymentTransactions(payments, TransactionType.CHARGEBACK);
         Assert.assertEquals(transactions.size(), 5);
@@ -110,7 +110,7 @@ public class TestChargeback extends TestJaxrsBase {
         input.setPaymentId(input.getPaymentId());
         input.setAmount(BigDecimal.TEN);
         try {
-            invoicePaymentApi.createChargeback(input.getPaymentId(), input, requestOptions);
+            invoicePaymentApi.createChargeback(input.getPaymentId(), input, NULL_PLUGIN_PROPERTIES, requestOptions);
             fail();
         } catch (NullPointerException e) {
         } catch (KillBillClientException e) {
@@ -126,7 +126,7 @@ public class TestChargeback extends TestJaxrsBase {
         input.setPaymentId(payment.getPaymentId());
 
         try {
-            invoicePaymentApi.createChargeback(payment.getPaymentId(), input, requestOptions);
+            invoicePaymentApi.createChargeback(payment.getPaymentId(), input, NULL_PLUGIN_PROPERTIES, requestOptions);
             fail();
         } catch (final KillBillClientException e) {
         }
@@ -150,7 +150,7 @@ public class TestChargeback extends TestJaxrsBase {
         chargeback.setPaymentId(payment.getPaymentId());
         chargeback.setAmount(BigDecimal.TEN);
 
-        final InvoicePayment chargebackJson = invoicePaymentApi.createChargeback(payment.getPaymentId(), chargeback, requestOptions);
+        final InvoicePayment chargebackJson = invoicePaymentApi.createChargeback(payment.getPaymentId(), chargeback, NULL_PLUGIN_PROPERTIES, requestOptions);
         final List<PaymentTransaction> chargebackTransactions = getInvoicePaymentTransactions(ImmutableList.of(chargebackJson), TransactionType.CHARGEBACK);
         assertEquals(chargebackTransactions.size(), 1);
 
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExceptions.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExceptions.java
index e02ef9c..6e6c896 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExceptions.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestExceptions.java
@@ -43,7 +43,7 @@ public class TestExceptions extends TestJaxrsBase {
         input.setPaymentId(payments.get(0).getPaymentId());
         input.setAmount(BigDecimal.TEN.negate());
         try {
-            invoicePaymentApi.createChargeback(payments.get(0).getPaymentId(), input, requestOptions);
+            invoicePaymentApi.createChargeback(payments.get(0).getPaymentId(), input, NULL_PLUGIN_PROPERTIES, requestOptions);
             fail();
         } catch (final KillBillClientException e) {
             Assert.assertEquals(e.getBillingException().getClassName(), InvoiceApiException.class.getName());
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 f4158c9..54d7810 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
@@ -34,10 +34,14 @@ public enum TableName {
     BLOCKING_STATES("blocking_states", ObjectType.BLOCKING_STATES, BLOCKING_STATE_HISTORY),
     CUSTOM_FIELD_HISTORY("custom_field_history"),
     CUSTOM_FIELD("custom_fields", ObjectType.CUSTOM_FIELD, CUSTOM_FIELD_HISTORY),
-    INVOICE_ITEMS("invoice_items", ObjectType.INVOICE_ITEM),
-    INVOICE_PAYMENTS("invoice_payments", ObjectType.INVOICE_PAYMENT),
-    INVOICES("invoices", ObjectType.INVOICE),
-    INVOICE_TRACKING_IDS("invoice_tracking_ids"),
+    INVOICE_ITEM_HISTORY("invoice_item_history"),
+    INVOICE_ITEMS("invoice_items", ObjectType.INVOICE_ITEM, INVOICE_ITEM_HISTORY),
+    INVOICE_PAYMENT_HISTORY("invoice_payment_history"),
+    INVOICE_PAYMENTS("invoice_payments", ObjectType.INVOICE_PAYMENT, INVOICE_PAYMENT_HISTORY),
+    INVOICE_HISTORY("invoice_history"),
+    INVOICES("invoices", ObjectType.INVOICE, INVOICE_HISTORY),
+    INVOICE_TRACKING_ID_HISTORY("invoice_tracking_id_history"),
+    INVOICE_TRACKING_IDS("invoice_tracking_ids", null, INVOICE_TRACKING_ID_HISTORY),
     INVOICE_PARENT_CHILDREN("invoice_parent_children"),
     NODE_INFOS("node_infos"),
     PAYMENT_ATTEMPT_HISTORY("payment_attempt_history"),
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 d9aa68f..e63c68a 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
@@ -61,14 +61,18 @@ public interface EntitySqlDao<M extends EntityModelDao<E>, E extends Entity> ext
                              @SmartBindBean final InternalCallContext context);
 
     @SqlQuery
-    public M getById(@Bind("id") final String id,
-                     @SmartBindBean final InternalTenantContext context);
-
-    @SqlQuery
     public M getByRecordId(@Bind("recordId") final Long recordId,
                            @SmartBindBean final InternalTenantContext context);
 
     @SqlQuery
+    public List<M> getByRecordIds(@BindIn("recordIds") final Collection<Long> recordId,
+                                  @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
+    public M getById(@Bind("id") final String id,
+                     @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
     List<M> getByIds(@BindIn("ids") final Collection<String> ids,
                      @SmartBindBean final InternalTenantContext context);
 
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 34238f6..8010182 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
@@ -373,8 +373,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
                     // Make sure to re-hydrate the objects first (especially needed for create calls)
                     final Collection<M> reHydratedEntities = new ArrayList<>(entityRecordIds.size());
                     if (deletedAndUpdatedEntities.isEmpty()) {
-                        Preconditions.checkState(entityRecordIds.size() == 1 && changeType == ChangeType.INSERT, "Unexpected number of entityRecordIds=%s and changeType=%s", entityRecordIds, changeType);
-                        reHydratedEntities.add(sqlDao.getByRecordId(entityRecordIds.get(0), context));
+                        reHydratedEntities.addAll((List<M>) sqlDao.getByRecordIds(entityRecordIds, context));
                         printSQLWarnings();
                     } else {
                         reHydratedEntities.addAll(deletedAndUpdatedEntities.values());
diff --git a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
index 0a9d423..e3c59da 100644
--- a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
@@ -189,6 +189,17 @@ where <recordIdField("t.")> = :recordId
 ;
 >>
 
+getByRecordIds(recordIds) ::= <<
+select
+<allTableFields("t.")>
+from <tableName()> t
+where <recordIdField("t.")> in (<recordIds>)
+<andCheckSoftDeletionWithComma("t.")>
+<AND_CHECK_TENANT("t.")>
+;
+>>
+
+
 getByIds(ids) ::= <<
 select
   <allTableFields("t.")>