killbill-memoizeit

account: Add ability to reset account fields by introducing

1/24/2017 5:25:11 PM

Details

diff --git a/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java b/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java
index 0fcf194..8c452f1 100644
--- a/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java
+++ b/account/src/main/java/org/killbill/billing/account/api/DefaultAccount.java
@@ -312,31 +312,15 @@ public class DefaultAccount extends EntityBase implements Account {
     public Account mergeWithDelegate(final Account currentAccount) {
         final DefaultMutableAccountData accountData = new DefaultMutableAccountData(this);
 
-        if (externalKey != null && currentAccount.getExternalKey() != null && !currentAccount.getExternalKey().equals(externalKey)) {
-            throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account external key yet: new=%s, current=%s",
-                                                             externalKey, currentAccount.getExternalKey()));
-        } else {
-            // Default to current value
-            accountData.setExternalKey(currentAccount.getExternalKey());
-        }
+        validateAccountUpdateInput(currentAccount, false);
 
-        if (currency != null && currentAccount.getCurrency() != null && !currentAccount.getCurrency().equals(currency)) {
-            throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account currency yet: new=%s, current=%s",
-                                                             currency, currentAccount.getCurrency()));
-        } else {
-            // Default to current value
-            accountData.setCurrency(currentAccount.getCurrency());
-        }
+        accountData.setExternalKey(currentAccount.getExternalKey());
 
+        accountData.setCurrency(currentAccount.getCurrency());
 
-        if (currentAccount.getBillCycleDayLocal() != DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is already a BCD set
-            billCycleDayLocal != null && // and the proposed date is not null
-            billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL && // and the proposed date is not 0
-            !currentAccount.getBillCycleDayLocal().equals(billCycleDayLocal)) { // and it does not match we we have
-            throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account BCD yet: new=%s, current=%s", billCycleDayLocal, currentAccount.getBillCycleDayLocal()));
-        } else if (currentAccount.getBillCycleDayLocal() == DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is *not* already a BCD set
-                   billCycleDayLocal != null && // and the value proposed is not null
-                   billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL) {  // and the proposed date is not 0
+        if (currentAccount.getBillCycleDayLocal() == DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is *not* already a BCD set
+            billCycleDayLocal != null && // and the value proposed is not null
+            billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL) {  // and the proposed date is not 0
             accountData.setBillCycleDayLocal(billCycleDayLocal);
         } else {
             accountData.setBillCycleDayLocal(currentAccount.getBillCycleDayLocal());
@@ -525,4 +509,46 @@ public class DefaultAccount extends EntityBase implements Account {
         result = 31 * result + (isNotifiedForInvoices != null ? isNotifiedForInvoices.hashCode() : 0);
         return result;
     }
+
+    public void validateAccountUpdateInput(final Account currentAccount, boolean ignoreNullInput) {
+
+        //
+        // We don't allow update on the following fields:
+        //
+        // All these conditions are written in the exact same way:
+        //
+        // There is already a defined value BUT those don't match (either input is null or different) => Not Allowed
+        // * ignoreNullInput=true (case where we allow to reset values)
+        // * ignoreNullInput=true (case where we DON'T allow to reset values and so is such value is null we ignore the check)
+        //
+        //
+        if ((ignoreNullInput || externalKey != null) &&
+            currentAccount.getExternalKey() != null &&
+            !currentAccount.getExternalKey().equals(externalKey)) {
+            throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account external key yet: new=%s, current=%s",
+                                                             externalKey, currentAccount.getExternalKey()));
+        }
+
+        if ((ignoreNullInput || currency != null) &&
+            currentAccount.getCurrency() != null &&
+            !currentAccount.getCurrency().equals(currency)) {
+            throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account currency yet: new=%s, current=%s",
+                                                             currency, currentAccount.getCurrency()));
+        }
+
+        if ((ignoreNullInput || (billCycleDayLocal != null && billCycleDayLocal != DEFAULT_BILLING_CYCLE_DAY_LOCAL)) &&
+            currentAccount.getBillCycleDayLocal() != DEFAULT_BILLING_CYCLE_DAY_LOCAL && // There is already a BCD set
+            !currentAccount.getBillCycleDayLocal().equals(billCycleDayLocal)) { // and it does not match we we have
+            throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account BCD yet: new=%s, current=%s", billCycleDayLocal, currentAccount.getBillCycleDayLocal()));
+        }
+
+        if ((ignoreNullInput || timeZone != null) &&
+            currentAccount.getTimeZone() != null &&
+            !currentAccount.getTimeZone().equals(timeZone)) {
+            throw new IllegalArgumentException(String.format("Killbill doesn't support updating the account timeZone yet: new=%s, current=%s",
+                                                             timeZone, currentAccount.getTimeZone()));
+        }
+
+    }
+
 }
