svn commit: r424483 - in /incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment: BillingAccountWorker.java PaymentGatewayServices.java

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r424483 - in /incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment: BillingAccountWorker.java PaymentGatewayServices.java

sichen
Author: sichen
Date: Fri Jul 21 15:39:22 2006
New Revision: 424483

URL: http://svn.apache.org/viewvc?rev=424483&view=rev
Log:
Refactoring of the way billing accounts work, using PaymentApplication to track the amount of a billing account used up in each invoice.  Also changed captureOrderPayments to use up billing accounts first before other payment methods.  Finally, new methods to get net vs. available balances of a billing account

Modified:
    incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/BillingAccountWorker.java
    incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java

Modified: incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/BillingAccountWorker.java
URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/BillingAccountWorker.java?rev=424483&r1=424482&r2=424483&view=diff
==============================================================================
--- incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/BillingAccountWorker.java (original)
+++ incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/BillingAccountWorker.java Fri Jul 21 15:39:22 2006
@@ -119,10 +119,21 @@
     public static BigDecimal getBillingAccountBalance(GenericValue billingAccount) throws GenericEntityException {
         return getBillingAccountBalance(billingAccount.getDelegator(), billingAccount.getString("billingAccountId"));
     }
-        
+    
+    /**
+     * Calculates the "available" balance of a billing account, which is net balance minus amount of pending (not canceled, rejected, or completed) orders.  When looking at
+     * using a billing account for a new order, you should use this method
+     * @param delegator
+     * @param billingAccountId
+     * @return
+     * @throws GenericEntityException
+     */
+    
     public static BigDecimal getBillingAccountBalance(GenericDelegator delegator, String billingAccountId) throws GenericEntityException {
-        BigDecimal balance = ZERO;
-        // first get all the pending orders (not cancelled, rejected or completed)
+        // first get the net balance of invoices - payments
+        BigDecimal balance = getBillingAccountNetBalance(delegator, billingAccountId);
+        
+        // now the amounts of all the pending orders (not cancelled, rejected or completed)
         List orderHeaders = null;
         List exprs1 = new LinkedList();
         exprs1.add(new EntityExpr("billingAccountId", EntityOperator.EQUALS, billingAccountId));
@@ -141,41 +152,55 @@
             }
         }
         
