killbill-aplcache

Merge remote-tracking branch 'origin/master' into work-for-release-0.17.x Signed-off-by:

7/1/2016 4:39:21 PM

Details

bin/gen_updater.rb 70(+70 -0)

diff --git a/bin/gen_updater.rb b/bin/gen_updater.rb
new file mode 100644
index 0000000..14f75c9
--- /dev/null
+++ b/bin/gen_updater.rb
@@ -0,0 +1,70 @@
+require 'json'
+require 'open-uri'
+
+def get_as_json(url)
+  raw = URI.parse(url).read
+  JSON.parse(raw)
+end
+
+current_stable_train = nil
+current_dev_train = nil
+
+current_stable_version = nil
+current_dev_version = nil
+
+metadata = get_as_json("https://api.github.com/repos/killbill/killbill/tags")
+releases = []
+metadata.each do |entry|
+  parsed = entry['name'].scan(/killbill-([0-9]+\.([0-9]+)\.[0-9]+)/).last
+  version = parsed.first
+
+  train = parsed.last.to_i
+  if train % 2 == 1
+    current_dev_train = train if current_dev_train.to_i < train
+    current_dev_version = version if current_dev_version.nil? || (current_dev_version < version)
+  else
+    current_stable_train = train if current_stable_train.to_i < train
+    current_stable_version = version if current_stable_version.nil? || (current_stable_version < version)
+  end
+
+  releases << {
+    :train => train,
+    :version => version
+  }
+end
+
+doc =<<EOF
+## Top level keys
+# general.notice = This notice should rarely, if ever, be used as everyone will see it
+
+EOF
+
+current_train = nil
+latest_from_train = nil
+releases.each do |release|
+  if release[:train] != current_train || current_train == nil
+    current_train = release[:train]
+    latest_from_train = release[:version]
+    doc << "### 0.#{current_train}.x series ###\n\n"
+  end
+
+  doc << "\# #{release[:version]}\n"
+
+  if release[:version] == latest_from_train
+    doc << "#{release[:version]}.updates           =\n"
+  else
+    doc << "#{release[:version]}.updates           = #{latest_from_train}\n"
+  end
+
+  if release[:version] == current_dev_version || release[:version] == current_stable_version
+    doc << "#{release[:version]}.notices           = This is the latest #{release[:train] % 2 == 1 ? 'dev' : 'GA'} release.\n"
+  elsif release[:train] != current_dev_train
+    doc << "#{release[:version]}.notices           = We recommend upgrading to #{current_stable_version}, our latest GA release.\n"
+  else
+    doc << "#{release[:version]}.notices           = We recommend upgrading to #{current_dev_version}, our latest dev release.\n"
+  end
+
+  doc << "#{release[:version]}.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-#{release[:version]}\n\n"
+end
+
+puts doc.chomp!
\ No newline at end of file

NEWS 5(+4 -1)

diff --git a/NEWS b/NEWS
index bb13ca8..842a436 100644
--- a/NEWS
+++ b/NEWS
@@ -20,7 +20,7 @@
     See https://github.com/killbill/killbill/releases/tag/killbill-0.16.1
 
 0.16.0
-    TBD (point to 0.16 release page)
+    See https://github.com/killbill/killbill/releases/tag/killbill-0.16.0
 
 0.15.10
     See https://github.com/killbill/killbill/releases/tag/killbill-0.15.10
@@ -50,6 +50,9 @@
 0.15.0
     See https://github.com/killbill/killbill/issues?q=milestone%3ARelease-0.15.0+is%3Aclosed
 
