killbill-uncached

Changes

account/pom.xml 2(+1 -1)

api/pom.xml 2(+1 -1)

beatrix/pom.xml 2(+1 -1)

catalog/pom.xml 2(+1 -1)

invoice/pom.xml 2(+1 -1)

jaxrs/pom.xml 2(+1 -1)

junction/pom.xml 2(+1 -1)

overdue/pom.xml 2(+1 -1)

payment/pom.xml 2(+1 -1)

pom.xml 2(+1 -1)

server/pom.xml 2(+1 -1)

usage/pom.xml 2(+1 -1)

util/pom.xml 2(+1 -1)

Details

account/pom.xml 2(+1 -1)

diff --git a/account/pom.xml b/account/pom.xml
index f20b6b4..433a057 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-account</artifactId>
diff --git a/analytics/pom.xml b/analytics/pom.xml
index 114a533..716bee7 100644
--- a/analytics/pom.xml
+++ b/analytics/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-analytics</artifactId>

api/pom.xml 2(+1 -1)

diff --git a/api/pom.xml b/api/pom.xml
index 0270f7d..d2dcc04 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-api</artifactId>
diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index e96af52..e6d853c 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -186,11 +186,13 @@ public enum ErrorCode {
     INVOICE_TARGET_DATE_TOO_FAR_IN_THE_FUTURE(4005, "The target date was too far in the future. Target Date: %s"),
     INVOICE_NOT_FOUND(4006, "No invoice could be found for id %s."),
     INVOICE_NOTHING_TO_DO(4007, "No invoice to generate for account %s and date %s"),
-    INVOICE_NO_SUCH_CREDIT(4008, "Credit Item for id %s does not exist"),
+    INVOICE_NO_SUCH_CREDIT(4008, "Credit item for id %s does not exist"),
     CREDIT_AMOUNT_INVALID(4009, "Credit amount %s should be strictly positive"),
     INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID(4010, "Invoice adjustment amount %s should be strictly positive"),
     INVOICE_ITEM_NOT_FOUND(4011, "No invoice item could be found for id %s."),
     INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT(4012, "Invoice item %s doesn't belong to invoice %s."),
+    INVOICE_NO_SUCH_EXTERNAL_CHARGE(4014, "External charge item for id %s does not exist"),
+    EXTERNAL_CHARGE_AMOUNT_INVALID(4015, "External charge amount %s should be strictly positive"),
 
     /*
      *
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
index 63cd82a..ee3aa05 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceItemType.java
@@ -17,6 +17,8 @@
 package com.ning.billing.invoice.api;
 
 public enum InvoiceItemType {
+    // Fixed (one-time) external charge (not part of the catalog)
+    EXTERNAL_CHARGE,
     // Fixed (one-time) charge
     FIXED,
     // Recurring charge
diff --git a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
index 74fa8e3..c8dd494 100644
--- a/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
+++ b/api/src/main/java/com/ning/billing/invoice/api/InvoiceUserApi.java
@@ -120,6 +120,46 @@ public interface InvoiceUserApi {
     public void tagInvoiceAsNotWrittenOff(UUID invoiceId, CallContext context) throws TagApiException;
 
     /**
+     * Retrieve an external charge by id.
+     *
+     * @param externalChargeId external charge id
+     * @return the external charge
+     * @throws InvoiceApiException
+     */
+    public InvoiceItem getExternalChargeById(UUID externalChargeId) throws InvoiceApiException;
+
+    /**
+     * Add an external charge to an account.
+     *
+     * @param accountId     account id
+     * @param amount        the external charge amount
+     * @param description   a description for that charge
+     * @param effectiveDate the day to post the external charge, in the account timezone
+     * @param currency      the external charge currency
+     * @param context       the call context
+     * @return the external charge invoice item
+     * @throws InvoiceApiException
+     */
+    public InvoiceItem insertExternalCharge(UUID accountId, BigDecimal amount, String description, LocalDate effectiveDate,
+                                            Currency currency, CallContext context) throws InvoiceApiException;
+
+    /**
+     * Add an external charge to an invoice.
+     *
+     * @param accountId     account id
+     * @param invoiceId     invoice id
+     * @param amount        the external charge amount
+     * @param description   a description for that charge
+     * @param effectiveDate the day to post the external charge, in the account timezone
+     * @param currency      the external charge currency
+     * @param context       the call context
+     * @return the external charge invoice item
+     * @throws InvoiceApiException
+     */
+    public InvoiceItem insertExternalChargeForInvoice(UUID accountId, UUID invoiceId, BigDecimal amount, String description,
+                                                      LocalDate effectiveDate, Currency currency, CallContext context) throws InvoiceApiException;
+
+    /**
      * Retrieve a credit by id.
      *
      * @param creditId credit id

beatrix/pom.xml 2(+1 -1)

diff --git a/beatrix/pom.xml b/beatrix/pom.xml
index 560a944..0dd6788 100644
--- a/beatrix/pom.xml
+++ b/beatrix/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-beatrix</artifactId>
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
index de00151..f6c2124 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestBundleTransfer.java
@@ -21,9 +21,6 @@ import static org.testng.Assert.assertTrue;
 
 import java.math.BigDecimal;
 import java.util.List;
-import java.util.UUID;
-
-import junit.framework.Assert;
 
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
@@ -32,6 +29,7 @@ import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
 import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.BillCycleDay;
 import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedItemCheck;
 import com.ning.billing.catalog.api.BillingPeriod;
@@ -103,15 +101,15 @@ public class TestBundleTransfer extends TestIntegrationBase {
         final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
         assertEquals(invoiceItems.size(), 1);
         InvoiceItem theItem = invoiceItems.get(0);
-        Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,5,11)) == 0);
-        Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2013,5,11)) == 0);
-        Assert.assertTrue(theItem.getAmount().compareTo(new BigDecimal("2399.9500")) == 0);
+        assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,5,11)) == 0);
+        assertTrue(theItem.getEndDate().compareTo(new LocalDate(2013,5,11)) == 0);
+        assertTrue(theItem.getAmount().compareTo(new BigDecimal("2399.9500")) == 0);
     }
 
     @Test(groups = "slow")
     public void testBundleTransferWithBPMonthlyOnly() throws Exception {
 
-        final Account account = createAccountWithPaymentMethod(getAccountData(9));
+        final Account account = createAccountWithPaymentMethod(getAccountData(0));
 
         // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
 
@@ -148,7 +146,7 @@ public class TestBundleTransfer extends TestIntegrationBase {
         assertListenerStatus();
 
         // BUNDLE TRANSFER
-        final Account newAccount = createAccountWithPaymentMethod(getAccountData(15));
+        final Account newAccount = createAccountWithPaymentMethod(getAccountData(0));
 
         busHandler.pushExpectedEvent(NextEvent.TRANSFER);
         busHandler.pushExpectedEvent(NextEvent.INVOICE);
@@ -157,15 +155,22 @@ public class TestBundleTransfer extends TestIntegrationBase {
         assertTrue(busHandler.isCompleted(DELAY));
         assertListenerStatus();
 
-        List<Invoice> invoices =invoiceUserApi.getInvoicesByAccount(newAccount.getId());
+        // Verify the BCD of the new account
+        final BillCycleDay oldBCD = accountUserApi.getAccountById(account.getId()).getBillCycleDay();
+        final BillCycleDay newBCD = accountUserApi.getAccountById(newAccount.getId()).getBillCycleDay();
+        assertEquals(oldBCD.getDayOfMonthUTC(), 1);
+        // Day of the transfer
+        assertEquals(newBCD.getDayOfMonthUTC(), 3);
+
+        final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(newAccount.getId());
         assertEquals(invoices.size(), 1);
 
         final List<InvoiceItem> invoiceItems = invoices.get(0).getInvoiceItems();
         assertEquals(invoiceItems.size(), 1);
-        InvoiceItem theItem = invoiceItems.get(0);
-        Assert.assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012,5,3)) == 0);
-        Assert.assertTrue(theItem.getEndDate().compareTo(new LocalDate(2012,5,15)) == 0);
-        Assert.assertTrue(theItem.getAmount().compareTo(new BigDecimal("99.98")) == 0);
+        final InvoiceItem theItem = invoiceItems.get(0);
+        assertTrue(theItem.getStartDate().compareTo(new LocalDate(2012, 5, 3)) == 0);
+        assertTrue(theItem.getEndDate().compareTo(new LocalDate(2012, 6, 3)) == 0);
+        assertTrue(theItem.getAmount().compareTo(new BigDecimal("249.95")) == 0);
     }
 
     @Test(groups = "slow")

catalog/pom.xml 2(+1 -1)

diff --git a/catalog/pom.xml b/catalog/pom.xml
index 3d0e54c..9deb122 100644
--- a/catalog/pom.xml
+++ b/catalog/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-catalog</artifactId>
diff --git a/entitlement/pom.xml b/entitlement/pom.xml
index ccf0a6f..7df9de5 100644
--- a/entitlement/pom.xml
+++ b/entitlement/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-entitlement</artifactId>

invoice/pom.xml 2(+1 -1)

diff --git a/invoice/pom.xml b/invoice/pom.xml
index 0ff62d2..78b0dd0 100644
--- a/invoice/pom.xml
+++ b/invoice/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-invoice</artifactId>
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index 927db0b..012bdf2 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -40,6 +40,7 @@ import com.ning.billing.invoice.api.InvoicePayment;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.invoice.dao.InvoiceDao;
 import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
+import com.ning.billing.invoice.model.ExternalChargeInvoiceItem;
 import com.ning.billing.invoice.template.HtmlInvoiceGenerator;
 import com.ning.billing.util.api.TagApiException;
 import com.ning.billing.util.api.TagUserApi;
@@ -134,12 +135,42 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
     }
 
     @Override
+    public InvoiceItem getExternalChargeById(final UUID externalChargeId) throws InvoiceApiException {
+        final InvoiceItem externalChargeItem = dao.getExternalChargeById(externalChargeId);
+        if (externalChargeItem == null) {
+            throw new InvoiceApiException(ErrorCode.INVOICE_NO_SUCH_EXTERNAL_CHARGE, externalChargeId);
+        }
+
+        return new ExternalChargeInvoiceItem(externalChargeItem.getId(), externalChargeItem.getInvoiceId(), externalChargeItem.getAccountId(),
+                                             externalChargeItem.getPlanName(), externalChargeItem.getStartDate(),
+                                             externalChargeItem.getAmount(), externalChargeItem.getCurrency());
+    }
+
+    @Override
+    public InvoiceItem insertExternalCharge(final UUID accountId, final BigDecimal amount, @Nullable final String description,
+                                            final LocalDate effectiveDate, final Currency currency, final CallContext context) throws InvoiceApiException {
+        return insertExternalChargeForInvoice(accountId, null, amount, description, effectiveDate, currency, context);
+    }
+
+    @Override
+    public InvoiceItem insertExternalChargeForInvoice(final UUID accountId, final UUID invoiceId, final BigDecimal amount, @Nullable final String description,
+                                                      final LocalDate effectiveDate, final Currency currency, final CallContext context) throws InvoiceApiException {
+        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new InvoiceApiException(ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID, amount);
+        }
+
+        return dao.insertExternalCharge(accountId, invoiceId, description, amount, effectiveDate, currency, context);
+    }
+
+    @Override
     public InvoiceItem getCreditById(final UUID creditId) throws InvoiceApiException {
         final InvoiceItem creditItem = dao.getCreditById(creditId);
         if (creditItem == null) {
             throw new InvoiceApiException(ErrorCode.INVOICE_NO_SUCH_CREDIT, creditId);
         }
-        return new CreditAdjInvoiceItem(creditItem.getId(), creditItem.getInvoiceId(), creditItem.getAccountId(), creditItem.getStartDate(), creditItem.getAmount().negate(), creditItem.getCurrency());
+
+        return new CreditAdjInvoiceItem(creditItem.getId(), creditItem.getInvoiceId(), creditItem.getAccountId(),
+                                        creditItem.getStartDate(), creditItem.getAmount().negate(), creditItem.getCurrency());
     }
 
     @Override
@@ -154,11 +185,13 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
         if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
             throw new InvoiceApiException(ErrorCode.CREDIT_AMOUNT_INVALID, amount);
         }
+
         return dao.insertCredit(accountId, invoiceId, amount, effectiveDate, currency, context);
     }
 
     @Override
-    public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId, final LocalDate effectiveDate, final CallContext context) throws InvoiceApiException {
+    public InvoiceItem insertInvoiceItemAdjustment(final UUID accountId, final UUID invoiceId, final UUID invoiceItemId,
+                                                   final LocalDate effectiveDate, final CallContext context) throws InvoiceApiException {
         return insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, null, null, context);
     }
 
@@ -169,6 +202,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
         if (amount != null && amount.compareTo(BigDecimal.ZERO) <= 0) {
             throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID, amount);
         }
+
         return dao.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, amount, currency, context);
     }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
index 4fe738e..f6a0e95 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/DefaultInvoiceDao.java
@@ -44,6 +44,7 @@ import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
 import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import com.ning.billing.invoice.model.DefaultInvoice;
 import com.ning.billing.invoice.model.DefaultInvoicePayment;
+import com.ning.billing.invoice.model.ExternalChargeInvoiceItem;
 import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
 import com.ning.billing.invoice.model.RefundAdjInvoiceItem;
@@ -525,12 +526,42 @@ public class DefaultInvoiceDao implements InvoiceDao {
     }
 
     @Override
+    public InvoiceItem getExternalChargeById(final UUID externalChargeId) throws InvoiceApiException {
+        return invoiceItemSqlDao.getById(externalChargeId.toString());
+    }
+
+    @Override
+    public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, final String description,
+                                            final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context) {
+        return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
+            @Override
+            public InvoiceItem inTransaction(final InvoiceSqlDao transactional, final TransactionStatus status) throws Exception {
+                UUID invoiceIdForExternalCharge = invoiceId;
+                // Create an invoice for that external charge if it doesn't exist
+                if (invoiceIdForExternalCharge == null) {
+                    final Invoice invoiceForExternalCharge = new DefaultInvoice(accountId, effectiveDate, effectiveDate, currency);
+                    transactional.create(invoiceForExternalCharge, context);
+                    invoiceIdForExternalCharge = invoiceForExternalCharge.getId();
+                }
+
+                final InvoiceItem externalCharge = new ExternalChargeInvoiceItem(invoiceIdForExternalCharge, accountId, description,
+                                                                                 effectiveDate, amount, currency);
+
+                final InvoiceItemSqlDao transInvoiceItemDao = transactional.become(InvoiceItemSqlDao.class);
+                transInvoiceItemDao.create(externalCharge, context);
+
+                return externalCharge;
+            }
+        });
+    }
+
+    @Override
     public InvoiceItem getCreditById(final UUID creditId) throws InvoiceApiException {
         return invoiceItemSqlDao.getById(creditId.toString());
     }
 
     @Override
-    public InvoiceItem insertCredit(final UUID accountId, final UUID invoiceId, final BigDecimal positiveCreditAmount,
+    public InvoiceItem insertCredit(final UUID accountId, @Nullable final UUID invoiceId, final BigDecimal positiveCreditAmount,
                                     final LocalDate effectiveDate, final Currency currency, final CallContext context) {
         return invoiceSqlDao.inTransaction(new Transaction<InvoiceItem, InvoiceSqlDao>() {
             @Override
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
index a9104f9..0c9b60e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDao.java
@@ -74,7 +74,6 @@ public interface InvoiceDao {
     /**
      * Create a refund.
      *
-     *
      * @param paymentId                 payment associated with that refund
      * @param amount                    amount to refund
      * @param isInvoiceAdjusted         whether the refund should trigger an invoice or invoice item adjustment
@@ -97,9 +96,51 @@ public interface InvoiceDao {
 
     InvoicePayment getChargebackById(final UUID chargebackId) throws InvoiceApiException;
 
+    /**
+     * Retrieve am external charge by id.
+     *
+     * @param externalChargeId the external charge id
+     * @return the external charge invoice item
+     * @throws InvoiceApiException
+     */
+    InvoiceItem getExternalChargeById(final UUID externalChargeId) throws InvoiceApiException;
+
+    /**
+     * Add an external charge to a given account and invoice. If invoiceId is null, a new invoice will be created.
+     *
+     * @param accountId     the account id
+     * @param invoiceId     the invoice id
+     * @param description   a description for that charge
+     * @param amount        the external charge amount
+     * @param effectiveDate the day to post the external charge, in the account timezone
+     * @param currency      the external charge currency
+     * @param context       the call context
+     * @return the newly created external charge invoice item
+     */
+    InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final String description,
+                                     final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context);
+
+    /**
+     * Retrieve a credit by id.
+     *
+     * @param creditId the credit id
+     * @return the credit invoice item
+     * @throws InvoiceApiException
+     */
     InvoiceItem getCreditById(final UUID creditId) throws InvoiceApiException;
 