-        // next get all the un-paid invoices (this will include all completed orders)
-        List invoices = null;
-        List exprs2 = new LinkedList();
-        exprs2.add(new EntityExpr("billingAccountId", EntityOperator.EQUALS, billingAccountId));
-        exprs2.add(new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "INVOICE_CANCELLED"));
-        exprs2.add(new EntityExpr("statusId", EntityOperator.NOT_EQUAL, "INVOICE_PAID"));
-
-        invoices = delegator.findByAnd("Invoice", exprs2);
-
-        if (invoices != null) {
-            Iterator ii = invoices.iterator();
-            while (ii.hasNext()) {
-                GenericValue invoice = (GenericValue) ii.next();
-                balance = balance.add(InvoiceWorker.getInvoiceNotApplied(invoice));
-            }
+        balance = balance.setScale(decimals, rounding);
+        return balance;
+    }
+    
+    /**
+     * Returns the amount which could be charged to a billing account, which is defined as the accountLimit minus account balance and minus the balance of outstanding orders
+     * When trying to figure out how much of a billing account can be used to pay for an outstanding order, use this method
+     * @param billingAccount
+     * @return
+     * @throws GenericEntityException
+     */
+    public static BigDecimal getBillingAccountAvailableBalance(GenericValue billingAccount) throws GenericEntityException {
+        if ((billingAccount != null) && (billingAccount.get("accountLimit") != null)) {
+            BigDecimal accountLimit = new BigDecimal(billingAccount.getDouble("accountLimit").doubleValue());
+            BigDecimal availableBalance = accountLimit.subtract(getBillingAccountBalance(billingAccount)).setScale(decimals, rounding);
+            return availableBalance;
+        } else {
+            return ZERO;
         }
-        
-        // finally apply any payments to the balance
-        List credits = null;
-        List exprs3 = new LinkedList();
-        exprs3.add(new EntityExpr("billingAccountId", EntityOperator.EQUALS, billingAccountId));
-        exprs3.add(new EntityExpr("invoiceId", EntityOperator.EQUALS, GenericEntity.NULL_FIELD));
-
-        credits = delegator.findByAnd("PaymentApplication", exprs3);
-
-        if (credits != null) {
-            Iterator ci = credits.iterator();
-            while (ci.hasNext()) {
-                GenericValue credit = (GenericValue) ci.next();
-                BigDecimal amount = credit.getBigDecimal("amountApplied");
-                if (amount != null) {
-                    balance = balance.subtract(amount);
+    }
+    
+    /**
+     * Calculates the net balance of a billing account, which is sum of all amounts applied to invoices minus sum of all amounts applied from payments.
+     * When charging or capturing an invoice to a billing account, use this method
+     * @param delegator
+     * @param billingAccountId
+     * @return
+     * @throws GenericEntityException
+     */
+    public static BigDecimal getBillingAccountNetBalance(GenericDelegator delegator, String billingAccountId) throws GenericEntityException {
+        BigDecimal balance = ZERO;
+    
+        // search through all PaymentApplications and add the amount that was applied to invoice and subtract the amount applied from payments
+        List paymentAppls = delegator.findByAnd("PaymentApplication", UtilMisc.toMap("billingAccountId", billingAccountId));
+        if (paymentAppls != null) {
+            for (Iterator pAi = paymentAppls.iterator(); pAi.hasNext(); ) {
+                GenericValue paymentAppl = (GenericValue) pAi.next();
+                BigDecimal amountApplied = paymentAppl.getBigDecimal("amountApplied");
+                if (paymentAppl.getString("invoiceId") != null) {
+                    // make sure the invoice has not been canceled
+                    if (!"INVOICE_CANCELED".equals(paymentAppl.getRelatedOne("Invoice").getString("statusId"))) {
+                        balance = balance.add(amountApplied);    
+                    }
+                } else {
+                    balance = balance.subtract(amountApplied);
                 }
             }
         }
+    
         balance = balance.setScale(decimals, rounding);
         return balance;
     }
@@ -185,9 +210,11 @@
         String billingAccountId = (String) context.get("billingAccountId");
         GenericValue billingAccount = null;
         Double accountBalance = null;
+        Double netAccountBalance = null;
         try {
             billingAccount = delegator.findByPrimaryKey("BillingAccount", UtilMisc.toMap("billingAccountId", billingAccountId));
             accountBalance = new Double((getBillingAccountBalance(delegator, billingAccountId)).doubleValue());
+            netAccountBalance = new Double((getBillingAccountNetBalance(delegator, billingAccountId)).doubleValue());
         } catch (GenericEntityException e) {
             Debug.logError(e, module);
             return ServiceUtil.returnError("Error getting billing account or calculating balance for billing account #" + billingAccountId);
@@ -199,6 +226,7 @@
         
         Map result = ServiceUtil.returnSuccess();
         result.put("accountBalance", accountBalance);
+        result.put("netAccountBalance", netAccountBalance);
         result.put("billingAccount", billingAccount);
         return result;  
     }

Modified: incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java
URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java?rev=424483&r1=424482&r2=424483&view=diff
==============================================================================
--- incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java (original)
+++ incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java Fri Jul 21 15:39:22 2006
@@ -34,6 +34,7 @@
 import org.ofbiz.base.util.GeneralException;
 import org.ofbiz.base.util.UtilDateTime;
 import org.ofbiz.base.util.UtilMisc;
+import org.ofbiz.base.util.UtilNumber;
 import org.ofbiz.base.util.UtilProperties;
 import org.ofbiz.base.util.UtilValidate;
 import org.ofbiz.entity.GenericDelegator;
@@ -74,7 +75,17 @@
     public static final String REFUND_SERVICE_TYPE = "PRDS_PAY_REFUND";
     public static final String CREDIT_SERVICE_TYPE = "PRDS_PAY_CREDIT";
     private static final int TX_TIME = 300;
+    private static BigDecimal ZERO = new BigDecimal("0");
+    private static int decimals = -1;
+    private static int rounding = -1;
+    static {
+        decimals = UtilNumber.getBigDecimalScale("order.decimals");
+        rounding = UtilNumber.getBigDecimalRoundingMode("order.rounding");
 
+        // set zero to the proper scale
+        if (decimals != -1) ZERO.setScale(decimals);
+    }
+    
     /**
      * Authorizes a single order preference with an option to specify an amount. The result map has the Booleans
      * "errors" and "finished" which notify the user if there were any errors and if the authorizatoin was finished.
@@ -815,6 +826,63 @@
             return ServiceUtil.returnError("Could not find OrderHeader with orderId: " + orderId + "; not processing payments.");
         }
 
+        // See if there is a billing account first.  If so, just charge the captureAmount to the billing account via PaymentApplication
+        // or at least charge up the full amount of the billing account's net balance and then try other payment methods
+        // Note that this needs to run here first because orders charged to billing account alone will have no OrderPaymentPreference
+        GenericValue billingAccount = null;
+        BigDecimal billingAccountBalance = null;
+        BigDecimal billingAccountAvail = null;
+        BigDecimal billingAccountCaptureAmount = ZERO;
+        Map billingAccountInfo = null;
+        if (UtilValidate.isNotEmpty(billingAccountId)) {
+            try {
+                billingAccountInfo = dispatcher.runSync("calcBillingAccountBalance", UtilMisc.toMap("billingAccountId", billingAccountId));
+            } catch (GenericServiceException e) {
+                Debug.logError(e, "Unable to get billing account information for #" + billingAccountId, module);
+            }
+        }
+        if (billingAccountInfo != null) {
+            billingAccount = (GenericValue) billingAccountInfo.get("billingAccount");
+            // use net account balance because we want to know how much we can charge to a billing account
+            Double billingAccountBalanceD = (Double) billingAccountInfo.get("netAccountBalance");  
+            billingAccountBalance = new BigDecimal(billingAccountBalanceD.doubleValue());
+        }
+        
+        // if a billing account is used to pay for an order, then charge as much as we can to it before proceeding to other payment methods.
+        if (billingAccount != null && billingAccountBalance != null) {
+            try {
+                billingAccountAvail = BillingAccountWorker.getBillingAccountAvailableBalance(billingAccount);
+            
+                // the amount to be "charged" to the billing account, which is the minimum of billing account remaining amount and amount to capture
+                BigDecimal captureAmountBd = new BigDecimal(captureAmount.doubleValue());
+                billingAccountCaptureAmount = billingAccountAvail.min(captureAmountBd);
+                Debug.logInfo("billing account avail = [" + billingAccountAvail + "] capture amount = [" + billingAccountCaptureAmount + "]", module);
+            
+                // capturing to a billing account is a matter of a creating a payment and then applying it to the invoice
+                Map tmpResult = dispatcher.runSync("captureBillingAccountPayment", UtilMisc.toMap("invoiceId", invoiceId, "billingAccountId", billingAccountId,
+                        "captureAmount", new Double(billingAccountCaptureAmount.doubleValue()), "userLogin", userLogin));
+                if (ServiceUtil.isError(tmpResult)) {
+                    return tmpResult;
+                }
+                
+                // now, if the full amount had not been captured, then capture
+                // it from other methods, otherwise return
+                if (billingAccountCaptureAmount.compareTo(captureAmountBd) == -1) {
+                    BigDecimal outstandingAmount = captureAmountBd.subtract(billingAccountCaptureAmount).setScale(decimals, rounding);
+                    captureAmount = new Double(outstandingAmount.doubleValue());
+                } else {
+                    Debug.logInfo("Amount to capture [" + captureAmount + "] was fully captured in Payment [" + tmpResult.get("paymentId") + "].", module);
+                    result = ServiceUtil.returnSuccess();
+                    result.put("processResult", "COMPLETE");
+                    return result;
+                }
+            } catch (GenericEntityException ex) {
+                return ServiceUtil.returnError(ex.getMessage());
+            } catch (GenericServiceException ex) {
+                return ServiceUtil.returnError(ex.getMessage());
+            }
+        }
+        
         // return complete if no payment prefs were found
         if (paymentPrefs == null || paymentPrefs.size() == 0) {
             Debug.logWarning("No orderPaymentPreferences available to capture", module);
@@ -854,30 +922,6 @@
         double amountToCapture = captureAmount.doubleValue();
         if (Debug.infoOn()) Debug.logInfo("Actual Expected Capture Amount : " + amountToCapture, module);
 
-        // if we have a billing account get balance/limit and available
-        GenericValue billingAccount = null;
-        Double billingAccountBalance = null;
-        Double billingAccountAvail = null;
-        Map billingAccountInfo = null;
-        if (UtilValidate.isNotEmpty(billingAccountId)) {
-            try {
-                billingAccountInfo = dispatcher.runSync("calcBillingAccountBalance", UtilMisc.toMap("billingAccountId", billingAccountId));
-            } catch (GenericServiceException e) {
-                Debug.logError(e, "Unable to get billing account information for #" + billingAccountId, module);
-            }
-        }
-        if (billingAccountInfo != null) {
-            billingAccount = (GenericValue) billingAccountInfo.get("billingAccount");
-            billingAccountBalance = (Double) billingAccountInfo.get("accountBalance");
-        }
-        if (billingAccount != null && billingAccountBalance != null) {
-            Double accountLimit = billingAccount.getDouble("accountLimit");
-            if (accountLimit == null) {
-                accountLimit = new Double(0.00);
-            }
-            billingAccountAvail = new Double(accountLimit.doubleValue() - billingAccountBalance.doubleValue());
-        }
-
         // iterate over the prefs and capture each one until we meet our total
         List finished = new ArrayList();
         Iterator payments = paymentPrefs.iterator();
@@ -897,7 +941,7 @@
                 continue;
             }
             //Debug.log("Actual Auth amount : " + authAmount, module);
-
+
             // if the authAmount is more then the remaining total; just use remaining total
             if (authAmount.doubleValue() > remainingTotal) {
                 authAmount = new Double(remainingTotal);
@@ -930,7 +974,7 @@
                 Debug.logError("The amount to capture was more then what was authorized; we only captured the authorized amount : " + paymentPref, module);
                 amountThisCapture = authAmount.doubleValue();
             }
-
+            
             Map captureResult = capturePayment(dctx, userLogin, orh, paymentPref, amountThisCapture);
             if (captureResult != null) {
                 Double amountCaptured = (Double) captureResult.get("captureAmount");
@@ -951,10 +995,14 @@
                 }
 
                 // create any splits which are needed
-                if (authAmount.doubleValue() > amountThisCapture) {
+                // we need to add up the amount that has been captured from both credit card and the billing account
+                // otherwise, if part of invoice was captured to billing account, the system will authorize that amount again on the credit card
+                BigDecimal totalAmountCaptured = new BigDecimal(amountThisCapture);
+                totalAmountCaptured = totalAmountCaptured.add(billingAccountCaptureAmount).setScale(decimals, rounding);
+                if (authAmount.doubleValue() > totalAmountCaptured.doubleValue()) {
                     // create a new payment preference and authorize it
+                    double newAmount = authAmount.doubleValue() - totalAmountCaptured.doubleValue(); // TODO: use BigDecimal arithmetic here (and everywhere else for that matter)
                     Debug.logInfo("Creating payment preference split", module);
-                    double newAmount = authAmount.doubleValue() - amountThisCapture;
                     String newPrefId = delegator.getNextSeqId("OrderPaymentPreference");
                     GenericValue newPref = delegator.makeValue("OrderPaymentPreference", UtilMisc.toMap("orderPaymentPreferenceId", newPrefId));
                     newPref.set("orderId", paymentPref.get("orderId"));
@@ -1010,6 +1058,47 @@
         }
     }
 
+    public static Map captureBillingAccountPayment(DispatchContext dctx, Map context) {
+        GenericDelegator delegator = dctx.getDelegator();
+        LocalDispatcher dispatcher = dctx.getDispatcher();
+        GenericValue userLogin = (GenericValue) context.get("userLogin");
+        String invoiceId = (String) context.get("invoiceId");
+        String billingAccountId = (String) context.get("billingAccountId");
+        Double captureAmount = (Double) context.get("captureAmount");
+        Map results = ServiceUtil.returnSuccess();
+        
+        try {
+            GenericValue invoice = delegator.findByPrimaryKey("Invoice", UtilMisc.toMap("invoiceId", invoiceId));
+            Map paymentParams = UtilMisc.toMap("paymentTypeId", "CUSTOMER_PAYMENT", "paymentMethodTypeId", "EXT_BILLACT",
+                    "partyIdFrom", invoice.getString("partyIdFrom"), "partyIdTo", invoice.getString("partyId"),
+                    "statusId", "PMNT_RECEIVED", "effectiveDate", UtilDateTime.nowTimestamp());
+            paymentParams.put("amount", captureAmount);
+            paymentParams.put("currencyUomId", invoice.getString("currencyUomId"));
+            paymentParams.put("userLogin", userLogin);
+            Map tmpResult = dispatcher.runSync("createPayment", paymentParams);
+            if (ServiceUtil.isError(tmpResult)) {
+                return tmpResult;
+            }
+            
+            String paymentId = (String) tmpResult.get("paymentId");
+            tmpResult = dispatcher.runSync("createPaymentApplication", UtilMisc.toMap("paymentId", paymentId, "invoiceId", invoiceId, "billingAccountId", billingAccountId,
+                    "amountApplied", captureAmount, "userLogin", userLogin));
+            if (ServiceUtil.isError(tmpResult)) {
+                return tmpResult;
+            }
+            if (paymentId == null) {
+                return ServiceUtil.returnError("No payment created for invoice [" + invoiceId + "] and billing account [" + billingAccountId + "]");
+            }
+            results.put("paymentId", paymentId);
+        } catch (GenericEntityException ex) {
+            return ServiceUtil.returnError(ex.getMessage());
+        } catch (GenericServiceException ex) {
+            return ServiceUtil.returnError(ex.getMessage());
+        }
+
+        return results;
+    }
+    
     private static Map capturePayment(DispatchContext dctx, GenericValue userLogin, OrderReadHelper orh, GenericValue paymentPref, double amount) {
         return capturePayment(dctx, userLogin, orh, paymentPref, amount, null);
     }
@@ -1486,6 +1575,8 @@
             }
         }
 
+        // SC 20060718: I think this should be re-factored to use invoice.partyIdFrom and invoice.partyId instead of getting the IDs from the order
+        // so we can capture payments on invoices without orders correctly
         String orderId = paymentPreference.getString("orderId");
         List orl = null;
         try {