+0.14.1
+    Fix usage bug (see 8511f41cdf78bd1cb9d0c335ffa8d40ca53aeccd)
+
 0.14.0
     http://killbill.io/blog/kill-bill-0-14-0-released/
 
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
index 7885616..4df2541 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PaymentProcessor.java
@@ -367,10 +367,17 @@ public class PaymentProcessor extends ProcessorBase {
         String currentStateName = null;
         if (paymentStateContext.getPaymentId() != null) {
             PaymentModelDao paymentModelDao = daoHelper.getPayment();
+
+            // Sanity: verify the payment belongs to the right account (in case it was looked-up by payment or transaction external key)
+            if (!paymentModelDao.getAccountRecordId().equals(internalCallContext.getAccountRecordId())) {
+                // TODO 0.17.x New ErrorCode (it's not necessarily the transaction external key that matches)
+                throw new PaymentApiException(ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS, paymentStateContext.getPaymentTransactionExternalKey());
+            }
+
             if (paymentStateContext.getTransactionId() != null || paymentStateContext.getPaymentTransactionExternalKey() != null) {
                 // If a transaction id or key is passed, we are maybe completing an existing transaction (unless a new key was provided)
                 final List<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment = daoHelper.getPaymentDao().getTransactionsForPayment(paymentStateContext.getPaymentId(), paymentStateContext.getInternalCallContext());
-                PaymentTransactionModelDao transactionToComplete = findTransactionToCompleteAndRunSanityChecks(paymentModelDao, paymentTransactionsForCurrentPayment, paymentStateContext);
+                PaymentTransactionModelDao transactionToComplete = findTransactionToCompleteAndRunSanityChecks(paymentModelDao, paymentTransactionsForCurrentPayment, paymentStateContext, internalCallContext);
 
                 if (transactionToComplete != null) {
                     // For completion calls, always invoke the Janitor first to get the latest state. The state machine will then
@@ -415,7 +422,8 @@ public class PaymentProcessor extends ProcessorBase {
 
     private PaymentTransactionModelDao findTransactionToCompleteAndRunSanityChecks(final PaymentModelDao paymentModelDao,
                                                                                    final Iterable<PaymentTransactionModelDao> paymentTransactionsForCurrentPayment,
-                                                                                   final PaymentStateContext paymentStateContext) throws PaymentApiException {
+                                                                                   final PaymentStateContext paymentStateContext,
+                                                                                   final InternalCallContext internalCallContext) throws PaymentApiException {
         final Collection<PaymentTransactionModelDao> completionCandidates = new LinkedList<PaymentTransactionModelDao>();
         for (final PaymentTransactionModelDao paymentTransactionModelDao : paymentTransactionsForCurrentPayment) {
             // Check if we already have a transaction for that id or key
@@ -434,7 +442,20 @@ public class PaymentProcessor extends ProcessorBase {
 
             // Sanity: if we already have a transaction for that id or key, the transaction type must match
             if (paymentTransactionModelDao.getTransactionType() != paymentStateContext.getTransactionType()) {
-                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_OPERATION, paymentStateContext.getTransactionType(), paymentModelDao.getStateName());
+                throw new PaymentApiException(ErrorCode.PAYMENT_INVALID_PARAMETER, "transactionType", String.format("%s doesn't match existing transaction type %s", paymentStateContext.getTransactionType(), paymentTransactionModelDao.getTransactionType()));
+            }
+
+            // Sanity: verify we don't already have a successful transaction for that key (chargeback reversals are a bit special, it's the only transaction type we can revert)
+            if (paymentTransactionModelDao.getTransactionExternalKey().equals(paymentStateContext.getPaymentTransactionExternalKey()) &&
+                paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.SUCCESS &&
+                paymentTransactionModelDao.getTransactionType() != TransactionType.CHARGEBACK) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS, paymentStateContext.getPaymentTransactionExternalKey());
+            }
+
+            // Sanity: don't share keys across accounts
+            if (paymentTransactionModelDao.getTransactionExternalKey().equals(paymentStateContext.getPaymentTransactionExternalKey()) &&
+                !paymentTransactionModelDao.getAccountRecordId().equals(internalCallContext.getAccountRecordId())) {
+                throw new PaymentApiException(ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS, paymentStateContext.getPaymentTransactionExternalKey());
             }
 
             // UNKNOWN transactions are potential candidates, we'll invoke the Janitor first though
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
index d7a03e2..012d7c0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/PaymentAutomatonRunner.java
@@ -18,6 +18,7 @@
 package org.killbill.billing.payment.core.sm;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
@@ -42,6 +43,7 @@ import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.osgi.api.OSGIServiceRegistration;
 import org.killbill.billing.payment.api.PaymentApiException;
 import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.api.TransactionStatus;
 import org.killbill.billing.payment.api.TransactionType;
 import org.killbill.billing.payment.core.PaymentExecutors;
 import org.killbill.billing.payment.core.sm.payments.AuthorizeCompleted;
@@ -67,6 +69,7 @@ import org.killbill.billing.payment.core.sm.payments.VoidInitiated;
 import org.killbill.billing.payment.core.sm.payments.VoidOperation;
 import org.killbill.billing.payment.dao.PaymentDao;
 import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
 import org.killbill.billing.payment.dispatcher.PluginDispatcher;
 import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
 import org.killbill.billing.util.callcontext.CallContext;
@@ -126,7 +129,7 @@ public class PaymentAutomatonRunner {
                                                         final CallContext callContext,
                                                         final InternalCallContext internalCallContext) throws PaymentApiException {
         // Retrieve the payment id from the payment external key if needed
-        final UUID effectivePaymentId = paymentId != null ? paymentId : retrievePaymentId(paymentExternalKey, internalCallContext);
+        final UUID effectivePaymentId = paymentId != null ? paymentId : retrievePaymentId(paymentExternalKey, paymentTransactionExternalKey, internalCallContext);
 
         return new PaymentStateContext(isApiPayment,
                                        effectivePaymentId,
@@ -245,12 +248,40 @@ public class PaymentAutomatonRunner {
         }
     }
 
-    private UUID retrievePaymentId(@Nullable final String paymentExternalKey, final InternalCallContext internalCallContext) {
-        if (paymentExternalKey == null) {
+    // TODO Could we cache these to avoid extra queries in PaymentAutomatonDAOHelper?
+    private UUID retrievePaymentId(@Nullable final String paymentExternalKey, @Nullable final String paymentTransactionExternalKey, final InternalCallContext internalCallContext) throws PaymentApiException {
+        if (paymentExternalKey != null) {
+            final PaymentModelDao payment = paymentDao.getPaymentByExternalKey(paymentExternalKey, internalCallContext);
+            if (payment != null) {
+                return payment.getId();
+            }
+        }
+
+        if (paymentTransactionExternalKey == null) {
             return null;
         }
 
-        final PaymentModelDao payment = paymentDao.getPaymentByExternalKey(paymentExternalKey, internalCallContext);
-        return payment == null ? null : payment.getId();
+        final List<PaymentTransactionModelDao> paymentTransactionModelDaos = paymentDao.getPaymentTransactionsByExternalKey(paymentTransactionExternalKey, internalCallContext);
+        for (final PaymentTransactionModelDao paymentTransactionModelDao : paymentTransactionModelDaos) {
+            if (paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.SUCCESS ||
+                paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PENDING ||
+                paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.UNKNOWN) {
+                return paymentTransactionModelDao.getPaymentId();
+            }
+        }
+
+        UUID paymentIdCandidate = null;
+        for (final PaymentTransactionModelDao paymentTransactionModelDao : paymentTransactionModelDaos) {
+            if (paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PAYMENT_FAILURE ||
+                paymentTransactionModelDao.getTransactionStatus() == TransactionStatus.PLUGIN_FAILURE) {
+                if (paymentIdCandidate == null) {
+                    paymentIdCandidate = paymentTransactionModelDao.getPaymentId();
+                } else if (!paymentIdCandidate.equals(paymentTransactionModelDao.getPaymentId())) {
+                    throw new PaymentApiException(ErrorCode.PAYMENT_INTERNAL_ERROR, "Multiple failed payments sharing the same transaction external key - this should never happen");
+                }
+            }
+        }
+
+        return paymentIdCandidate;
     }
 }
diff --git a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
index 8db97ce..d455610 100644
--- a/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
+++ b/payment/src/test/java/org/killbill/billing/payment/api/TestPaymentApi.java
@@ -1715,7 +1715,6 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
 
     @Test(groups = "slow")
     public void testSanityAcrossTransactionTypes() throws PaymentApiException {
-
         final BigDecimal requestedAmount = BigDecimal.TEN;
         final String paymentExternalKey = "ahhhhhhhh";
         final String transactionExternalKey = "okkkkkkk";
@@ -1730,21 +1729,18 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         Assert.assertEquals(pendingPayment.getTransactions().get(0).getExternalKey(), transactionExternalKey);
         Assert.assertEquals(pendingPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
 
-
         try {
             createPayment(TransactionType.PURCHASE, null, paymentExternalKey, transactionExternalKey, requestedAmount, PaymentPluginStatus.PENDING);
             Assert.fail("PURCHASE transaction with same key should have failed");
         } catch (final PaymentApiException expected) {
-            Assert.assertEquals(expected.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+            Assert.assertEquals(expected.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
         }
     }
 
     @Test(groups = "slow")
     public void testSuccessfulInitialTransactionToSameTransaction() throws Exception {
-
         final BigDecimal requestedAmount = BigDecimal.TEN;
         for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
-
             final String paymentExternalKey = UUID.randomUUID().toString();
             final String keyA = UUID.randomUUID().toString();
 
@@ -1760,6 +1756,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
                 createPayment(transactionType, processedPayment.getId(), paymentExternalKey, keyB, requestedAmount, PaymentPluginStatus.PROCESSED);
                 Assert.fail("Retrying initial successful transaction (AUTHORIZE, PURCHASE, CREDIT) with same different key should fail");
             } catch (final PaymentApiException e) {
+                Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
             }
 
             // Attempt to create another {AUTH, PURCHASE, CREDIT} with same key => key constraint should make the request fail
@@ -1767,17 +1764,15 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
                 createPayment(transactionType, processedPayment.getId(), paymentExternalKey, keyA, requestedAmount, PaymentPluginStatus.PROCESSED);
                 Assert.fail("Retrying initial successful transaction (AUTHORIZE, PURCHASE, CREDIT) with same transaction key should fail");
             } catch (final PaymentApiException e) {
+                Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
             }
         }
     }
 
-
     @Test(groups = "slow")
     public void testPendingInitialTransactionToSameTransaction() throws Exception {
-
         final BigDecimal requestedAmount = BigDecimal.TEN;
         for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
-
             final String paymentExternalKey = UUID.randomUUID().toString();
             final String keyA = UUID.randomUUID().toString();
 
@@ -1793,6 +1788,7 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
                 createPayment(transactionType, pendingPayment.getId(), paymentExternalKey, keyB, requestedAmount, PaymentPluginStatus.PROCESSED);
                 Assert.fail("Retrying initial successful transaction (AUTHORIZE, PURCHASE, CREDIT) with same different key should fail");
             } catch (final PaymentApiException e) {
+                Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
             }
 
             // Attempt to create another {AUTH, PURCHASE, CREDIT} with same key => That should work because we are completing the payment
@@ -1803,13 +1799,10 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         }
     }
 
-
     @Test(groups = "slow")
     public void testFailedInitialTransactionToSameTransactionWithSameKey() throws Exception {
-
         final BigDecimal requestedAmount = BigDecimal.TEN;
         for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
-
             final String paymentExternalKey = UUID.randomUUID().toString();
             final String keyA = UUID.randomUUID().toString();
 
@@ -1826,13 +1819,10 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         }
     }
 
-
     @Test(groups = "slow")
     public void testFailedInitialTransactionToSameTransactionWithDifferentKey() throws Exception {
-
         final BigDecimal requestedAmount = BigDecimal.TEN;
         for (final TransactionType transactionType : ImmutableList.<TransactionType>of(TransactionType.AUTHORIZE, TransactionType.PURCHASE, TransactionType.CREDIT)) {
-
             final String paymentExternalKey = UUID.randomUUID().toString();
             final String keyA = UUID.randomUUID().toString();
 
@@ -1851,7 +1841,292 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
         }
     }
 
+    @Test(groups = "slow")
+    public void testKeysSanityOnPending() throws Exception {
+        final String authKey = UUID.randomUUID().toString();
+        final Payment pendingAuthorization = createPayment(TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.PENDING);
+        assertNotNull(pendingAuthorization);
+        Assert.assertEquals(pendingAuthorization.getTransactions().size(), 1);
+        Assert.assertEquals(pendingAuthorization.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
+
+        try {
+            // Capture with the same transaction external key should fail
+            createPayment(TransactionType.CAPTURE, pendingAuthorization.getId(), null, authKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
+        }
+
+        final Account account1 = testHelper.createTestAccount("bobo2@gmail.com", true);
+        try {
+            // Different auth with the same payment external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, pendingAuthorization.getExternalKey(), null, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        try {
+            // Different auth with the same payment external key but different transaction external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, pendingAuthorization.getExternalKey(), UUID.randomUUID().toString(), BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        try {
+            // Different auth with the same transaction external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        try {
+            // Auth with the same payment external key but different transaction external key should not go through
+            createPayment(TransactionType.AUTHORIZE, null, pendingAuthorization.getExternalKey(), UUID.randomUUID().toString(), BigDecimal.TEN, PaymentPluginStatus.PENDING);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+        }
+
+        // Auth with the same payment and transaction external keys should go through (completion)
+        final Payment pendingAuthorization2 = createPayment(TransactionType.AUTHORIZE, null, pendingAuthorization.getExternalKey(), authKey, BigDecimal.TEN, PaymentPluginStatus.PENDING);
+        assertNotNull(pendingAuthorization2);
+        Assert.assertEquals(pendingAuthorization2.getTransactions().size(), 1);
+        Assert.assertEquals(pendingAuthorization2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PENDING);
+
+        // Auth with the same transaction external key should go through (completion)
+        final Payment authorization = createPayment(TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+        assertNotNull(authorization);
+        Assert.assertEquals(authorization.getTransactions().size(), 1);
+        Assert.assertEquals(authorization.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+
+        try {
+            // Different auth with the same payment external key on a different account should still fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, pendingAuthorization.getExternalKey(), null, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        try {
+            // Different auth with the same payment external key but different transaction external key on a different account should still fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, pendingAuthorization.getExternalKey(), UUID.randomUUID().toString(), BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        try {
+            // Different auth with the same transaction external key on a different account should still fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        // Capture with a different transaction external key should go through
+        final String captureKey = UUID.randomUUID().toString();
+        final Payment pendingCapture = createPayment(TransactionType.CAPTURE, authorization.getId(), null, captureKey, BigDecimal.ONE, PaymentPluginStatus.PENDING);
+        Assert.assertEquals(pendingCapture.getTransactions().size(), 2);
+        Assert.assertEquals(pendingCapture.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        Assert.assertEquals(pendingCapture.getTransactions().get(1).getTransactionStatus(), TransactionStatus.PENDING);
+
+        try {
+            // Different auth with the same transaction external key should fail
+            createPayment(TransactionType.AUTHORIZE, null, null, captureKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
+        }
+
+        try {
+            // Different auth with the same transaction external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, null, captureKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        // Second capture with the same transaction external key should go through (completion)
+        final Payment capturedPayment = createPayment(TransactionType.CAPTURE, authorization.getId(), null, captureKey, BigDecimal.ONE, PaymentPluginStatus.PROCESSED);
+        Assert.assertEquals(capturedPayment.getTransactions().size(), 2);
+        Assert.assertEquals(capturedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        Assert.assertEquals(capturedPayment.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+
+        // Second capture with a different transaction external key should go through
+        final String captureKey2 = UUID.randomUUID().toString();
+        final Payment capturedPayment2 = createPayment(TransactionType.CAPTURE, authorization.getId(), null, captureKey2, BigDecimal.ONE, PaymentPluginStatus.PROCESSED);
+        Assert.assertEquals(capturedPayment2.getTransactions().size(), 3);
+        Assert.assertEquals(capturedPayment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        Assert.assertEquals(capturedPayment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+        Assert.assertEquals(capturedPayment2.getTransactions().get(2).getTransactionStatus(), TransactionStatus.SUCCESS);
+    }
+
+    @Test(groups = "slow")
+    public void testKeysSanityOnSuccess() throws Exception {
+        final String authKey = UUID.randomUUID().toString();
+        final Payment authorization = createPayment(TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+        assertNotNull(authorization);
+        Assert.assertEquals(authorization.getTransactions().size(), 1);
+        Assert.assertEquals(authorization.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+
+        try {
+            // Capture with the same transaction external key should fail
+            createPayment(TransactionType.CAPTURE, authorization.getId(), null, authKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
+        }
+
+        try {
+            // Different auth with the same payment external key should fail
+            createPayment(TransactionType.AUTHORIZE, null, authorization.getExternalKey(), null, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+        }
+
+        try {
+            // Different auth with the same payment external key but different transaction external key should fail
+            createPayment(TransactionType.AUTHORIZE, null, authorization.getExternalKey(), UUID.randomUUID().toString(), BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_OPERATION.getCode());
+        }
+
+        try {
+            // Different auth with the same transaction external key should fail
+            createPayment(TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        final Account account1 = testHelper.createTestAccount("bobo2@gmail.com", true);
+        try {
+            // Different auth with the same payment external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, authorization.getExternalKey(), UUID.randomUUID().toString(), BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        try {
+            // Different auth with the same payment external key but different transaction external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, authorization.getExternalKey(), UUID.randomUUID().toString(), BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        try {
+            // Different auth with the same transaction external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        // Capture with a different transaction external key should go through
+        final String captureKey = UUID.randomUUID().toString();
+        final Payment capturedPayment = createPayment(TransactionType.CAPTURE, authorization.getId(), null, captureKey, BigDecimal.ONE, PaymentPluginStatus.PROCESSED);
+        Assert.assertEquals(capturedPayment.getTransactions().size(), 2);
+        Assert.assertEquals(capturedPayment.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        Assert.assertEquals(capturedPayment.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+
+        try {
+            // Second capture with the same transaction external key should fail
+            createPayment(TransactionType.CAPTURE, authorization.getId(), null, captureKey, BigDecimal.ONE, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        try {
+            // Different auth with the same transaction external key should fail
+            createPayment(TransactionType.AUTHORIZE, null, null, captureKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_INVALID_PARAMETER.getCode());
+        }
+
+        try {
+            // Different auth with the same transaction external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, null, captureKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        // Second capture with a different transaction external key should go through
+        final String captureKey2 = UUID.randomUUID().toString();
+        final Payment capturedPayment2 = createPayment(TransactionType.CAPTURE, authorization.getId(), null, captureKey2, BigDecimal.ONE, PaymentPluginStatus.PROCESSED);
+        Assert.assertEquals(capturedPayment2.getTransactions().size(), 3);
+        Assert.assertEquals(capturedPayment2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.SUCCESS);
+        Assert.assertEquals(capturedPayment2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.SUCCESS);
+        Assert.assertEquals(capturedPayment2.getTransactions().get(2).getTransactionStatus(), TransactionStatus.SUCCESS);
+    }
+
+    @Test(groups = "slow")
+    public void testKeysSanityOnFailure() throws Exception {
+        final String authKey = UUID.randomUUID().toString();
+        final Payment failedAuthorization1 = createPayment(TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.ERROR);
+        assertNotNull(failedAuthorization1);
+        Assert.assertEquals(failedAuthorization1.getTransactions().size(), 1);
+        Assert.assertEquals(failedAuthorization1.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+
+        final Account account1 = testHelper.createTestAccount("bobo2@gmail.com", true);
+        try {
+            // Different auth with the same payment external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, failedAuthorization1.getExternalKey(), null, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
 
+        try {
+            // Different auth with the same transaction external key on a different account should fail
+            createPayment(account1, TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.PROCESSED);
+            Assert.fail();
+        } catch (final PaymentApiException e) {
+            Assert.assertEquals(e.getCode(), ErrorCode.PAYMENT_ACTIVE_TRANSACTION_KEY_EXISTS.getCode());
+        }
+
+        // Different auth with the same payment external key should go through
+        final Payment failedAuthorization2 = createPayment(TransactionType.AUTHORIZE, null, failedAuthorization1.getExternalKey(), null, BigDecimal.TEN, PaymentPluginStatus.ERROR);
+        assertNotNull(failedAuthorization2);
+        Assert.assertEquals(failedAuthorization2.getTransactions().size(), 2);
+        Assert.assertEquals(failedAuthorization2.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertEquals(failedAuthorization2.getTransactions().get(0).getExternalKey(), authKey);
+        Assert.assertEquals(failedAuthorization2.getTransactions().get(1).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertNotEquals(failedAuthorization2.getTransactions().get(1).getExternalKey(), authKey);
+
+        // Different auth with the same transaction external key should go through
+        final Payment failedAuthorization3 = createPayment(TransactionType.AUTHORIZE, null, null, authKey, BigDecimal.TEN, PaymentPluginStatus.ERROR);
+        assertNotNull(failedAuthorization3);
+        Assert.assertEquals(failedAuthorization3.getTransactions().size(), 3);
+        Assert.assertEquals(failedAuthorization3.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertEquals(failedAuthorization3.getTransactions().get(0).getExternalKey(), authKey);
+        Assert.assertEquals(failedAuthorization3.getTransactions().get(1).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertNotEquals(failedAuthorization3.getTransactions().get(1).getExternalKey(), authKey);
+        Assert.assertEquals(failedAuthorization3.getTransactions().get(2).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertEquals(failedAuthorization3.getTransactions().get(2).getExternalKey(), authKey);
+
+        // Different auth with the same payment external key but different transaction external key should go through
+        final Payment failedAuthorization4 = createPayment(TransactionType.AUTHORIZE, null, failedAuthorization1.getExternalKey(), UUID.randomUUID().toString(), BigDecimal.TEN, PaymentPluginStatus.ERROR);
+        assertNotNull(failedAuthorization4);
+        Assert.assertEquals(failedAuthorization4.getTransactions().size(), 4);
+        Assert.assertEquals(failedAuthorization4.getTransactions().get(0).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertEquals(failedAuthorization4.getTransactions().get(0).getExternalKey(), authKey);
+        Assert.assertEquals(failedAuthorization4.getTransactions().get(1).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertNotEquals(failedAuthorization4.getTransactions().get(1).getExternalKey(), authKey);
+        Assert.assertEquals(failedAuthorization4.getTransactions().get(2).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertEquals(failedAuthorization4.getTransactions().get(2).getExternalKey(), authKey);
+        Assert.assertEquals(failedAuthorization4.getTransactions().get(3).getTransactionStatus(), TransactionStatus.PAYMENT_FAILURE);
+        Assert.assertNotEquals(failedAuthorization4.getTransactions().get(3).getExternalKey(), authKey);
+    }
 
     private void verifyRefund(final Payment refund, final String paymentExternalKey, final String paymentTransactionExternalKey, final String refundTransactionExternalKey, final BigDecimal requestedAmount, final BigDecimal refundAmount, final TransactionStatus transactionStatus) {
         Assert.assertEquals(refund.getExternalKey(), paymentExternalKey);
@@ -1945,6 +2220,16 @@ public class TestPaymentApi extends PaymentTestSuiteWithEmbeddedDB {
                                   @Nullable final String paymentTransactionExternalKey,
                                   @Nullable final BigDecimal amount,
                                   final PaymentPluginStatus paymentPluginStatus) throws PaymentApiException {
+        return createPayment(account, transactionType, paymentId, paymentExternalKey, paymentTransactionExternalKey, amount, paymentPluginStatus);
+    }
+
+    private Payment createPayment(final Account account,
+                                  final TransactionType transactionType,
+                                  @Nullable final UUID paymentId,
+                                  @Nullable final String paymentExternalKey,
+                                  @Nullable final String paymentTransactionExternalKey,
+                                  @Nullable final BigDecimal amount,
+                                  final PaymentPluginStatus paymentPluginStatus) throws PaymentApiException {
         final Iterable<PluginProperty> pluginProperties = ImmutableList.<PluginProperty>of(new PluginProperty(MockPaymentProviderPlugin.PLUGIN_PROPERTY_PAYMENT_PLUGIN_STATUS_OVERRIDE, paymentPluginStatus.toString(), false));
         switch (transactionType) {
             case AUTHORIZE:
diff --git a/profiles/killbill/src/main/resources/update-checker/killbill-server-update-list.properties b/profiles/killbill/src/main/resources/update-checker/killbill-server-update-list.properties
index 8444c7d..182a482 100644
--- a/profiles/killbill/src/main/resources/update-checker/killbill-server-update-list.properties
+++ b/profiles/killbill/src/main/resources/update-checker/killbill-server-update-list.properties
@@ -1,231 +1,164 @@
 ## Top level keys
 # general.notice = This notice should rarely, if ever, be used as everyone will see it
 
+### 0.17.x series ###
+
+# 0.17.0
+0.17.0.updates           =
+0.17.0.notices           = This is the latest dev release.
+0.17.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.17.0
+
+### 0.16.x series ###
+
+# 0.16.6
+0.16.6.updates           =
+0.16.6.notices           = This is the latest GA release.
+0.16.6.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.6
+
+# 0.16.5
+0.16.5.updates           = 0.16.6
+0.16.5.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.5.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.5
+
+# 0.16.4
+0.16.4.updates           = 0.16.6
+0.16.4.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.4.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.4
+
+# 0.16.3
+0.16.3.updates           = 0.16.6
+0.16.3.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.3.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.3
+
+# 0.16.2
+0.16.2.updates           = 0.16.6
+0.16.2.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.2.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.2
+
+# 0.16.1
+0.16.1.updates           = 0.16.6
+0.16.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.1
+
+# 0.16.0
+0.16.0.updates           = 0.16.6
+0.16.0.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.0
+
+### 0.15.x series ###
+
+# 0.15.10
+0.15.10.updates           =
+0.15.10.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.10.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.10
+
+# 0.15.9
+0.15.9.updates           = 0.15.10
+0.15.9.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.9.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.9
+
+# 0.15.8
+0.15.8.updates           = 0.15.10
+0.15.8.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.8.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.8
+
+# 0.15.7
+0.15.7.updates           = 0.15.10
+0.15.7.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.7.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.7
+
+# 0.15.6
+0.15.6.updates           = 0.15.10
+0.15.6.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.6.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.6
+
+# 0.15.5
+0.15.5.updates           = 0.15.10
+0.15.5.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.5.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.5
+
+# 0.15.4
+0.15.4.updates           = 0.15.10
+0.15.4.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.4.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.4
+
+# 0.15.3
+0.15.3.updates           = 0.15.10
+0.15.3.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.3.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.3
+
+# 0.15.2
+0.15.2.updates           = 0.15.10
+0.15.2.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.2.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.2
+
+# 0.15.1
+0.15.1.updates           = 0.15.10
+0.15.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.1
+
+# 0.15.0
+0.15.0.updates           = 0.15.10
+0.15.0.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.0
+
 ### 0.14.x series ###
 
+# 0.14.1
+0.14.1.updates           =
+0.14.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.14.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.14.1
+
 # 0.14.0
-0.14.0.updates           =
-0.14.0.notices           = This is the latest GA release.
-0.14.0.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
+0.14.0.updates           = 0.14.1
+0.14.0.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.14.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.14.0
 
 ### 0.13.x series ###
 
-## 0.13.7 -- latest unstable release
+# 0.13.7
 0.13.7.updates           =
-0.13.7.notices           = This is the latest dev release.
+0.13.7.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.7.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.7
 
-## 0.13.6
+# 0.13.6
 0.13.6.updates           = 0.13.7
-0.13.6.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.6.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.6.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.6
 
-## 0.13.5
+# 0.13.5
 0.13.5.updates           = 0.13.7
-0.13.5.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.5.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.5.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.5
 
-## 0.13.4
+# 0.13.4
 0.13.4.updates           = 0.13.7
-0.13.4.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.4.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.4.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.4
 
-## 0.13.3
+# 0.13.3
 0.13.3.updates           = 0.13.7
-0.13.3.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.3.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.3.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.3
 
-## 0.13.2
+# 0.13.2
 0.13.2.updates           = 0.13.7
-0.13.2.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.2.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.2.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.2
 
-## 0.13.1
+# 0.13.1
 0.13.1.updates           = 0.13.7
-0.13.1.notices           = We recommend upgrading to 0.13.7, our latest dev release.
-0.13.1.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
+0.13.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.13.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.1
 
 ### 0.12.x series ###
 
-## 0.12.1
+# 0.12.1
 0.12.1.updates           =
-0.12.1.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.12.1.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
+0.12.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.12.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.12.1
 
 # 0.12.0
 0.12.0.updates           = 0.12.1
-0.12.0.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.12.0.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-### 0.11.x series ###
-
-## 0.11.13
-0.11.13.updates           =
-0.11.13.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.13.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.12
-0.11.12.updates           = 0.11.13
-0.11.12.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.12.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.11
-0.11.11.updates           = 0.11.13
-0.11.11.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.11.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.10
-0.11.10.updates           = 0.11.13
-0.11.10.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.10.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.9
-0.11.9.updates           = 0.11.13
-0.11.9.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.9.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.8
-0.11.8.updates           = 0.11.13
-0.11.8.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.8.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.7
-0.11.7.updates           = 0.11.13
-0.11.7.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.7.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.6
-0.11.6.updates           = 0.11.13
-0.11.6.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.6.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.5
-0.11.5.updates           = 0.11.13
-0.11.5.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.5.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.4
-0.11.4.updates           = 0.11.13
-0.11.4.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.4.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.3
-0.11.3.updates           = 0.11.13
-0.11.3.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.3.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.2
-0.11.2.updates           = 0.11.13
-0.11.2.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.2.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.1
-0.11.1.updates           = 0.11.13
-0.11.1.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.1.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-### 0.10.x series ###
-
-## 0.10.2
-0.10.2.updates           =
-0.10.2.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.10.2.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.10.1
-0.10.1.updates           = 0.10.2
-0.10.1.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.10.1.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.10.0
-0.10.0.updates           = 0.10.2
-0.10.0.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.10.0.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-### 0.9.x series ###
-
-## 0.9.2
-0.9.2.updates           =
-0.9.2.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.9.2.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.9.1
-0.9.1.updates           = 0.9.2
-0.9.1.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.9.1.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.9.0
-0.9.0.updates           = 0.9.2
-0.9.0.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.9.0.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-### 0.8.x series ###
-
-## 0.8.13
-0.8.13.updates           =
-0.8.13.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.13.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.12
-0.8.12.updates           = 0.8.13
-0.8.12.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.12.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.11
-0.8.11.updates           = 0.8.13
-0.8.11.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.11.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.10
-0.8.10.updates           = 0.8.13
-0.8.10.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.10.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.9
-0.8.9.updates           = 0.8.13
-0.8.9.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.9.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.8
-0.8.8.updates           = 0.8.13
-0.8.8.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.8.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.7
-0.8.7.updates           = 0.8.13
-0.8.7.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.7.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.6
-0.8.6.updates           = 0.8.13
-0.8.6.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.6.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.5
-0.8.5.updates           = 0.8.13
-0.8.5.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.5.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.4
-0.8.4.updates           = 0.8.13
-0.8.4.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.4.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.3
-0.8.3.updates           = 0.8.13
-0.8.3.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.3.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.2
-0.8.2.updates           = 0.8.13
-0.8.2.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.2.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.1
-0.8.1.updates           = 0.8.13
-0.8.1.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.1.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.8.0
-0.8.0.updates           = 0.8.13
-0.8.0.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.8.0.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
+0.12.0.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.12.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.12.0
diff --git a/profiles/killpay/src/main/resources/update-checker/killbill-server-update-list.properties b/profiles/killpay/src/main/resources/update-checker/killbill-server-update-list.properties
index f585321..182a482 100644
--- a/profiles/killpay/src/main/resources/update-checker/killbill-server-update-list.properties
+++ b/profiles/killpay/src/main/resources/update-checker/killbill-server-update-list.properties
@@ -1,105 +1,164 @@
 ## Top level keys
 # general.notice = This notice should rarely, if ever, be used as everyone will see it
 
+### 0.17.x series ###
+
+# 0.17.0
+0.17.0.updates           =
+0.17.0.notices           = This is the latest dev release.
+0.17.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.17.0
+
+### 0.16.x series ###
+
+# 0.16.6
+0.16.6.updates           =
+0.16.6.notices           = This is the latest GA release.
+0.16.6.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.6
+
+# 0.16.5
+0.16.5.updates           = 0.16.6
+0.16.5.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.5.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.5
+
+# 0.16.4
+0.16.4.updates           = 0.16.6
+0.16.4.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.4.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.4
+
+# 0.16.3
+0.16.3.updates           = 0.16.6
+0.16.3.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.3.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.3
+
+# 0.16.2
+0.16.2.updates           = 0.16.6
+0.16.2.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.2.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.2
+
+# 0.16.1
+0.16.1.updates           = 0.16.6
+0.16.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.1
+
+# 0.16.0
+0.16.0.updates           = 0.16.6
+0.16.0.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.16.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.16.0
+
+### 0.15.x series ###
+
+# 0.15.10
+0.15.10.updates           =
+0.15.10.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.10.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.10
+
+# 0.15.9
+0.15.9.updates           = 0.15.10
+0.15.9.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.9.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.9
+
+# 0.15.8
+0.15.8.updates           = 0.15.10
+0.15.8.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.8.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.8
+
+# 0.15.7
+0.15.7.updates           = 0.15.10
+0.15.7.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.7.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.7
+
+# 0.15.6
+0.15.6.updates           = 0.15.10
+0.15.6.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.6.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.6
+
+# 0.15.5
+0.15.5.updates           = 0.15.10
+0.15.5.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.5.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.5
+
+# 0.15.4
+0.15.4.updates           = 0.15.10
+0.15.4.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.4.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.4
+
+# 0.15.3
+0.15.3.updates           = 0.15.10
+0.15.3.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.3.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.3
+
+# 0.15.2
+0.15.2.updates           = 0.15.10
+0.15.2.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.2.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.2
+
+# 0.15.1
+0.15.1.updates           = 0.15.10
+0.15.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.1
+
+# 0.15.0
+0.15.0.updates           = 0.15.10
+0.15.0.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.15.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.15.0
+
 ### 0.14.x series ###
 
+# 0.14.1
+0.14.1.updates           =
+0.14.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.14.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.14.1
+
 # 0.14.0
-0.14.0.updates           =
-0.14.0.notices           = This is the latest GA release.
-0.14.0.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
+0.14.0.updates           = 0.14.1
+0.14.0.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.14.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.14.0
 
 ### 0.13.x series ###
 
-## 0.13.7 -- latest unstable release
+# 0.13.7
 0.13.7.updates           =
-0.13.7.notices           = This is the latest dev release.
+0.13.7.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.7.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.7
 
-## 0.13.6
+# 0.13.6
 0.13.6.updates           = 0.13.7
-0.13.6.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.6.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.6.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.6
 
-## 0.13.5
+# 0.13.5
 0.13.5.updates           = 0.13.7
-0.13.5.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.5.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.5.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.5
 
-## 0.13.4
+# 0.13.4
 0.13.4.updates           = 0.13.7
-0.13.4.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.4.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.4.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.4
 
-## 0.13.3
+# 0.13.3
 0.13.3.updates           = 0.13.7
-0.13.3.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.3.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.3.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.3
 
-## 0.13.2
+# 0.13.2
 0.13.2.updates           = 0.13.7
-0.13.2.notices           = We recommend upgrading to 0.13.7, our latest dev release.
+0.13.2.notices           = We recommend upgrading to 0.16.6, our latest GA release.
 0.13.2.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.2
 
-## 0.13.1
+# 0.13.1
 0.13.1.updates           = 0.13.7
-0.13.1.notices           = We recommend upgrading to 0.13.7, our latest dev release.
-0.13.1.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
+0.13.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.13.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.13.1
 
 ### 0.12.x series ###
 
-## 0.12.1
+# 0.12.1
 0.12.1.updates           =
-0.12.1.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.12.1.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
+0.12.1.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.12.1.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.12.1
 
 # 0.12.0
 0.12.0.updates           = 0.12.1
-0.12.0.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.12.0.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-### 0.11.x series ###
-
-## 0.11.13
-0.11.13.updates           =
-0.11.13.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.13.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.12
-0.11.12.updates           = 0.11.13
-0.11.12.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.12.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.11
-0.11.11.updates           = 0.11.13
-0.11.11.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.11.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.10
-0.11.10.updates           = 0.11.13
-0.11.10.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.10.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.9
-0.11.9.updates           = 0.11.13
-0.11.9.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.9.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.8
-0.11.8.updates           = 0.11.13
-0.11.8.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.8.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.7
-0.11.7.updates           = 0.11.13
-0.11.7.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.7.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.6
-0.11.6.updates           = 0.11.13
-0.11.6.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.6.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
-
-## 0.11.5
-0.11.5.updates           = 0.11.13
-0.11.5.notices           = We recommend upgrading to 0.14.0, our latest GA release.
-0.11.5.release-notes     = https://github.com/killbill/killbill/blob/master/NEWS
+0.12.0.notices           = We recommend upgrading to 0.16.6, our latest GA release.
+0.12.0.release-notes     = https://github.com/killbill/killbill/releases/tag/killbill-0.12.0
diff --git a/util/src/main/java/org/killbill/billing/util/config/definition/JaxrsConfig.java b/util/src/main/java/org/killbill/billing/util/config/definition/JaxrsConfig.java
index a55cd88..95fe22d 100644
--- a/util/src/main/java/org/killbill/billing/util/config/definition/JaxrsConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/definition/JaxrsConfig.java
@@ -35,7 +35,7 @@ public interface JaxrsConfig extends KillbillConfig {
     TimeSpan getJaxrsTimeout();
 
     @Config("org.killbill.jaxrs.location.full.url")
-    @Default("false")
+    @Default("true")
     @Description("Type of return for the jaxrs response location URL")
     boolean isJaxrsLocationFullUrl();