killbill-uncached

Add logic for Invoice Item Adjustment check. We allow scenario

6/14/2013 8:15:10 PM

Details

diff --git a/api/src/main/java/com/ning/billing/ErrorCode.java b/api/src/main/java/com/ning/billing/ErrorCode.java
index aff1bd1..c21c935 100644
--- a/api/src/main/java/com/ning/billing/ErrorCode.java
+++ b/api/src/main/java/com/ning/billing/ErrorCode.java
@@ -194,7 +194,7 @@ public enum ErrorCode {
     INVOICE_NOTHING_TO_DO(4007, "No invoice to generate for account %s and date %s"),
     INVOICE_NO_SUCH_CREDIT(4008, "Credit item for id %s does not exist"),
     CREDIT_AMOUNT_INVALID(4009, "Credit amount %s should be strictly positive"),
-    INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID(4010, "Invoice adjustment amount %s should be strictly positive"),
+    INVOICE_ITEM_ADJUSTMENT_AMOUNT_SHOULD_BE_POSITIVE(4010, "Invoice adjustment amount %s should be strictly positive"),
     INVOICE_ITEM_NOT_FOUND(4011, "No invoice item could be found for id %s."),
     INVOICE_INVALID_FOR_INVOICE_ITEM_ADJUSTMENT(4012, "Invoice item %s doesn't belong to invoice %s."),
     INVOICE_NO_SUCH_EXTERNAL_CHARGE(4014, "External charge item for id %s does not exist"),
@@ -203,6 +203,7 @@ public enum ErrorCode {
     INVOICE_ALREADY_EXISTS(4017, "The invoice already exists %s"),
     INVOICE_NUMBER_NOT_FOUND(4018, "No invoice could be found for number %s."),
     INVOICE_INVALID_NUMBER(4019, "Invalid invoice number %s."),
+    INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID(4020, "Invoice adjustment amount %s should be lower than %s"),
 
     /*
      *
@@ -216,7 +217,7 @@ public enum ErrorCode {
     CHARGE_BACK_DOES_NOT_EXIST(4004, "Could not find chargeback for id %s."),
     INVOICE_PAYMENT_BY_ATTEMPT_NOT_FOUND(4905, "No invoice payment could be found for paymentAttempt id %s."),
     REFUND_AMOUNT_TOO_HIGH(4906, "Tried to refund %s of a %s payment."),
-    REFUND_AMOUNT_DONT_MATCH_ITEMS_TO_ADJUST(4907, "You can't specify a refund amount of %s that doesn't match the invoice items amount of %s."),
+    REFUND_AMOUNT_DONT_MATCH_ITEMS_TO_ADJUST(4907, "You can't specify a refund amount of %s that is less than the sum of the invoice items amount of %s."),
 
     /*
      *
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
index e0fcc2f..fa3e420 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceUserApi.java
@@ -270,7 +270,7 @@ public class DefaultInvoiceUserApi implements InvoiceUserApi {
                                                    final LocalDate effectiveDate, @Nullable final BigDecimal amount,
                                                    @Nullable final Currency currency, final CallContext context) throws InvoiceApiException {
         if (amount != null && amount.compareTo(BigDecimal.ZERO) <= 0) {
-            throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID, amount);
+            throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_SHOULD_BE_POSITIVE, amount);
         }
 
         final InvoiceItemModelDao adjustment = dao.insertInvoiceItemAdjustment(accountId, invoiceId, invoiceItemId, effectiveDate, amount, currency, internalCallContextFactory.createInternalCallContext(accountId, context));
diff --git a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java
index 641d4f3..61797f6 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/dao/InvoiceDaoHelper.java
@@ -78,18 +78,28 @@ public class InvoiceDaoHelper {
             throw new IllegalStateException("Invoice shouldn't be null for id " + invoiceId);
         }
 
+        //
+        // If we have an item amount, we 'd like to use it, but we need to check first that it is lesser or equal than maximum allowed
+        //If, not we compute maximum value we can adjust per item
         for (final UUID invoiceItemId : invoiceItemIdsWithNullAmounts.keySet()) {
-            final BigDecimal adjAmount = Objects.firstNonNull(invoiceItemIdsWithNullAmounts.get(invoiceItemId),
-                                                              getInvoiceItemAmountForId(invoice, invoiceItemId));
-            final BigDecimal adjAmountRemainingAfterRepair = computeItemAdjustmentAmount(invoiceItemId, adjAmount, invoice.getInvoiceItems());
-            if (adjAmountRemainingAfterRepair.compareTo(BigDecimal.ZERO) > 0) {
-                invoiceItemIdsWithAmountsBuilder.put(invoiceItemId, adjAmountRemainingAfterRepair);
+            final BigDecimal originalItemAmount = getInvoiceItemAmountForId(invoice, invoiceItemId);
+            final BigDecimal maxAdjAmount = computeItemAdjustmentAmount(invoiceItemId, originalItemAmount, invoice.getInvoiceItems());
+
+            final BigDecimal proposedItemAmount = invoiceItemIdsWithNullAmounts.get(invoiceItemId);
+            if (proposedItemAmount != null && proposedItemAmount.compareTo(maxAdjAmount) > 0) {
+                throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID, proposedItemAmount, maxAdjAmount);
+            }
+
+            final BigDecimal itemAmountToAdjust = Objects.firstNonNull(proposedItemAmount, maxAdjAmount);
+            if (itemAmountToAdjust.compareTo(BigDecimal.ZERO) > 0) {
+                invoiceItemIdsWithAmountsBuilder.put(invoiceItemId, itemAmountToAdjust);
             }
         }
 
         return invoiceItemIdsWithAmountsBuilder.build();
     }
 
+
     /**
      * @param invoiceItem  item we are adjusting
      * @param requestedPositiveAmountToAdjust
@@ -124,9 +134,9 @@ public class InvoiceDaoHelper {
         throw new InvoiceApiException(ErrorCode.INVOICE_ITEM_NOT_FOUND, invoiceItemId);
     }
 
-    public BigDecimal computePositiveRefundAmount(final InvoicePaymentModelDao payment, final BigDecimal requestedAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) throws InvoiceApiException {
+    public BigDecimal computePositiveRefundAmount(final InvoicePaymentModelDao payment, final BigDecimal requestedRefundAmount, final Map<UUID, BigDecimal> invoiceItemIdsWithAmounts) throws InvoiceApiException {
         final BigDecimal maxRefundAmount = payment.getAmount() == null ? BigDecimal.ZERO : payment.getAmount();
-        final BigDecimal requestedPositiveAmount = requestedAmount == null ? maxRefundAmount : requestedAmount;
+        final BigDecimal requestedPositiveAmount = requestedRefundAmount == null ? maxRefundAmount : requestedRefundAmount;
         // This check is good but not enough, we need to also take into account previous refunds
         // (But that should have been checked in the payment call already)
         if (requestedPositiveAmount.compareTo(maxRefundAmount) > 0) {
@@ -140,7 +150,7 @@ public class InvoiceDaoHelper {
         }
 
         // Sanity check: if some items were specified, then the sum should be equal to specified refund amount, if specified
-        if (amountFromItems.compareTo(BigDecimal.ZERO) != 0 && requestedPositiveAmount.compareTo(amountFromItems) != 0) {
+        if (amountFromItems.compareTo(BigDecimal.ZERO) != 0 && requestedPositiveAmount.compareTo(amountFromItems) < 0) {
             throw new InvoiceApiException(ErrorCode.REFUND_AMOUNT_DONT_MATCH_ITEMS_TO_ADJUST, requestedPositiveAmount, amountFromItems);
         }
         return requestedPositiveAmount;
diff --git a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
index 364a4b6..5145f8f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/api/user/TestDefaultInvoiceUserApi.java
@@ -332,7 +332,7 @@ public class TestDefaultInvoiceUserApi extends InvoiceTestSuiteWithEmbeddedDB {
                                                        BigDecimal.TEN.negate(), accountCurrency, callContext);
             Assert.fail("Should not have been able to adjust an item with a negative amount");
         } catch (InvoiceApiException e) {
-            Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID.getCode());
+            Assert.assertEquals(e.getCode(), ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_SHOULD_BE_POSITIVE.getCode());
         }
     }
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
index a612e00..977f427 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/mappers/InvoiceApiExceptionMapper.java
@@ -60,7 +60,7 @@ public class InvoiceApiExceptionMapper extends ExceptionMapperBase implements Ex
             return buildBadRequestResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.CREDIT_AMOUNT_INVALID.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
-        } else if (exception.getCode() == ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_INVALID.getCode()) {
+        } else if (exception.getCode() == ErrorCode.INVOICE_ITEM_ADJUSTMENT_AMOUNT_SHOULD_BE_POSITIVE.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
         } else if (exception.getCode() == ErrorCode.INVOICE_NO_SUCH_EXTERNAL_CHARGE.getCode()) {
             return buildBadRequestResponse(exception, uriInfo);
diff --git a/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java b/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java
index 106499a..f1e918a 100644
--- a/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java
+++ b/payment/src/main/java/com/ning/billing/payment/provider/ExternalPaymentProviderPlugin.java
@@ -16,6 +16,23 @@
 
 package com.ning.billing.payment.provider;
 
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.payment.api.PaymentMethodKVInfo;
+import com.ning.billing.payment.api.PaymentMethodPlugin;
+import com.ning.billing.payment.plugin.api.PaymentInfoPlugin;
+import com.ning.billing.payment.plugin.api.PaymentMethodInfoPlugin;
+import com.ning.billing.payment.plugin.api.PaymentPluginApi;
+import com.ning.billing.payment.plugin.api.PaymentPluginApiException;
+import com.ning.billing.payment.plugin.api.PaymentPluginStatus;
+import com.ning.billing.payment.plugin.api.RefundInfoPlugin;
+import com.ning.billing.payment.plugin.api.RefundPluginStatus;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TenantContext;
 import com.ning.billing.util.clock.Clock;
 
 import com.google.inject.Inject;
@@ -25,14 +42,61 @@ import com.google.inject.Inject;
  * <p/>
  * The implementation is very similar to the no-op plugin, which it extends. This can potentially be an issue
  * if Killbill is processing a lot of external payments as they are all kept in memory.
- * TODO: do something about it
  */
-public class ExternalPaymentProviderPlugin extends DefaultNoOpPaymentProviderPlugin {
+public class ExternalPaymentProviderPlugin implements PaymentPluginApi {
 
     public static final String PLUGIN_NAME = "__EXTERNAL_PAYMENT__";
 
+    private final Clock clock;
+
     @Inject
     public ExternalPaymentProviderPlugin(final Clock clock) {
-        super(clock);
+        this.clock = clock;
+    }
+
+    @Override
+    public PaymentInfoPlugin processPayment(final UUID kbAccountId, final UUID kbPaymentId, final UUID kbPaymentMethodId, final BigDecimal amount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentInfoPlugin(amount, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
+    }
+
+    @Override
+    public PaymentInfoPlugin getPaymentInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentInfoPlugin(BigDecimal.ZERO, clock.getUTCNow(), clock.getUTCNow(), PaymentPluginStatus.PROCESSED, null);
+    }
+
+    @Override
+    public RefundInfoPlugin processRefund(final UUID kbAccountId, final UUID kbPaymentId, final BigDecimal refundAmount, final Currency currency, final CallContext context) throws PaymentPluginApiException {
+        return new DefaultNoOpRefundInfoPlugin(BigDecimal.ZERO, clock.getUTCNow(), clock.getUTCNow(), RefundPluginStatus.PROCESSED, null);
+    }
+
+    @Override
+    public List<RefundInfoPlugin> getRefundInfo(final UUID kbAccountId, final UUID kbPaymentId, final TenantContext context) throws PaymentPluginApiException {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void addPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final PaymentMethodPlugin paymentMethodProps, final boolean setDefault, final CallContext context) throws PaymentPluginApiException {
+    }
+
+    @Override
+    public void deletePaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    }
+
+    @Override
+    public PaymentMethodPlugin getPaymentMethodDetail(final UUID kbAccountId, final UUID kbPaymentMethodId, final TenantContext context) throws PaymentPluginApiException {
+        return new DefaultNoOpPaymentMethodPlugin("unknow", false, Collections.<PaymentMethodKVInfo>emptyList());
+    }
+
+    @Override
+    public void setDefaultPaymentMethod(final UUID kbAccountId, final UUID kbPaymentMethodId, final CallContext context) throws PaymentPluginApiException {
+    }
+
+    @Override
+    public List<PaymentMethodInfoPlugin> getPaymentMethods(final UUID kbAccountId, final boolean refreshFromGateway, final CallContext context) throws PaymentPluginApiException {
+        return null;
+    }
+
+    @Override
+    public void resetPaymentMethods(final UUID kbAccountId, final List<PaymentMethodInfoPlugin> paymentMethods) throws PaymentPluginApiException {
     }
 }