-    InvoiceItem insertCredit(final UUID accountId, final UUID invoiceId, final BigDecimal amount,
+    /**
+     * Add a credit to a given account and invoice. If invoiceId is null, a new invoice will be created.
+     *
+     * @param accountId     the account id
+     * @param invoiceId     the invoice id
+     * @param amount        the credit amount
+     * @param effectiveDate the day to grant the credit, in the account timezone
+     * @param currency      the credit currency
+     * @param context       the call context
+     * @return the newly created credit invoice item
+     */
+    InvoiceItem insertCredit(final UUID accountId, @Nullable final UUID invoiceId, final BigDecimal amount,
                              final LocalDate effectiveDate, final Currency currency, final CallContext context);
 
     /**
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
index 8156f30..d3758c4 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceItemSqlDao.java
@@ -46,6 +46,7 @@ import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.invoice.model.CreditAdjInvoiceItem;
 import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
+import com.ning.billing.invoice.model.ExternalChargeInvoiceItem;
 import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
@@ -132,6 +133,9 @@ public interface InvoiceItemSqlDao extends EntitySqlDao<InvoiceItem> {
 
             InvoiceItem item = null;
             switch (type) {
+                case EXTERNAL_CHARGE:
+                    item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, planName, startDate, amount, currency);
+                    break;
                 case FIXED:
                     item = new FixedPriceInvoiceItem(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, amount, currency);
                     break;
diff --git a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
index 1a08fd7..1c45243 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/generator/DefaultInvoiceGenerator.java
@@ -97,7 +97,7 @@ public class DefaultInvoiceGenerator implements InvoiceGenerator {
         if (existingInvoices != null) {
             for (final Invoice invoice : existingInvoices) {
                 for (final InvoiceItem item : invoice.getInvoiceItems()) {
-                    if (item.getSubscriptionId() == null || // Always include migration invoices, credits etc.
+                    if (item.getSubscriptionId() == null || // Always include migration invoices, credits, external charged etc.
                         !events.getSubscriptionIdsWithAutoInvoiceOff()
                                .contains(item.getSubscriptionId())) { //don't add items with auto_invoice_off tag
                         existingItems.add(item);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
new file mode 100644
index 0000000..ae5dbae
--- /dev/null
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/ExternalChargeInvoiceItem.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoiceItemType;
+
+public class ExternalChargeInvoiceItem extends InvoiceItemBase {
+
+    public ExternalChargeInvoiceItem(final UUID invoiceId, final UUID accountId, final String description, final LocalDate date,
+                                     final BigDecimal amount, final Currency currency) {
+        super(invoiceId, accountId, null, null, description, null, date, null, amount, currency);
+    }
+
+    public ExternalChargeInvoiceItem(final UUID id, final UUID invoiceId, final UUID accountId, final String description,
+                                     final LocalDate date, final BigDecimal amount, final Currency currency) {
+        super(id, invoiceId, accountId, null, (UUID) null, description, null, date, null, amount, currency);
+    }
+
+    @Override
+    public String getDescription() {
+        return String.format("%s (external charge) on %s", getPlanName(), getStartDate().toString());
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountId.hashCode();
+        result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + (planName != null ? planName.hashCode() : 0);
+        result = 31 * result + (phaseName != null ? phaseName.hashCode() : 0);
+        result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
+        result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
+        result = 31 * result + amount.hashCode();
+        result = 31 * result + currency.hashCode();
+        return result;
+    }
+
+    @Override
+    public int compareTo(final InvoiceItem item) {
+        if (!(item instanceof ExternalChargeInvoiceItem)) {
+            return 1;
+        }
+
+        final ExternalChargeInvoiceItem that = (ExternalChargeInvoiceItem) item;
+        final int compareAccounts = getAccountId().compareTo(that.getAccountId());
+        if (compareAccounts == 0) {
+            return getStartDate().compareTo(that.getStartDate());
+        } else {
+            return compareAccounts;
+        }
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("InvoiceItem = {").append("id = ").append(id.toString()).append(", ");
+        sb.append("invoiceId = ").append(invoiceId.toString()).append(", ");
+        sb.append("accountId = ").append(accountId.toString()).append(", ");
+        sb.append("description = ").append(planName).append(", ");
+        sb.append("startDate = ").append(startDate.toString()).append(", ");
+
+        sb.append("amount = ");
+        if (amount == null) {
+            sb.append("null");
+        } else {
+            sb.append(amount.toString());
+        }
+
+        sb.append("}");
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final ExternalChargeInvoiceItem that = (ExternalChargeInvoiceItem) o;
+        if (accountId.compareTo(that.accountId) != 0) {
+            return false;
+        }
+        if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null) {
+            return false;
+        }
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null) {
+            return false;
+        }
+        if (amount != null ? amount.compareTo(that.amount) != 0 : that.amount != null) {
+            return false;
+        }
+        if (currency != that.currency) {
+            return false;
+        }
+        if (startDate != null ? startDate.compareTo(that.startDate) != 0 : that.startDate != null) {
+            return false;
+        }
+        if (endDate != null ? endDate.compareTo(that.endDate) != 0 : that.endDate != null) {
+            return false;
+        }
+        if (phaseName != null ? !phaseName.equals(that.phaseName) : that.phaseName != null) {
+            return false;
+        }
+        if (planName != null ? !planName.equals(that.planName) : that.planName != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public InvoiceItemType getInvoiceItemType() {
+        return InvoiceItemType.EXTERNAL_CHARGE;
+    }
+}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
index 616e935..e505d9e 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/model/InvoiceItemList.java
@@ -53,7 +53,7 @@ public class InvoiceItemList extends ArrayList<InvoiceItem> {
     }
 
     public BigDecimal getChargedAmount() {
-        return getAmoutForItems(InvoiceItemType.RECURRING, InvoiceItemType.FIXED, InvoiceItemType.REPAIR_ADJ);
+        return getAmoutForItems(InvoiceItemType.EXTERNAL_CHARGE, InvoiceItemType.RECURRING, InvoiceItemType.FIXED, InvoiceItemType.REPAIR_ADJ);
     }
 
     public BigDecimal getCBAAmount() {
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index 2de15d7..23a3445 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -50,6 +50,63 @@ public class TestDefaultInvoiceUserApi extends InvoiceApiTestBase {
     }
 
     @Test(groups = "slow")
+    public void testPostExternalChargeOnNewInvoice() throws Exception {
+        // Initial account balance
+        final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+
+        // Post an external charge
+        final BigDecimal externalChargeAmount = BigDecimal.TEN;
+        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalCharge(accountId, externalChargeAmount, UUID.randomUUID().toString(),
+                                                                                          clock.getUTCToday(), accountCurrency, context);
+        Assert.assertNotNull(externalChargeInvoiceItem.getInvoiceId());
+        Assert.assertNotEquals(externalChargeInvoiceItem.getInvoiceId(), invoiceId);
+        Assert.assertEquals(externalChargeInvoiceItem.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
+        Assert.assertEquals(externalChargeInvoiceItem.getAccountId(), accountId);
+        Assert.assertEquals(externalChargeInvoiceItem.getAmount(), externalChargeAmount);
+        Assert.assertEquals(externalChargeInvoiceItem.getCurrency(), accountCurrency);
+        Assert.assertNull(externalChargeInvoiceItem.getLinkedItemId());
+
+        // Verify the adjusted invoice balance
+        final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(externalChargeInvoiceItem.getInvoiceId()).getBalance();
+        Assert.assertEquals(adjustedInvoiceBalance.compareTo(externalChargeAmount), 0);
+
+        // Verify the adjusted account balance
+        final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(adjustedAccountBalance, accountBalance.add(externalChargeAmount));
+    }
+
+    @Test(groups = "slow")
+    public void testPostExternalChargeOnExistingInvoice() throws Exception {
+        // Verify the initial invoice balance
+        final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        Assert.assertEquals(invoiceBalance.compareTo(BigDecimal.ZERO), 1);
+
+        // Verify the initial account balance
+        final BigDecimal accountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(accountBalance, invoiceBalance);
+
+        // Post an external charge
+        final BigDecimal externalChargeAmount = BigDecimal.TEN;
+        final InvoiceItem externalChargeInvoiceItem = invoiceUserApi.insertExternalChargeForInvoice(accountId, invoiceId,
+                                                                                                    externalChargeAmount, UUID.randomUUID().toString(),
+                                                                                                    clock.getUTCToday(), accountCurrency, context);
+        Assert.assertEquals(externalChargeInvoiceItem.getInvoiceId(), invoiceId);
+        Assert.assertEquals(externalChargeInvoiceItem.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
+        Assert.assertEquals(externalChargeInvoiceItem.getAccountId(), accountId);
+        Assert.assertEquals(externalChargeInvoiceItem.getAmount(), externalChargeAmount);
+        Assert.assertEquals(externalChargeInvoiceItem.getCurrency(), accountCurrency);
+        Assert.assertNull(externalChargeInvoiceItem.getLinkedItemId());
+
+        // Verify the adjusted invoice balance
+        final BigDecimal adjustedInvoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
+        Assert.assertEquals(adjustedInvoiceBalance.compareTo(invoiceBalance.add(externalChargeAmount)), 0);
+
+        // Verify the adjusted account balance
+        final BigDecimal adjustedAccountBalance = invoiceUserApi.getAccountBalance(accountId);
+        Assert.assertEquals(adjustedAccountBalance, adjustedInvoiceBalance);
+    }
+
+    @Test(groups = "slow")
     public void testAdjustFullInvoice() throws Exception {
         // Verify the initial invoice balance
         final BigDecimal invoiceBalance = invoiceUserApi.getInvoice(invoiceId).getBalance();
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
index 301866c..a7884b5 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/MockInvoiceDao.java
@@ -259,6 +259,16 @@ public class MockInvoiceDao implements InvoiceDao {
     }
 
     @Override
+    public InvoiceItem getExternalChargeById(final UUID externalChargeId) throws InvoiceApiException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public InvoiceItem insertExternalCharge(final UUID accountId, @Nullable final UUID invoiceId, @Nullable final String description, final BigDecimal amount, final LocalDate effectiveDate, final Currency currency, final CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public InvoiceItem getCreditById(final UUID creditId) throws InvoiceApiException {
         throw new UnsupportedOperationException();
     }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java
index 4c98b52..4bf050e 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/dao/TestInvoiceItemDao.java
@@ -20,7 +20,6 @@ import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
-import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 import org.testng.annotations.Test;
 
@@ -28,6 +27,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.model.CreditBalanceAdjInvoiceItem;
 import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.ExternalChargeInvoiceItem;
 import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
 import com.ning.billing.invoice.model.RecurringInvoiceItem;
 
@@ -158,4 +158,18 @@ public class TestInvoiceItemDao extends InvoiceDaoTestBase {
         final InvoiceItem savedItem = invoiceItemSqlDao.getById(fixedPriceInvoiceItem.getId().toString());
         assertEquals(savedItem, fixedPriceInvoiceItem);
     }
+
+    @Test(groups = "slow")
+    public void testExternalChargeInvoiceSqlDao() throws Exception {
+        final UUID invoiceId = UUID.randomUUID();
+        final UUID accountId = UUID.randomUUID();
+        final String description = UUID.randomUUID().toString();
+        final LocalDate startDate = new LocalDate(2012, 4, 1);
+        final InvoiceItem externalChargeInvoiceItem = new ExternalChargeInvoiceItem(invoiceId, accountId, description,
+                                                                                    startDate, TEN, Currency.USD);
+        invoiceItemSqlDao.create(externalChargeInvoiceItem, context);
+
+        final InvoiceItem savedItem = invoiceItemSqlDao.getById(externalChargeInvoiceItem.getId().toString());
+        assertEquals(savedItem, externalChargeInvoiceItem);
+    }
 }
diff --git a/invoice/src/test/java/com/ning/billing/invoice/model/TestExternalChargeInvoiceItem.java b/invoice/src/test/java/com/ning/billing/invoice/model/TestExternalChargeInvoiceItem.java
new file mode 100644
index 0000000..cd59851
--- /dev/null
+++ b/invoice/src/test/java/com/ning/billing/invoice/model/TestExternalChargeInvoiceItem.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.invoice.model;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.joda.time.LocalDate;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItemType;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
+
+public class TestExternalChargeInvoiceItem {
+
+    private final Clock clock = new ClockMock();
+
+    @Test(groups = "fast")
+    public void testEquals() throws Exception {
+        final UUID id = UUID.randomUUID();
+        final UUID invoiceId = UUID.randomUUID();
+        final UUID accountId = UUID.randomUUID();
+        final String description = UUID.randomUUID().toString();
+        final LocalDate effectiveDate = clock.getUTCToday();
+        final BigDecimal amount = BigDecimal.TEN;
+        final Currency currency = Currency.GBP;
+        final ExternalChargeInvoiceItem item = new ExternalChargeInvoiceItem(id, invoiceId, accountId, description,
+                                                                             effectiveDate, amount, currency);
+        Assert.assertEquals(item.getAccountId(), accountId);
+        Assert.assertEquals(item.getAmount(), amount);
+        Assert.assertEquals(item.getCurrency(), currency);
+        Assert.assertEquals(item.getInvoiceItemType(), InvoiceItemType.EXTERNAL_CHARGE);
+        Assert.assertEquals(item.getPlanName(), description);
+        Assert.assertNull(item.getBundleId());
+        Assert.assertNull(item.getEndDate());
+        Assert.assertNull(item.getLinkedItemId());
+        Assert.assertNull(item.getPhaseName());
+        Assert.assertNull(item.getRate());
+        Assert.assertNull(item.getSubscriptionId());
+
+        Assert.assertEquals(item, item);
+
+        final ExternalChargeInvoiceItem otherItem = new ExternalChargeInvoiceItem(id, invoiceId, UUID.randomUUID(), description,
+                                                                                  effectiveDate, amount, currency);
+        Assert.assertNotEquals(otherItem, item);
+
+        // Check comparison (done by start date)
+        final ExternalChargeInvoiceItem itemBefore = new ExternalChargeInvoiceItem(id, invoiceId, accountId, description,
+                                                                                   effectiveDate.minusDays(1), amount, currency);
+        Assert.assertEquals(itemBefore.compareTo(item), -1);
+        final ExternalChargeInvoiceItem itemAfter = new ExternalChargeInvoiceItem(id, invoiceId, accountId, description,
+                                                                                  effectiveDate.plusDays(1), amount, currency);
+        Assert.assertEquals(itemAfter.compareTo(item), 1);
+    }
+}

jaxrs/pom.xml 2(+1 -1)

diff --git a/jaxrs/pom.xml b/jaxrs/pom.xml
index 0963879..684e6fc 100644
--- a/jaxrs/pom.xml
+++ b/jaxrs/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-jaxrs</artifactId>
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
index 05f405f..a83e8b7 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
@@ -29,33 +29,25 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class AccountJson extends AccountJsonSimple {
-    // STEPH Missing city, locale, postalCode from https://home.ninginc.com:8443/display/REVINFRA/Killbill+1.0+APIs
 
     private final String name;
-
     private final Integer length;
-
     private final String email;
-
     private final BillCycleDayJson billCycleDayJson;
-
     private final String currency;
-
     private final String paymentMethodId;
-
     private final String timeZone;
-
     private final String address1;
-
     private final String address2;
-
+    private final String postalCode;
     private final String company;
-
+    private final String city;
     private final String state;
-
     private final String country;
-
+    private final String locale;
     private final String phone;
+    private final Boolean isMigrated;
+    private final Boolean isNotifiedForInvoices;
 
     public AccountJson(final Account account) {
         super(account.getId().toString(), account.getExternalKey());
@@ -68,10 +60,15 @@ public class AccountJson extends AccountJsonSimple {
         this.timeZone = account.getTimeZone().toString();
         this.address1 = account.getAddress1();
         this.address2 = account.getAddress2();
+        this.postalCode = account.getPostalCode();
         this.company = account.getCompanyName();
+        this.city = account.getCity();
         this.state = account.getStateOrProvince();
         this.country = account.getCountry();
+        this.locale = account.getLocale();
         this.phone = account.getPhone();
+        this.isMigrated = account.isMigrated();
+        this.isNotifiedForInvoices = account.isNotifiedForInvoices();
     }
 
     public AccountData toAccountData() {
@@ -88,7 +85,7 @@ public class AccountJson extends AccountJsonSimple {
 
             @Override
             public String getPostalCode() {
-                return null;
+                return postalCode;
             }
 
             @Override
@@ -98,12 +95,12 @@ public class AccountJson extends AccountJsonSimple {
 
             @Override
             public Boolean isMigrated() {
-                return false;
+                return isMigrated;
             }
 
             @Override
             public Boolean isNotifiedForInvoices() {
-                return false;
+                return isNotifiedForInvoices;
             }
 
             @Override
@@ -118,8 +115,7 @@ public class AccountJson extends AccountJsonSimple {
 
             @Override
             public String getLocale() {
-                // TODO
-                return "en";
+                return locale;
             }
 
             @Override
@@ -154,7 +150,7 @@ public class AccountJson extends AccountJsonSimple {
 
             @Override
             public String getCity() {
-                return null;
+                return city;
             }
 
             @Override
@@ -200,10 +196,15 @@ public class AccountJson extends AccountJsonSimple {
                        @JsonProperty("timezone") final String timeZone,
                        @JsonProperty("address1") final String address1,
                        @JsonProperty("address2") final String address2,
+                       @JsonProperty("postalCode") final String postalCode,
                        @JsonProperty("company") final String company,
+                       @JsonProperty("city") final String city,
                        @JsonProperty("state") final String state,
                        @JsonProperty("country") final String country,
-                       @JsonProperty("phone") final String phone) {
+                       @JsonProperty("locale") final String locale,
+                       @JsonProperty("phone") final String phone,
+                       @JsonProperty("isMigrated") final Boolean isMigrated,
+                       @JsonProperty("isNotifiedForInvoices") final Boolean isNotifiedForInvoices) {
         super(accountId, externalKey);
         this.name = name;
         this.length = length;
@@ -214,10 +215,15 @@ public class AccountJson extends AccountJsonSimple {
         this.timeZone = timeZone;
         this.address1 = address1;
         this.address2 = address2;
+        this.postalCode = postalCode;
         this.company = company;
+        this.city = city;
         this.state = state;
         this.country = country;
+        this.locale = locale;
         this.phone = phone;
+        this.isMigrated = isMigrated;
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
     }
 
     public String getName() {
@@ -256,10 +262,18 @@ public class AccountJson extends AccountJsonSimple {
         return address2;
     }
 
+    public String getPostalCode() {
+        return postalCode;
+    }
+
     public String getCompany() {
         return company;
     }
 
+    public String getCity() {
+        return city;
+    }
+
     public String getState() {
         return state;
     }
@@ -268,167 +282,152 @@ public class AccountJson extends AccountJsonSimple {
         return country;
     }
 
+    public String getLocale() {
+        return locale;
+    }
+
     public String getPhone() {
         return phone;
     }
 
+    @JsonProperty("isMigrated")
+    public Boolean isMigrated() {
+        return isMigrated;
+    }
+
+    @JsonProperty("isNotifiedForInvoices")
+    public Boolean isNotifiedForInvoices() {
+        return isNotifiedForInvoices;
+    }
+
     @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result
-                 + ((accountId == null) ? 0 : accountId.hashCode());
-        result = prime * result
-                 + ((address1 == null) ? 0 : address1.hashCode());
-        result = prime * result
-                 + ((address2 == null) ? 0 : address2.hashCode());
-        result = prime * result
-                 + ((billCycleDayJson == null) ? 0 : billCycleDayJson.hashCode());
-        result = prime * result + ((company == null) ? 0 : company.hashCode());
-        result = prime * result + ((country == null) ? 0 : country.hashCode());
-        result = prime * result
-                 + ((currency == null) ? 0 : currency.hashCode());
-        result = prime * result + ((email == null) ? 0 : email.hashCode());
-        result = prime * result
-                 + ((externalKey == null) ? 0 : externalKey.hashCode());
-        result = prime * result + ((length == null) ? 0 : length.hashCode());
-        result = prime * result + ((name == null) ? 0 : name.hashCode());
-        result = prime * result
-                 + ((paymentMethodId == null) ? 0 : paymentMethodId.hashCode());
-        result = prime * result + ((phone == null) ? 0 : phone.hashCode());
-        result = prime * result + ((state == null) ? 0 : state.hashCode());
-        result = prime * result
-                 + ((timeZone == null) ? 0 : timeZone.hashCode());
-        return result;
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("AccountJson");
+        sb.append("{name='").append(name).append('\'');
+        sb.append(", length=").append(length);
+        sb.append(", email='").append(email).append('\'');
+        sb.append(", billCycleDayJson=").append(billCycleDayJson);
+        sb.append(", currency='").append(currency).append('\'');
+        sb.append(", paymentMethodId='").append(paymentMethodId).append('\'');
+        sb.append(", timeZone='").append(timeZone).append('\'');
+        sb.append(", address1='").append(address1).append('\'');
+        sb.append(", address2='").append(address2).append('\'');
+        sb.append(", postalCode='").append(postalCode).append('\'');
+        sb.append(", company='").append(company).append('\'');
+        sb.append(", city='").append(city).append('\'');
+        sb.append(", state='").append(state).append('\'');
+        sb.append(", country='").append(country).append('\'');
+        sb.append(", locale='").append(locale).append('\'');
+        sb.append(", phone='").append(phone).append('\'');
+        sb.append(", isMigrated=").append(isMigrated);
+        sb.append(", isNotifiedForInvoices=").append(isNotifiedForInvoices);
+        sb.append('}');
+        return sb.toString();
     }
 
-    // Used to check POST versus GET
-    public boolean equalsNoId(final Object obj) {
-        if (this == obj) {
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
             return true;
         }
-        if (obj == null) {
+        if (o == null || getClass() != o.getClass()) {
             return false;
         }
-        if (getClass() != obj.getClass()) {
+
+        final AccountJson that = (AccountJson) o;
+
+        if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null) {
             return false;
+        } else {
+            return equalsNoId(that);
         }
-        final AccountJson other = (AccountJson) obj;
-        if (address1 == null) {
-            if (other.address1 != null) {
-                return false;
-            }
-        } else if (!address1.equals(other.address1)) {
+    }
+
+    // Used to check POST versus GET
+    public boolean equalsNoId(final AccountJson that) {
+        if (address1 != null ? !address1.equals(that.address1) : that.address1 != null) {
             return false;
         }
-        if (address2 == null) {
-            if (other.address2 != null) {
-                return false;
-            }
-        } else if (!address2.equals(other.address2)) {
+        if (address2 != null ? !address2.equals(that.address2) : that.address2 != null) {
             return false;
         }
-        if (billCycleDayJson == null) {
-            if (other.billCycleDayJson != null) {
-                return false;
-            }
-        } else if (!billCycleDayJson.equals(other.billCycleDayJson)) {
+        if (billCycleDayJson != null ? !billCycleDayJson.equals(that.billCycleDayJson) : that.billCycleDayJson != null) {
             return false;
         }
-        if (company == null) {
-            if (other.company != null) {
-                return false;
-            }
-        } else if (!company.equals(other.company)) {
+        if (city != null ? !city.equals(that.city) : that.city != null) {
             return false;
         }
-        if (country == null) {
-            if (other.country != null) {
-                return false;
-            }
-        } else if (!country.equals(other.country)) {
+        if (company != null ? !company.equals(that.company) : that.company != null) {
             return false;
         }
-        if (currency == null) {
-            if (other.currency != null) {
-                return false;
-            }
-        } else if (!currency.equals(other.currency)) {
+        if (country != null ? !country.equals(that.country) : that.country != null) {
             return false;
         }
-        if (email == null) {
-            if (other.email != null) {
-                return false;
-            }
-        } else if (!email.equals(other.email)) {
+        if (currency != null ? !currency.equals(that.currency) : that.currency != null) {
             return false;
         }
-        if (externalKey == null) {
-            if (other.externalKey != null) {
-                return false;
-            }
-        } else if (!externalKey.equals(other.externalKey)) {
+        if (email != null ? !email.equals(that.email) : that.email != null) {
             return false;
         }
-        if (length == null) {
-            if (other.length != null) {
-                return false;
-            }
-        } else if (!length.equals(other.length)) {
+        if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
             return false;
         }
-        if (name == null) {
-            if (other.name != null) {
-                return false;
-            }
-        } else if (!name.equals(other.name)) {
+        if (isMigrated != null ? !isMigrated.equals(that.isMigrated) : that.isMigrated != null) {
             return false;
         }
-        if (paymentMethodId == null) {
-            if (other.paymentMethodId != null) {
-                return false;
-            }
-        } else if (!paymentMethodId.equals(other.paymentMethodId)) {
+        if (isNotifiedForInvoices != null ? !isNotifiedForInvoices.equals(that.isNotifiedForInvoices) : that.isNotifiedForInvoices != null) {
             return false;
         }
-        if (phone == null) {
-            if (other.phone != null) {
-                return false;
-            }
-        } else if (!phone.equals(other.phone)) {
+        if (length != null ? !length.equals(that.length) : that.length != null) {
             return false;
         }
-        if (state == null) {
-            if (other.state != null) {
-                return false;
-            }
-        } else if (!state.equals(other.state)) {
+        if (locale != null ? !locale.equals(that.locale) : that.locale != null) {
             return false;
         }
-        if (timeZone == null) {
-            if (other.timeZone != null) {
-                return false;
-            }
-        } else if (!timeZone.equals(other.timeZone)) {
+        if (name != null ? !name.equals(that.name) : that.name != null) {
+            return false;
+        }
+        if (paymentMethodId != null ? !paymentMethodId.equals(that.paymentMethodId) : that.paymentMethodId != null) {
+            return false;
+        }
+        if (phone != null ? !phone.equals(that.phone) : that.phone != null) {
+            return false;
+        }
+        if (postalCode != null ? !postalCode.equals(that.postalCode) : that.postalCode != null) {
+            return false;
+        }
+        if (state != null ? !state.equals(that.state) : that.state != null) {
+            return false;
+        }
+        if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) {
             return false;
         }
+
         return true;
     }
 
     @Override
-    public boolean equals(final Object obj) {
-        if (!equalsNoId(obj)) {
-            return false;
-        } else {
-            final AccountJson other = (AccountJson) obj;
-            if (accountId == null) {
-                if (other.accountId != null) {
-                    return false;
-                }
-            } else if (!accountId.equals(other.accountId)) {
-                return false;
-            }
-        }
-        return true;
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (length != null ? length.hashCode() : 0);
+        result = 31 * result + (email != null ? email.hashCode() : 0);
+        result = 31 * result + (billCycleDayJson != null ? billCycleDayJson.hashCode() : 0);
+        result = 31 * result + (currency != null ? currency.hashCode() : 0);
+        result = 31 * result + (paymentMethodId != null ? paymentMethodId.hashCode() : 0);
+        result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0);
+        result = 31 * result + (address1 != null ? address1.hashCode() : 0);
+        result = 31 * result + (address2 != null ? address2.hashCode() : 0);
+        result = 31 * result + (postalCode != null ? postalCode.hashCode() : 0);
+        result = 31 * result + (company != null ? company.hashCode() : 0);
+        result = 31 * result + (city != null ? city.hashCode() : 0);
+        result = 31 * result + (state != null ? state.hashCode() : 0);
+        result = 31 * result + (country != null ? country.hashCode() : 0);
+        result = 31 * result + (locale != null ? locale.hashCode() : 0);
+        result = 31 * result + (phone != null ? phone.hashCode() : 0);
+        result = 31 * result + (isMigrated != null ? isMigrated.hashCode() : 0);
+        result = 31 * result + (isNotifiedForInvoices != null ? isNotifiedForInvoices.hashCode() : 0);
+        return result;
     }
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
index 01c3bda..e37400c 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
@@ -62,6 +62,10 @@ public class InvoiceApiExceptionMapper extends ExceptionMapperBase implements Ex
             return buildBadRequestResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
+        } else if (exception.getCode() == ErrorCode.INVOICE_NO_SUCH_EXTERNAL_CHARGE.getCode()) {
+            return buildBadRequestResponse(exception, uriInfo);
+        } else if (exception.getCode() == ErrorCode.EXTERNAL_CHARGE_AMOUNT_INVALID.getCode()) {
+            return buildBadRequestResponse(exception, uriInfo);
         } else {
             return buildBadRequestResponse(exception, uriInfo);
         }
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 4205b8c..13960aa 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
@@ -16,9 +16,6 @@
 
 package com.ning.billing.jaxrs.resources;
 
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
-import static javax.ws.rs.core.MediaType.TEXT_HTML;
-
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -46,11 +43,11 @@ import org.joda.time.format.ISODateTimeFormat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountUserApi;
+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.invoice.api.InvoiceItem;
@@ -74,6 +71,12 @@ import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.dao.ObjectType;
 
+import com.google.common.base.Objects;
+import com.google.inject.Inject;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.TEXT_HTML;
+
 @Path(JaxrsResource.INVOICES_PATH)
 public class InvoiceResource extends JaxRsResourceBase {
 
@@ -222,6 +225,68 @@ public class InvoiceResource extends JaxRsResourceBase {
         return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", adjustmentItem.getInvoiceId());
     }
 
+    @POST
+    @Produces(APPLICATION_JSON)
+    @Consumes(APPLICATION_JSON)
+    @Path(CHARGES)
+    public Response createExternalCharge(final InvoiceItemJsonSimple externalChargeJson,
+                                         @QueryParam(QUERY_REQUESTED_DT) final String requestedDateTimeString,
+                                         @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) throws AccountApiException, InvoiceApiException {
+        final Account account = accountApi.getAccountById(UUID.fromString(externalChargeJson.getAccountId()));
+        final CallContext callContext = context.createContext(createdBy, reason, comment);
+
+        // Get the effective date of the external charge, in the account timezone
+        final LocalDate requestedDate;
+        if (requestedDateTimeString == null) {
+            requestedDate = clock.getUTCToday();
+        } else {
+            final DateTime requestedDateTime = DATE_TIME_FORMATTER.parseDateTime(requestedDateTimeString);
+            requestedDate = requestedDateTime.toDateTime(account.getTimeZone()).toLocalDate();
+        }
+
+        final Currency currency = Objects.firstNonNull(externalChargeJson.getCurrency(), account.getCurrency());
+        final InvoiceItem externalCharge = invoiceApi.insertExternalCharge(account.getId(), externalChargeJson.getAmount(),
+                                                                           externalChargeJson.getDescription(), requestedDate,
+                                                                           currency, callContext);
+
+        return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
+    }
+
+    @POST
+    @Produces(APPLICATION_JSON)
+    @Consumes(APPLICATION_JSON)
+    @Path("/{invoiceId:" + UUID_PATTERN + "}/" + CHARGES)
+    public Response createExternalChargeForInvoice(final InvoiceItemJsonSimple externalChargeJson,
+                                                   @PathParam("invoiceId") final String invoiceIdString,
+                                                   @QueryParam(QUERY_REQUESTED_DT) final String requestedDateTimeString,
+                                                   @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) throws AccountApiException, InvoiceApiException {
+        final Account account = accountApi.getAccountById(UUID.fromString(externalChargeJson.getAccountId()));
+        final CallContext callContext = context.createContext(createdBy, reason, comment);
+
+        // Get the effective date of the external charge, in the account timezone
+        final LocalDate requestedDate;
+        if (requestedDateTimeString == null) {
+            requestedDate = clock.getUTCToday();
+        } else {
+            final DateTime requestedDateTime = DATE_TIME_FORMATTER.parseDateTime(requestedDateTimeString);
+            requestedDate = requestedDateTime.toDateTime(account.getTimeZone()).toLocalDate();
+        }
+
+        final UUID invoiceId = UUID.fromString(invoiceIdString);
+        final Currency currency = Objects.firstNonNull(externalChargeJson.getCurrency(), account.getCurrency());
+        final InvoiceItem externalCharge = invoiceApi.insertExternalChargeForInvoice(account.getId(), invoiceId,
+                                                                                     externalChargeJson.getAmount(), externalChargeJson.getDescription(),
+                                                                                     requestedDate, currency, callContext);
+
+        return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", externalCharge.getInvoiceId(), uriInfo.getBaseUri().toString());
+    }
+
     @GET
     @Path("/{invoiceId:" + UUID_PATTERN + "}/" + PAYMENTS)
     @Produces(APPLICATION_JSON)
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 17959f9..e52d1c2 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
@@ -13,16 +13,17 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.jaxrs.resources;
 
 public interface JaxrsResource {
+
     public static final String API_PREFIX = "";
     public static final String API_VERSION = "/1.0";
     public static final String API_POSTFIX = "/kb";
 
     public static final String PREFIX = API_PREFIX + API_VERSION + API_POSTFIX;
 
-
     public static final String TIMELINE = "timeline";
 
     /*
@@ -85,6 +86,9 @@ public interface JaxrsResource {
     public static final String INVOICES = "invoices";
     public static final String INVOICES_PATH = PREFIX + "/" + INVOICES;
 
+    public static final String CHARGES = "charges";
+    public static final String CHARGES_PATH = PREFIX + "/" + INVOICES + "/" + CHARGES;
+
     public static final String PAYMENTS = "payments";
     public static final String PAYMENTS_PATH = PREFIX + "/" + PAYMENTS;
 
diff --git a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java
index d787fc8..3c70a20 100644
--- a/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java
+++ b/jaxrs/src/test/java/com/ning/billing/jaxrs/json/TestAccountJson.java
@@ -28,12 +28,8 @@ import com.ning.billing.jaxrs.JaxrsTestSuite;
 import com.ning.billing.mock.MockAccountBuilder;
 import com.ning.billing.mock.api.MockBillCycleDay;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 public class TestAccountJson extends JaxrsTestSuite {
 
-    private static final ObjectMapper mapper = new ObjectMapper();
-
     @Test(groups = "fast")
     public void testJson() throws Exception {
         final String accountId = UUID.randomUUID().toString();
@@ -47,15 +43,20 @@ public class TestAccountJson extends JaxrsTestSuite {
         final String timeZone = UUID.randomUUID().toString();
         final String address1 = UUID.randomUUID().toString();
         final String address2 = UUID.randomUUID().toString();
+        final String postalCode = UUID.randomUUID().toString();
         final String company = UUID.randomUUID().toString();
+        final String city = UUID.randomUUID().toString();
         final String state = UUID.randomUUID().toString();
         final String country = UUID.randomUUID().toString();
+        final String locale = UUID.randomUUID().toString();
         final String phone = UUID.randomUUID().toString();
+        final Boolean isMigrated = true;
+        final Boolean isNotifiedForInvoice = false;
 
         final AccountJson accountJson = new AccountJson(accountId, name, length, externalKey,
                                                         email, billCycleDay, currency, paymentMethodId,
-                                                        timeZone, address1, address2, company, state,
-                                                        country, phone);
+                                                        timeZone, address1, address2, postalCode, company, city, state,
+                                                        country, locale, phone, isMigrated, isNotifiedForInvoice);
         Assert.assertEquals(accountJson.getAccountId(), accountId);
         Assert.assertEquals(accountJson.getName(), name);
         Assert.assertEquals(accountJson.getLength(), length);
@@ -67,21 +68,17 @@ public class TestAccountJson extends JaxrsTestSuite {
         Assert.assertEquals(accountJson.getTimeZone(), timeZone);
         Assert.assertEquals(accountJson.getAddress1(), address1);
         Assert.assertEquals(accountJson.getAddress2(), address2);
+        Assert.assertEquals(accountJson.getPostalCode(), postalCode);
         Assert.assertEquals(accountJson.getCompany(), company);
+        Assert.assertEquals(accountJson.getCity(), city);
         Assert.assertEquals(accountJson.getState(), state);
         Assert.assertEquals(accountJson.getCountry(), country);
+        Assert.assertEquals(accountJson.getLocale(), locale);
         Assert.assertEquals(accountJson.getPhone(), phone);
+        Assert.assertEquals(accountJson.isMigrated(), isMigrated);
+        Assert.assertEquals(accountJson.isNotifiedForInvoices(), isNotifiedForInvoice);
 
         final String asJson = mapper.writeValueAsString(accountJson);
-        Assert.assertEquals(asJson, "{\"accountId\":\"" + accountJson.getAccountId() + "\",\"name\":\"" + accountJson.getName() + "\"," +
-                                    "\"externalKey\":\"" + accountJson.getExternalKey() + "\",\"email\":\"" + accountJson.getEmail() + "\"," +
-                                    "\"billCycleDay\":" + accountJson.getBillCycleDay() + "," +
-                                    "\"currency\":\"" + accountJson.getCurrency() + "\",\"paymentMethodId\":\"" + accountJson.getPaymentMethodId() + "\"," +
-                                    "\"address1\":\"" + accountJson.getAddress1() + "\",\"address2\":\"" + accountJson.getAddress2() + "\"," +
-                                    "\"company\":\"" + accountJson.getCompany() + "\",\"state\":\"" + accountJson.getState() + "\"," +
-                                    "\"country\":\"" + accountJson.getCountry() + "\",\"phone\":\"" + accountJson.getPhone() + "\"," +
-                                    "\"length\":" + accountJson.getLength() + ",\"timeZone\":\"" + accountJson.getTimeZone() + "\"}");
-
         final AccountJson fromJson = mapper.readValue(asJson, AccountJson.class);
         Assert.assertEquals(fromJson, accountJson);
     }
@@ -116,13 +113,17 @@ public class TestAccountJson extends JaxrsTestSuite {
         Assert.assertEquals(accountJson.getAddress2(), account.getAddress2());
         Assert.assertEquals(accountJson.getBillCycleDay().toString(), "{\"dayOfMonthLocal\":" + bcd + ",\"dayOfMonthUTC\":" + bcd + "}");
         Assert.assertEquals(accountJson.getCountry(), account.getCountry());
+        Assert.assertEquals(accountJson.getLocale(), account.getLocale());
         Assert.assertEquals(accountJson.getCompany(), account.getCompanyName());
+        Assert.assertEquals(accountJson.getCity(), account.getCity());
         Assert.assertEquals(accountJson.getCurrency(), account.getCurrency().toString());
         Assert.assertEquals(accountJson.getEmail(), account.getEmail());
         Assert.assertEquals(accountJson.getExternalKey(), account.getExternalKey());
         Assert.assertEquals(accountJson.getName(), account.getName());
         Assert.assertEquals(accountJson.getPaymentMethodId(), account.getPaymentMethodId().toString());
         Assert.assertEquals(accountJson.getPhone(), account.getPhone());
+        Assert.assertEquals(accountJson.isMigrated(), account.isMigrated());
+        Assert.assertEquals(accountJson.isNotifiedForInvoices(), account.isNotifiedForInvoices());
         Assert.assertEquals(accountJson.getState(), account.getStateOrProvince());
         Assert.assertEquals(accountJson.getTimeZone(), account.getTimeZone().toString());
     }

junction/pom.xml 2(+1 -1)

diff --git a/junction/pom.xml b/junction/pom.xml
index 296b703..617a57e 100644
--- a/junction/pom.xml
+++ b/junction/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-junction</artifactId>
diff --git a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
index 8ecc101..0580d60 100644
--- a/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
+++ b/junction/src/main/java/com/ning/billing/junction/plumbing/billing/BillCycleDayCalculator.java
@@ -16,6 +16,8 @@
 
 package com.ning.billing.junction.plumbing.billing;
 
+import java.util.List;
+
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.slf4j.Logger;
@@ -92,7 +94,7 @@ public class BillCycleDayCalculator {
             case ACCOUNT:
                 result = account.getBillCycleDay();
                 if (result == null || result.getDayOfMonthUTC() == 0) {
-                    result = calculateBcdFromSubscription(subscription, plan, account);
+                    result = calculateBcdFromSubscription(subscription, plan, account, catalog);
                 }
                 break;
             case BUNDLE:
@@ -103,10 +105,10 @@ public class BillCycleDayCalculator {
                     final EffectiveSubscriptionEvent previousTransition = baseSub.getPreviousTransition();
                     basePlan = catalog.findPlan(previousTransition.getPreviousPlan(), previousTransition.getEffectiveTransitionTime(), previousTransition.getSubscriptionStartDate());
                 }
-                result = calculateBcdFromSubscription(baseSub, basePlan, account);
+                result = calculateBcdFromSubscription(baseSub, basePlan, account, catalog);
                 break;
             case SUBSCRIPTION:
-                result = calculateBcdFromSubscription(subscription, plan, account);
+                result = calculateBcdFromSubscription(subscription, plan, account, catalog);
                 break;
         }
 
@@ -118,10 +120,30 @@ public class BillCycleDayCalculator {
     }
 
     @VisibleForTesting
-    BillCycleDay calculateBcdFromSubscription(final Subscription subscription, final Plan plan, final Account account) throws AccountApiException {
+    BillCycleDay calculateBcdFromSubscription(final Subscription subscription, final Plan plan, final Account account, final Catalog catalog)
+            throws AccountApiException, CatalogApiException {
+        // Retrieve the initial phase type for that subscription
+        // TODO - this should be extracted somewhere, along with this code above
+        final PhaseType initialPhaseType;
+        final List<EffectiveSubscriptionEvent> transitions = subscription.getAllTransitions();
+        if (transitions.size() == 0) {
+            initialPhaseType = null;
+        } else {
+            final DateTime requestedDate = subscription.getStartDate();
+            final String initialPhaseString = transitions.get(0).getNextPhase();
+            if (initialPhaseString == null) {
+                initialPhaseType = null;
+            } else {
+                final PlanPhase initialPhase = catalog.findPhase(initialPhaseString, requestedDate, subscription.getStartDate());
+                if (initialPhase == null) {
+                    initialPhaseType = null;
+                } else {
+                    initialPhaseType = initialPhase.getPhaseType();
+                }
+            }
+        }
 
-        final PhaseType currentPhaseType = subscription.getCurrentPhase() != null ?  subscription.getCurrentPhase().getPhaseType() : null;
-        final DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate(), currentPhaseType);
+        final DateTime date = plan.dateOfFirstRecurringNonZeroCharge(subscription.getStartDate(), initialPhaseType);
         // There are really two kinds of billCycleDay:
         // - a System billingCycleDay which should be computed from UTC time (in order to get the correct notification time at
         //   the end of each service period)
diff --git a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
index f243bbf..fc10afb 100644
--- a/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
+++ b/junction/src/test/java/com/ning/billing/junction/plumbing/billing/TestBillCycleDayCalculator.java
@@ -29,6 +29,7 @@ import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.BillCycleDay;
 import com.ning.billing.catalog.api.BillingAlignment;
 import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
@@ -133,7 +134,7 @@ public class TestBillCycleDayCalculator {
         verifyBCDCalculation(accountTimeZone, startDate, bcdUTC, bcdLocal);
     }
 
-    private void verifyBCDCalculation(final DateTimeZone accountTimeZone, final DateTime startDateUTC, final int bcdUTC, final int bcdLocal) throws AccountApiException {
+    private void verifyBCDCalculation(final DateTimeZone accountTimeZone, final DateTime startDateUTC, final int bcdUTC, final int bcdLocal) throws AccountApiException, CatalogApiException {
         final BillCycleDayCalculator billCycleDayCalculator = new BillCycleDayCalculator(Mockito.mock(CatalogService.class), Mockito.mock(EntitlementUserApi.class));
 
         final Subscription subscription = Mockito.mock(Subscription.class);
@@ -145,7 +146,7 @@ public class TestBillCycleDayCalculator {
         final Account account = Mockito.mock(Account.class);
         Mockito.when(account.getTimeZone()).thenReturn(accountTimeZone);
 
-        final BillCycleDay bcd = billCycleDayCalculator.calculateBcdFromSubscription(subscription, plan, account);
+        final BillCycleDay bcd = billCycleDayCalculator.calculateBcdFromSubscription(subscription, plan, account, Mockito.mock(Catalog.class));
         Assert.assertEquals(bcd.getDayOfMonthUTC(), bcdUTC);
         Assert.assertEquals(bcd.getDayOfMonthLocal(), bcdLocal);
     }

overdue/pom.xml 2(+1 -1)

diff --git a/overdue/pom.xml b/overdue/pom.xml
index b9f4c74..78f5d36 100644
--- a/overdue/pom.xml
+++ b/overdue/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-overdue</artifactId>

payment/pom.xml 2(+1 -1)

diff --git a/payment/pom.xml b/payment/pom.xml
index 45e1f1c..733701b 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-payment</artifactId>

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index a43d535..8f2235e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
     <groupId>com.ning.billing</groupId>
     <artifactId>killbill</artifactId>
     <packaging>pom</packaging>
-    <version>0.1.28-SNAPSHOT</version>
+    <version>0.1.29-SNAPSHOT</version>
     <name>killbill</name>
     <description>Library for managing recurring subscriptions and the associated billing</description>
     <url>http://github.com/ning/killbill</url>

server/pom.xml 2(+1 -1)

diff --git a/server/pom.xml b/server/pom.xml
index 3859e33..88b7481 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-server</artifactId>
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
index 9ab06be..fab5627 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
@@ -74,7 +74,8 @@ public class TestAccount extends TestJaxrsBase {
         // Update Account
         final AccountJson newInput = new AccountJson(objFromJson.getAccountId(),
                                                      "zozo", 4, objFromJson.getExternalKey(), "rr@google.com", new BillCycleDayJson(18, 18),
-                                                     "USD", null, "UTC", "bl1", "bh2", "", "ca", "usa", "415-255-2991");
+                                                     "USD", null, "UTC", "bl1", "bh2", "", "", "ca", "San Francisco", "usa", "en", "415-255-2991",
+                                                     false, false);
         baseJson = mapper.writeValueAsString(newInput);
         final String uri = JaxrsResource.ACCOUNTS_PATH + "/" + objFromJson.getAccountId();
         response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
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 675159a..66cfb7c 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -186,4 +186,44 @@ public class TestInvoice extends TestJaxrsBase {
         final BigDecimal adjustedInvoiceBalance = invoice.getBalance().add(adjustedAmount.negate().setScale(2, RoundingMode.HALF_UP));
         assertEquals(adjustedInvoice.getBalance().compareTo(adjustedInvoiceBalance), 0);
     }
+
+    @Test(groups = "slow")
+    public void testExternalChargeOnNewInvoice() throws Exception {
+        final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        // Get the invoices
+        assertEquals(getInvoicesForAccount(accountJson.getAccountId()).size(), 2);
+
+        // Post an external charge
+        final BigDecimal chargeAmount = BigDecimal.TEN;
+        final InvoiceJsonWithItems invoiceWithItems = createExternalCharge(accountJson.getAccountId(), chargeAmount, null, null);
+        assertEquals(invoiceWithItems.getBalance().compareTo(chargeAmount), 0);
+        assertEquals(invoiceWithItems.getItems().size(), 1);
+
+        // Verify the total number of invoices
+        assertEquals(getInvoicesForAccount(accountJson.getAccountId()).size(), 3);
+    }
+
+    @Test(groups = "slow")
+    public void testExternalChargeOnExistingInvoice() throws Exception {
+        final AccountJson accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();
+
+        // Get the invoices
+        final List<InvoiceJsonWithItems> 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 InvoiceJsonWithItems invoiceWithItems = createExternalChargeForInvoice(accountJson.getAccountId(), invoiceId, chargeAmount, null, null);
+        assertEquals(invoiceWithItems.getItems().size(), originalNumberOfItemsForInvoice + 1);
+
+        // Verify the new invoice balance
+        final InvoiceJsonSimple adjustedInvoice = getInvoice(invoiceId);
+        final BigDecimal adjustedInvoiceBalance = originalInvoiceAmount.add(chargeAmount.setScale(2, RoundingMode.HALF_UP));
+        assertEquals(adjustedInvoice.getBalance().compareTo(adjustedInvoiceBalance), 0);
+    }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 6708ddf..b682877 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -346,6 +346,19 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
     // ACCOUNT UTILITIES
     //
 
+    protected AccountJson getAccountByExternalKey(final String externalKey) throws Exception {
+        final Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(JaxrsResource.QUERY_EXTERNAL_KEY, externalKey);
+        final Response response = doGet(JaxrsResource.ACCOUNTS_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+        final String baseJson = response.getResponseBody();
+        final AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
+        Assert.assertNotNull(objFromJson);
+
+        return objFromJson;
+    }
+
     protected AccountJson createAccountWithDefaultPaymentMethod(final String name, final String key, final String email) throws Exception {
 
         final AccountJson input = createAccount(name, key, email);
@@ -565,6 +578,46 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
     }
 
+    protected InvoiceJsonWithItems createExternalCharge(final String accountId, final BigDecimal amount, @Nullable final Currency currency,
+                                                        @Nullable final DateTime requestedDate) throws Exception {
+        return doCreateExternalCharge(accountId, null, amount, currency, requestedDate, JaxrsResource.CHARGES_PATH);
+    }
+
+    protected InvoiceJsonWithItems createExternalChargeForInvoice(final String accountId, final String invoiceId, final BigDecimal amount,
+                                                        @Nullable final Currency currency, @Nullable final DateTime requestedDate) throws Exception {
+        final String uri = JaxrsResource.INVOICES_PATH + "/" + invoiceId + "/" + JaxrsResource.CHARGES;
+        return doCreateExternalCharge(accountId, invoiceId, amount, currency, requestedDate, uri);
+    }
+
+    private InvoiceJsonWithItems doCreateExternalCharge(final String accountId, @Nullable final String invoiceId, @Nullable final BigDecimal amount,
+                                                        @Nullable final Currency currency, final DateTime requestedDate, 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());
+        }
+
+        final InvoiceItemJsonSimple externalCharge = new InvoiceItemJsonSimple(null, invoiceId, accountId, null, null, null, null,
+                                                                               null, null, null, amount, currency, null);
+        final String externalChargeJson = mapper.writeValueAsString(externalCharge);
+        final Response response = doPost(uri, externalChargeJson, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        final String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+
+        final Map<String, String> queryParamsForInvoice = new HashMap<String, String>();
+        queryParamsForInvoice.put(JaxrsResource.QUERY_ACCOUNT_ID, accountId);
+        queryParamsForInvoice.put(JaxrsResource.QUERY_INVOICE_WITH_ITEMS, "true");
+        final Response invoiceResponse = doGetWithUrl(location, queryParamsForInvoice, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(invoiceResponse.getStatusCode(), Status.OK.getStatusCode());
+
+        final String invoicesBaseJson = invoiceResponse.getResponseBody();
+        final InvoiceJsonWithItems invoice = mapper.readValue(invoicesBaseJson, new TypeReference<InvoiceJsonWithItems>(){});
+        assertNotNull(invoice);
+
+        return invoice;
+    }
+
     //
     // PAYMENT UTILITIES
     //
@@ -805,13 +858,17 @@ public class TestJaxrsBase extends ServerTestSuiteWithEmbeddedDB {
         final String timeZone = "UTC";
         final String address1 = "12 rue des ecoles";
         final String address2 = "Poitier";
+        final String postalCode = "44 567";
         final String company = "Renault";
+        final String city = "Quelque part";
         final String state = "Poitou";
         final String country = "France";
+        final String locale = "fr";
         final String phone = "81 53 26 56";
 
         // Note: the accountId payload is ignored on account creation
-        return new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, null, timeZone, address1, address2, company, state, country, phone);
+        return new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, null, timeZone,
+                               address1, address2, postalCode, company, city, state, country, locale, phone, false, false);
     }
 
     /**

usage/pom.xml 2(+1 -1)

diff --git a/usage/pom.xml b/usage/pom.xml
index 8d254a4..f674f66 100644
--- a/usage/pom.xml
+++ b/usage/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-usage</artifactId>

util/pom.xml 2(+1 -1)

diff --git a/util/pom.xml b/util/pom.xml
index d4ae0b6..378717e 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -13,7 +13,7 @@
     <parent>
         <groupId>com.ning.billing</groupId>
         <artifactId>killbill</artifactId>
-        <version>0.1.28-SNAPSHOT</version>
+        <version>0.1.29-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
     <artifactId>killbill-util</artifactId>