diff --git a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
index 8c75df8..a164bef 100644
--- a/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/org/killbill/billing/account/api/user/DefaultAccountUserApi.java
@@ -147,7 +147,20 @@ public class DefaultAccountUserApi extends DefaultAccountApiBase implements Acco
 
     @Override
     public void updateAccount(final Account account, final CallContext context) throws AccountApiException {
-        updateAccount(account.getId(), account, context);
+
+        // Convert to DefaultAccount to make sure we can safely call validateAccountUpdateInput
+        final DefaultAccount input = new DefaultAccount(account.getId(), account);
+
+        final Account currentAccount = getAccountById(input.getId(), context);
+        if (currentAccount == null) {
+            throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID, input.getId());
+        }
+
+        input.validateAccountUpdateInput(currentAccount, true);
+
+        final AccountModelDao updatedAccountModelDao = new AccountModelDao(currentAccount.getId(), input);
+
+        accountDao.update(updatedAccountModelDao, internalCallContextFactory.createInternalCallContext(updatedAccountModelDao.getId(), context));
     }
 
     @Override
diff --git a/account/src/test/java/org/killbill/billing/account/api/TestDefaultAccount.java b/account/src/test/java/org/killbill/billing/account/api/TestDefaultAccount.java
index f88b9e0..f5f3077 100644
--- a/account/src/test/java/org/killbill/billing/account/api/TestDefaultAccount.java
+++ b/account/src/test/java/org/killbill/billing/account/api/TestDefaultAccount.java
@@ -110,17 +110,20 @@ public class TestDefaultAccount extends AccountTestSuiteNoDB {
         // Null BCD -> 0 BCD
         Assert.assertEquals(accountWithNullBCD.getBillCycleDayLocal(), (Integer) 0);
 
-        final AccountData accountDataWithZeroBCD = getAccountData(0, currency, externalKey);
+        final DefaultMutableAccountData accountDataWithZeroBCD = new DefaultMutableAccountData(accountDataWithNullBCD);
+        accountDataWithZeroBCD.setBillCycleDayLocal(0);
         final Account accountWithZeroBCD = new DefaultAccount(accountId, accountDataWithZeroBCD);
         // Null BCD and 0 BCD -> 0 BCD
         Assert.assertEquals(accountWithNullBCD.mergeWithDelegate(accountWithZeroBCD).getBillCycleDayLocal(), (Integer) 0);
 
-        final AccountData accountDataWithRealBCD = getAccountData(12, currency, externalKey);
+        final DefaultMutableAccountData accountDataWithRealBCD = new DefaultMutableAccountData(accountDataWithNullBCD);
+        accountDataWithRealBCD.setBillCycleDayLocal(12);
         final Account accountWithRealBCD = new DefaultAccount(accountId, accountDataWithRealBCD);
         // Null BCD and real BCD -> real BCD
         Assert.assertEquals(accountWithNullBCD.mergeWithDelegate(accountWithRealBCD).getBillCycleDayLocal(), (Integer) 12);
 
-        final AccountData accountDataWithAnotherRealBCD = getAccountData(20, currency, externalKey);
+        final DefaultMutableAccountData accountDataWithAnotherRealBCD = new DefaultMutableAccountData(accountDataWithNullBCD);
+        accountDataWithAnotherRealBCD.setBillCycleDayLocal(20);
         final Account accountWithAnotherBCD = new DefaultAccount(accountId, accountDataWithAnotherRealBCD);
         // Same BCD
         Assert.assertEquals(accountWithAnotherBCD.mergeWithDelegate(accountWithAnotherBCD).getBillCycleDayLocal(), (Integer) 20);
diff --git a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java
index 6149547..3fa19fc 100644
--- a/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java
+++ b/account/src/test/java/org/killbill/billing/account/api/user/TestDefaultAccountUserApi.java
@@ -20,9 +20,12 @@ package org.killbill.billing.account.api.user;
 
 import java.util.LinkedList;
 import java.util.List;
+import java.util.TimeZone;
 import java.util.UUID;
 import java.util.concurrent.Callable;
+import java.util.spi.TimeZoneNameProvider;
 
+import org.joda.time.DateTimeZone;
 import org.killbill.billing.ErrorCode;
 import org.killbill.billing.account.AccountTestSuiteWithEmbeddedDB;
 import org.killbill.billing.account.api.Account;
@@ -45,6 +48,7 @@ import static com.jayway.awaitility.Awaitility.await;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.killbill.billing.account.AccountTestUtils.createAccountData;
 import static org.killbill.billing.account.AccountTestUtils.createTestAccount;
+import static org.killbill.billing.account.api.DefaultMutableAccountData.DEFAULT_BILLING_CYCLE_DAY_LOCAL;
 import static org.testng.Assert.assertEquals;
 
 public class TestDefaultAccountUserApi extends AccountTestSuiteWithEmbeddedDB {
@@ -177,6 +181,136 @@ public class TestDefaultAccountUserApi extends AccountTestSuiteWithEmbeddedDB {
         accountUserApi.updateAccount(new DefaultAccount(account.getId(), otherAccount), callContext);
     }
 
+
+    @Test(groups = "slow", description = "Test Account update to reset notes")
+    public void testAccountResetAccountNotes() throws Exception {
+        final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+        // Update the address and leave other fields null
+        final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+        mutableAccountData.setNotes(null);
+
+        DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+        accountUserApi.updateAccount(newAccount, callContext);
+
+        final Account retrievedAccount = accountUserApi.getAccountById(account.getId(), callContext);
+
+        Assert.assertEquals(retrievedAccount.getName(), account.getName());
+        Assert.assertEquals(retrievedAccount.getFirstNameLength(), account.getFirstNameLength());
+        Assert.assertEquals(retrievedAccount.getEmail(), account.getEmail());
+        Assert.assertEquals(retrievedAccount.getBillCycleDayLocal(), account.getBillCycleDayLocal());
+        Assert.assertEquals(retrievedAccount.getCurrency(), account.getCurrency());
+        Assert.assertEquals(retrievedAccount.getPaymentMethodId(), account.getPaymentMethodId());
+        Assert.assertEquals(retrievedAccount.getTimeZone(), account.getTimeZone());
+        Assert.assertEquals(retrievedAccount.getLocale(), account.getLocale());
+        Assert.assertEquals(retrievedAccount.getAddress1(), account.getAddress1());
+        Assert.assertEquals(retrievedAccount.getAddress2(), account.getAddress2());
+        Assert.assertEquals(retrievedAccount.getCompanyName(), account.getCompanyName());
+        Assert.assertEquals(retrievedAccount.getCity(), account.getCity());
+        Assert.assertEquals(retrievedAccount.getStateOrProvince(), account.getStateOrProvince());
+        Assert.assertEquals(retrievedAccount.getPostalCode(), account.getPostalCode());
+        Assert.assertEquals(retrievedAccount.getCountry(), account.getCountry());
+        Assert.assertEquals(retrievedAccount.getPhone(), account.getPhone());
+        Assert.assertEquals(retrievedAccount.isMigrated(), account.isMigrated());
+        Assert.assertEquals(retrievedAccount.isNotifiedForInvoices(), account.isNotifiedForInvoices());
+        Assert.assertEquals(retrievedAccount.getParentAccountId(), account.getParentAccountId());
+        Assert.assertEquals(retrievedAccount.isPaymentDelegatedToParent(), account.isPaymentDelegatedToParent());
+        // Finally check account notes did get reset
+        Assert.assertNull(retrievedAccount.getNotes());
+    }
+
+
+    @Test(groups = "slow", description = "Test failure on resetting externalKey", expectedExceptions = IllegalArgumentException.class)
+    public void testAccountResetExternalKey() throws Exception {
+        final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+        // Update the address and leave other fields null
+        final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+        mutableAccountData.setExternalKey(null);
+
+        DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+        accountUserApi.updateAccount(newAccount, callContext);
+    }
+
+
+    @Test(groups = "slow", description = "Test failure on changing externalKey", expectedExceptions = IllegalArgumentException.class)
+    public void testAccountChangeExternalKey() throws Exception {
+        final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+        // Update the address and leave other fields null
+        final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+        mutableAccountData.setExternalKey("somethingVeryDifferent");
+
+        DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+        accountUserApi.updateAccount(newAccount, callContext);
+    }
+
+
+    @Test(groups = "slow", description = "Test failure on resetting currency", expectedExceptions = IllegalArgumentException.class)
+    public void testAccountResetCurrency() throws Exception {
+        final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+        // Update the address and leave other fields null
+        final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+        mutableAccountData.setCurrency(null);
+
+        DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+        accountUserApi.updateAccount(newAccount, callContext);
+    }
+
+
+    @Test(groups = "slow", description = "Test failure on changing currency", expectedExceptions = IllegalArgumentException.class)
+    public void testAccountChangeCurrency() throws Exception {
+        final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+        // Update the address and leave other fields null
+        final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+        mutableAccountData.setCurrency(Currency.AFN);
+
+        DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+        accountUserApi.updateAccount(newAccount, callContext);
+    }
+
+    @Test(groups = "slow", description = "Test failure on resetting BCD", expectedExceptions = IllegalArgumentException.class)
+    public void testAccountResetBCD() throws Exception {
+        final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+        // Update the address and leave other fields null
+        final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+        mutableAccountData.setBillCycleDayLocal(DEFAULT_BILLING_CYCLE_DAY_LOCAL);
+
+        DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+        accountUserApi.updateAccount(newAccount, callContext);
+    }
+
+    @Test(groups = "slow", description = "Test failure on resetting timeZone", expectedExceptions = IllegalArgumentException.class)
+    public void testAccountResetTimeZone() throws Exception {
+        final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+        // Update the address and leave other fields null
+        final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+        mutableAccountData.setTimeZone(null);
+
+        DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+        accountUserApi.updateAccount(newAccount, callContext);
+    }
+
+
+    @Test(groups = "slow", description = "Test failure on changing timeZone", expectedExceptions = IllegalArgumentException.class)
+    public void testAccountChangingTimeZone() throws Exception {
+        final Account account = createAccount(new DefaultAccount(createTestAccount()));
+
+        // Update the address and leave other fields null
+        final MutableAccountData mutableAccountData = new DefaultMutableAccountData(account);
+        mutableAccountData.setTimeZone(DateTimeZone.UTC);
+
+        DefaultAccount newAccount = new DefaultAccount(account.getId(), mutableAccountData);
+        accountUserApi.updateAccount(newAccount, callContext);
+    }
+
+
+
+
     private static final class AccountEventHandler {
 
         private final List<AccountCreationInternalEvent> accountCreationInternalEvents = new LinkedList<AccountCreationInternalEvent>();
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java
index e3d3e80..f33f284 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/AccountJson.java
@@ -24,9 +24,11 @@ import java.util.UUID;
 
 import javax.annotation.Nullable;
 
+import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountData;
+import org.killbill.billing.account.api.MutableAccountData;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.util.audit.AccountAuditLogs;
 
@@ -150,8 +152,14 @@ public class AccountJson extends JsonBase {
         this.accountCBA = accountCBA;
     }
 
-    public AccountData toAccountData() {
-        return new AccountData() {
+    public Account toAccountData(@Nullable final UUID accountId) {
+        return new Account() {
+
+            @Override
+            public UUID getId() {
+                return accountId;
+            }
+
             @Override
             public DateTimeZone getTimeZone() {
                 if (Strings.emptyToNull(timeZone) == null) {
@@ -215,6 +223,7 @@ public class AccountJson extends JsonBase {
                 return firstNameLength;
             }
 
+
             @Override
             public String getExternalKey() {
                 return externalKey;
@@ -277,6 +286,39 @@ public class AccountJson extends JsonBase {
             public Boolean isPaymentDelegatedToParent() {
                 return isPaymentDelegatedToParent;
             }
+
+            //
+            // Non implemented
+            //
+            @Override
+            public DateTimeZone getFixedOffsetTimeZone() {
+                return null;
+            }
+
+            @Override
+            public DateTime getReferenceTime() {
+                return null;
+            }
+
+            @Override
+            public DateTime getCreatedDate() {
+                return null;
+            }
+
+            @Override
+            public DateTime getUpdatedDate() {
+                return null;
+            }
+
+            @Override
+            public MutableAccountData toMutableAccountData() {
+                return null;
+            }
+
+            @Override
+            public Account mergeWithDelegate(final Account delegate) {
+                return null;
+            }
         };
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
index 586d769..8d36405 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/AccountResource.java
@@ -354,7 +354,7 @@ public class AccountResource extends JaxRsResourceBase {
                                   @javax.ws.rs.core.Context final UriInfo uriInfo) throws AccountApiException {
         verifyNonNullOrEmpty(json, "AccountJson body should be specified");
 
-        final AccountData data = json.toAccountData();
+        final AccountData data = json.toAccountData(null);
         final Account account = accountUserApi.createAccount(data, context.createContext(createdBy, reason, comment, request));
         return uriBuilder.buildResponse(uriInfo, AccountResource.class, "getAccount", account.getId(), request);
     }
@@ -368,15 +368,20 @@ public class AccountResource extends JaxRsResourceBase {
     @ApiResponses(value = {@ApiResponse(code = 400, message = "Invalid account data supplied")})
     public Response updateAccount(final AccountJson json,
                                   @PathParam("accountId") final String accountId,
+                                  @QueryParam(QUERY_ACCOUNT_TREAT_NULL_AS_RESET) @DefaultValue("false") final Boolean treatNullValueAsReset,
                                   @HeaderParam(HDR_CREATED_BY) final String createdBy,
                                   @HeaderParam(HDR_REASON) final String reason,
                                   @HeaderParam(HDR_COMMENT) final String comment,
                                   @javax.ws.rs.core.Context final HttpServletRequest request) throws AccountApiException {
         verifyNonNullOrEmpty(json, "AccountJson body should be specified");
 
-        final AccountData data = json.toAccountData();
         final UUID uuid = UUID.fromString(accountId);
-        accountUserApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment, request));
+        final Account data = json.toAccountData(uuid);
+        if (treatNullValueAsReset) {
+            accountUserApi.updateAccount(data, context.createContext(createdBy, reason, comment, request));
+        } else {
+            accountUserApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment, request));
+        }
         return getAccount(accountId, false, false, new AuditMode(AuditLevel.NONE.toString()), request);
     }
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
index 947e124..0182647 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/ComboPaymentResource.java
@@ -76,7 +76,7 @@ public abstract class ComboPaymentResource extends JaxRsResourceBase {
             }
         }
         // Finally create if does not exist
-        return accountUserApi.createAccount(accountJson.toAccountData(), callContext);
+        return accountUserApi.createAccount(accountJson.toAccountData(null), callContext);
     }
 
     protected UUID getOrCreatePaymentMethod(final Account account, final PaymentMethodJson paymentMethodJson, final Iterable<PluginProperty> pluginProperties, final CallContext callContext) throws PaymentApiException {
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index bcfd4dc..dd706db 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -89,6 +89,7 @@ public interface JaxrsResource {
 
     public static final String QUERY_ACCOUNT_WITH_BALANCE = "accountWithBalance";
     public static final String QUERY_ACCOUNT_WITH_BALANCE_AND_CBA = "accountWithBalanceAndCBA";
+    public static final String QUERY_ACCOUNT_TREAT_NULL_AS_RESET = "treatNullAsReset";
 
     public static final String QUERY_ACCOUNT_ID = "accountId";
 

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 833806a..8b05ea8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.140.9</version>
+        <version>0.140.10</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.18.3-SNAPSHOT</version>
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java
index b306e85..83f655b 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestAccount.java
@@ -126,6 +126,80 @@ public class TestAccount extends TestJaxrsBase {
         searchAccount(input, null);
     }
 
+    @Test(groups = "slow", description = "Can reset account notes using flag treatNullAsReset")
+    public void testResetAccountNotes() throws Exception {
+
+        final Account input = createAccount();
+        Assert.assertNotNull(input.getExternalKey());
+        Assert.assertNotNull(input.getNotes());
+        Assert.assertEquals(input.getNotes(), "notes");
+        Assert.assertEquals(input.getTimeZone(), "UTC");
+        Assert.assertEquals(input.getAddress1(), "12 rue des ecoles");
+        Assert.assertEquals(input.getAddress2(), "Poitier");
+        Assert.assertEquals(input.getCity(), "Quelque part");
+        Assert.assertEquals(input.getState(), "Poitou");
+        Assert.assertEquals(input.getCountry(), "France");
+        Assert.assertEquals(input.getLocale(), "fr");
+
+
+        // Set notes to something else
+        final Account newInput = new Account(input.getAccountId(),
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             null,
+                                             "notes2",
+                                             null,
+                                             null,
+                                             null,
+                                             null);
+
+
+        // Update notes, all other fields remaining the same (value set to null but treatNullAsReset defaults to false)
+        Account updatedAccount = killBillClient.updateAccount(newInput, requestOptions);
+
+        Assert.assertNotNull(updatedAccount.getExternalKey());
+        Assert.assertNotNull(updatedAccount.getNotes());
+        Assert.assertEquals(updatedAccount.getNotes(), "notes2");
+        Assert.assertEquals(updatedAccount.getTimeZone(), "UTC");
+        Assert.assertEquals(updatedAccount.getAddress1(), "12 rue des ecoles");
+        Assert.assertEquals(updatedAccount.getAddress2(), "Poitier");
+        Assert.assertEquals(updatedAccount.getCity(), "Quelque part");
+        Assert.assertEquals(updatedAccount.getState(), "Poitou");
+        Assert.assertEquals(updatedAccount.getCountry(), "France");
+        Assert.assertEquals(updatedAccount.getLocale(), "fr");
+
+        // Reset notes, all other fields remaining the same
+        updatedAccount.setNotes(null);
+        updatedAccount = killBillClient.updateAccount(updatedAccount, true, requestOptions);
+
+        Assert.assertNotNull(updatedAccount.getExternalKey());
+        Assert.assertNull(updatedAccount.getNotes());
+        Assert.assertEquals(updatedAccount.getTimeZone(), "UTC");
+        Assert.assertEquals(updatedAccount.getAddress1(), "12 rue des ecoles");
+        Assert.assertEquals(updatedAccount.getAddress2(), "Poitier");
+        Assert.assertEquals(updatedAccount.getCity(), "Quelque part");
+        Assert.assertEquals(updatedAccount.getState(), "Poitou");
+        Assert.assertEquals(updatedAccount.getCountry(), "France");
+        Assert.assertEquals(updatedAccount.getLocale(), "fr");
+    }
+
+
     @Test(groups = "slow", description = "Can retrieve the account balance")
     public void testAccountWithBalance() throws Exception {
         final Account accountJson = createAccountNoPMBundleAndSubscriptionAndWaitForFirstInvoice();