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 { |
Free forum by Nabble | Edit this page |