blob: d3ec75986362be4333cba8035ef24f66b01fcd70 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.ofbiz.accounting.finaccount;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Locale;
import java.util.Map;
import javolution.util.FastMap;
import org.ofbiz.accounting.payment.PaymentGatewayServices;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.GeneralException;
import org.ofbiz.base.util.UtilDateTime;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilProperties;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.entity.condition.EntityCondition;
import org.ofbiz.entity.condition.EntityOperator;
import org.ofbiz.entity.util.EntityQuery;
import org.ofbiz.entity.util.EntityUtilProperties;
import org.ofbiz.order.finaccount.FinAccountHelper;
import org.ofbiz.order.order.OrderReadHelper;
import org.ofbiz.product.store.ProductStoreWorker;
import org.ofbiz.service.DispatchContext;
import org.ofbiz.service.GenericServiceException;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.service.ServiceUtil;
/**
* FinAccountPaymentServices - Financial account used as payment method
*/
public class FinAccountPaymentServices {
public static final String module = FinAccountPaymentServices.class.getName();
public static final String resourceError = "AccountingErrorUiLabels";
// base payment integration services
public static Map<String, Object> finAccountPreAuth(DispatchContext dctx, Map<String, Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
String finAccountCode = (String) context.get("finAccountCode");
String finAccountPin = (String) context.get("finAccountPin");
String finAccountId = (String) context.get("finAccountId");
String orderId = (String) context.get("orderId");
BigDecimal amount = (BigDecimal) context.get("processAmount");
// check for an existing auth trans and cancel it
GenericValue authTrans = PaymentGatewayServices.getAuthTransaction(paymentPref);
if (authTrans != null) {
Map<String, Object> input = UtilMisc.toMap("userLogin", userLogin, "finAccountAuthId", authTrans.get("referenceNum"));
try {
dispatcher.runSync("expireFinAccountAuth", input);
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
}
if (finAccountId == null && paymentPref != null) {
finAccountId = paymentPref.getString("finAccountId");
}
// obtain the order information
OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
// NOTE DEJ20070808: this means that we want store related settings for where the item is being purchased,
//NOT where the account was setup; should this be changed to use settings from the store where the account was setup?
String productStoreId = orh.getProductStoreId();
// TODO, NOTE DEJ20070808: why is this setup this way anyway? for the allowAuthToNegative wouldn't that be better setup
//on the FinAccount and not on the ProductStoreFinActSetting? maybe an override on the FinAccount would be good...
// get the financial account
GenericValue finAccount;
if (finAccountId != null) {
try {
finAccount = EntityQuery.use(delegator).from("FinAccount").where("finAccountId", finAccountId).queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
} else {
if (finAccountCode != null) {
try {
finAccount = FinAccountHelper.getFinAccountFromCode(finAccountCode, delegator);
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountCannotLocateItFromAccountCode", locale));
}
} else {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountIdAndFinAccountCodeAreNull", locale));
}
}
if (finAccount == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountIdInvalid", locale));
}
String finAccountTypeId = finAccount.getString("finAccountTypeId");
finAccountId = finAccount.getString("finAccountId");
String statusId = finAccount.getString("statusId");
try {
// fin the store requires a pin number; validate the PIN with the code
Map<String, Object> findProductStoreFinActSettingMap = UtilMisc.<String, Object>toMap("productStoreId", productStoreId, "finAccountTypeId", finAccountTypeId);
GenericValue finAccountSettings = EntityQuery.use(delegator).from("ProductStoreFinActSetting").where(findProductStoreFinActSettingMap).cache().queryOne();
if (finAccountSettings == null) {
Debug.logWarning("In finAccountPreAuth could not find ProductStoreFinActSetting record, values searched by: " + findProductStoreFinActSettingMap, module);
}
if (Debug.verboseOn()) Debug.logVerbose("In finAccountPreAuth finAccountSettings=" + finAccountSettings, module);
BigDecimal minBalance = FinAccountHelper.ZERO;
String allowAuthToNegative = "N";
if (finAccountSettings != null) {
allowAuthToNegative = finAccountSettings.getString("allowAuthToNegative");
minBalance = finAccountSettings.getBigDecimal("minBalance");
if (minBalance == null) {
minBalance = FinAccountHelper.ZERO;
}
// validate the PIN if the store requires it
if ("Y".equals(finAccountSettings.getString("requirePinCode"))) {
if (!FinAccountHelper.validatePin(delegator, finAccountCode, finAccountPin)) {
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("authMessage", UtilProperties.getMessage(resourceError,
"AccountingFinAccountPinCodeCombinatorNotFound", locale));
result.put("authResult", Boolean.FALSE);
result.put("processAmount", amount);
result.put("authFlag", "0");
result.put("authCode", "A");
result.put("authRefNum", "0");
Debug.logWarning("Unable to auth FinAccount: " + result, module);
return result;
}
}
}
// check for expiration date
if ((finAccount.getTimestamp("thruDate") != null) && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) {
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("authMessage", UtilProperties.getMessage(resourceError,
"AccountingFinAccountExpired",
UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")), locale));
result.put("authResult", Boolean.FALSE);
result.put("processAmount", amount);
result.put("authFlag", "0");
result.put("authCode", "A");
result.put("authRefNum", "0");
Debug.logWarning("Unable to auth FinAccount: " + result, module);
return result;
}
// check for account being in bad standing somehow
if ("FNACT_NEGPENDREPL".equals(statusId) || "FNACT_MANFROZEN".equals(statusId) || "FNACT_CANCELLED".equals(statusId)) {
// refresh the finaccount
finAccount.refresh();
statusId = finAccount.getString("statusId");
if ("FNACT_NEGPENDREPL".equals(statusId) || "FNACT_MANFROZEN".equals(statusId) || "FNACT_CANCELLED".equals(statusId)) {
Map<String, Object> result = ServiceUtil.returnSuccess();
if ("FNACT_NEGPENDREPL".equals(statusId)) {
result.put("authMessage", UtilProperties.getMessage(resourceError,
"AccountingFinAccountNegative", locale));
} else if ("FNACT_MANFROZEN".equals(statusId)) {
result.put("authMessage", UtilProperties.getMessage(resourceError,
"AccountingFinAccountFrozen", locale));
} else if ("FNACT_CANCELLED".equals(statusId)) {
result.put("authMessage", UtilProperties.getMessage(resourceError,
"AccountingFinAccountCancelled", locale));
}
result.put("authResult", Boolean.FALSE);
result.put("processAmount", amount);
result.put("authFlag", "0");
result.put("authCode", "A");
result.put("authRefNum", "0");
Debug.logWarning("Unable to auth FinAccount: " + result, module);
return result;
}
}
// check the amount to authorize against the available balance of fin account, which includes active authorizations as well as transactions
BigDecimal availableBalance = finAccount.getBigDecimal("availableBalance");
if (availableBalance == null) {
availableBalance = FinAccountHelper.ZERO;
} else {
BigDecimal availableBalanceOriginal = availableBalance;
availableBalance = availableBalance.setScale(FinAccountHelper.decimals, FinAccountHelper.rounding);
if (availableBalance.compareTo(availableBalanceOriginal) != 0) {
Debug.logWarning("In finAccountPreAuth for finAccountId [" + finAccountId + "] availableBalance [" + availableBalanceOriginal + "] was different after rounding [" + availableBalance + "]; it should never have made it into the database this way, so check whatever put it there.", module);
}
}
Map<String, Object> result = ServiceUtil.returnSuccess();
String authMessage = null;
Boolean processResult;
String refNum;
// make sure to round and scale it to the same as availableBalance
amount = amount.setScale(FinAccountHelper.decimals, FinAccountHelper.rounding);
Debug.logInfo("Allow auth to negative: " + allowAuthToNegative + " :: available: " + availableBalance + " comp: " + minBalance + " = " + availableBalance.compareTo(minBalance) + " :: req: " + amount, module);
// check the available balance to see if we can auth this tx
if (("Y".equals(allowAuthToNegative) && availableBalance.compareTo(minBalance) > -1)
|| (availableBalance.compareTo(amount) > -1)) {
Timestamp thruDate;
if (finAccountSettings != null && finAccountSettings.getLong("authValidDays") != null) {
thruDate = UtilDateTime.getDayEnd(UtilDateTime.nowTimestamp(), finAccountSettings.getLong("authValidDays"));
} else {
thruDate = UtilDateTime.getDayEnd(UtilDateTime.nowTimestamp(), Long.valueOf(30)); // default 30 days for an auth
}
Map<String, Object> tmpResult = dispatcher.runSync("createFinAccountAuth", UtilMisc.<String, Object>toMap("finAccountId", finAccountId,
"amount", amount, "thruDate", thruDate, "userLogin", userLogin));
if (ServiceUtil.isError(tmpResult)) {
return tmpResult;
}
refNum = (String) tmpResult.get("finAccountAuthId");
processResult = Boolean.TRUE;
// refresh the account
finAccount.refresh();
} else {
Debug.logWarning("Attempted to authorize [" + amount + "] against a balance of only [" + availableBalance + "] for finAccountId [" + finAccountId + "]", module);
refNum = "0"; // a refNum is always required from authorization
authMessage = "Insufficient funds";
processResult = Boolean.FALSE;
}
result.put("processAmount", amount);
result.put("authMessage", authMessage);
result.put("authResult", processResult);
result.put("processAmount", amount);
result.put("authFlag", "1");
result.put("authCode", "A");
result.put("authRefNum", refNum);
Debug.logInfo("FinAccont Auth: " + result, module);
return result;
} catch (GenericEntityException ex) {
Debug.logError(ex, "Cannot authorize financial account", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountCannotBeAuthorized",
UtilMisc.toMap("errorString", ex.getMessage()), locale));
} catch (GenericServiceException ex) {
Debug.logError(ex, "Cannot authorize financial account", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountCannotBeAuthorized",
UtilMisc.toMap("errorString", ex.getMessage()), locale));
}
}
public static Map<String, Object> finAccountReleaseAuth(DispatchContext dctx, Map<String, Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
GenericValue userLogin = (GenericValue) context.get("userLogin");
GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
Locale locale = (Locale) context.get("locale");
String err = UtilProperties.getMessage(resourceError, "AccountingFinAccountCannotBeExpired", locale);
try {
// expire the related financial authorization transaction
GenericValue authTransaction = PaymentGatewayServices.getAuthTransaction(paymentPref);
if (authTransaction == null) {
return ServiceUtil.returnError(err + UtilProperties.getMessage(resourceError,
"AccountingFinAccountCannotFindAuthorization", locale));
}
Map<String, Object> input = UtilMisc.toMap("userLogin", userLogin, "finAccountAuthId", authTransaction.get("referenceNum"));
Map<String, Object> serviceResults = dispatcher.runSync("expireFinAccountAuth", input);
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("releaseRefNum", authTransaction.getString("referenceNum"));
result.put("releaseAmount", authTransaction.getBigDecimal("amount"));
result.put("releaseResult", Boolean.TRUE);
// if there's an error, don't release
if (ServiceUtil.isError(serviceResults)) {
return ServiceUtil.returnError(err + ServiceUtil.getErrorMessage(serviceResults));
}
return result;
} catch (GenericServiceException e) {
Debug.logError(e, e.getMessage(), module);
return ServiceUtil.returnError(err + e.getMessage());
}
}
public static Map<String, Object> finAccountCapture(DispatchContext dctx, Map<String, Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
Locale locale = (Locale) context.get("locale");
GenericValue orderPaymentPreference = (GenericValue) context.get("orderPaymentPreference");
GenericValue userLogin = (GenericValue) context.get("userLogin");
GenericValue authTrans = (GenericValue) context.get("authTrans");
BigDecimal amount = (BigDecimal) context.get("captureAmount");
String currency = (String) context.get("currency");
// get the authorization transaction
if (authTrans == null) {
authTrans = PaymentGatewayServices.getAuthTransaction(orderPaymentPreference);
}
if (authTrans == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountCannotCapture", locale));
}
// get the auth record
String finAccountAuthId = authTrans.getString("referenceNum");
GenericValue finAccountAuth;
try {
finAccountAuth = EntityQuery.use(delegator).from("FinAccountAuth").where("finAccountAuthId", finAccountAuthId).queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
Debug.logInfo("Financial account capture [" + finAccountAuth.get("finAccountId") + "] for the amount of $" +
amount + " Tx #" + finAccountAuth.get("finAccountAuthId"), module);
// get the financial account
GenericValue finAccount;
try {
finAccount = finAccountAuth.getRelatedOne("FinAccount", false);
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
// make sure authorization has not expired
Timestamp authExpiration = finAccountAuth.getTimestamp("thruDate");
if ((authExpiration != null) && (authExpiration.before(UtilDateTime.nowTimestamp()))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountAuthorizationExpired",
UtilMisc.toMap("paymentGatewayResponseId", authTrans.getString("paymentGatewayResponseId"),
"authExpiration", authExpiration), locale));
}
// make sure the fin account itself has not expired
if ((finAccount.getTimestamp("thruDate") != null) && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountExpired",
UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")), locale));
}
String finAccountId = finAccount.getString("finAccountId");
// need the product store ID & party ID
String orderId = orderPaymentPreference.getString("orderId");
String productStoreId = null;
String partyId = null;
if (orderId != null) {
OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
productStoreId = orh.getProductStoreId();
GenericValue billToParty = orh.getBillToParty();
if (billToParty != null) {
partyId = billToParty.getString("partyId");
}
}
// BIG NOTE: make sure the expireFinAccountAuth and finAccountWithdraw services are done in the SAME TRANSACTION
//(i.e. no require-new-transaction in either of them AND no running async)
// cancel the authorization before doing the withdraw to avoid problems with way negative available amount on account; should happen in same transaction to avoid conflict problems
Map<String, Object> releaseResult;
try {
releaseResult = dispatcher.runSync("expireFinAccountAuth", UtilMisc.<String, Object>toMap("userLogin", userLogin, "finAccountAuthId", finAccountAuthId));
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
if (ServiceUtil.isError(releaseResult)) {
return releaseResult;
}
// build the withdraw context
Map<String, Object> withdrawCtx = FastMap.newInstance();
withdrawCtx.put("finAccountId", finAccountId);
withdrawCtx.put("productStoreId", productStoreId);
withdrawCtx.put("currency", currency);
withdrawCtx.put("partyId", partyId);
withdrawCtx.put("orderId", orderId);
withdrawCtx.put("amount", amount);
withdrawCtx.put("reasonEnumId", "FATR_PURCHASE");
withdrawCtx.put("requireBalance", Boolean.FALSE); // for captures; if auth passed, allow
withdrawCtx.put("userLogin", userLogin);
// call the withdraw service
Map<String, Object> withdrawResp;
try {
withdrawResp = dispatcher.runSync("finAccountWithdraw", withdrawCtx);
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
if (ServiceUtil.isError(withdrawResp)) {
return withdrawResp;
}
// create the capture response
Map<String, Object> result = ServiceUtil.returnSuccess();
Boolean processResult = (Boolean) withdrawResp.get("processResult");
BigDecimal withdrawAmount = (BigDecimal) withdrawResp.get("amount");
String referenceNum = (String) withdrawResp.get("referenceNum");
result.put("captureResult", processResult);
result.put("captureRefNum", referenceNum);
result.put("captureCode", "C");
result.put("captureFlag", "1");
result.put("captureAmount", withdrawAmount);
return result;
}
public static Map<String, Object> finAccountRefund(DispatchContext dctx, Map<String, Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
Locale locale = (Locale) context.get("locale");
GenericValue orderPaymentPreference = (GenericValue) context.get("orderPaymentPreference");
GenericValue userLogin = (GenericValue) context.get("userLogin");
BigDecimal amount = (BigDecimal) context.get("refundAmount");
String currency = (String) context.get("currency");
String finAccountId = (String) context.get("finAccountId");
String productStoreId = null;
String partyId = null;
String orderId = null;
if (orderPaymentPreference != null) {
orderId = orderPaymentPreference.getString("orderId");
if (orderId != null) {
OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
productStoreId = orh.getProductStoreId();
GenericValue billToParty = orh.getBillToParty();
if (billToParty != null) {
partyId = billToParty.getString("partyId");
}
}
if (finAccountId == null) {
finAccountId = orderPaymentPreference.getString("finAccountId");
}
}
if (finAccountId == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", ""), locale));
}
// call the deposit service
Map<String, Object> depositCtx = FastMap.newInstance();
depositCtx.put("finAccountId", finAccountId);
depositCtx.put("productStoreId", productStoreId);
depositCtx.put("isRefund", Boolean.TRUE);
depositCtx.put("currency", currency);
depositCtx.put("partyId", partyId);
depositCtx.put("orderId", orderId);
depositCtx.put("amount", amount);
depositCtx.put("reasonEnumId", "FATR_REFUND");
depositCtx.put("userLogin", userLogin);
Map<String, Object> depositResp;
try {
depositResp = dispatcher.runSync("finAccountDeposit", depositCtx);
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
if (ServiceUtil.isError(depositResp)) {
return depositResp;
}
// create the refund response
Map<String, Object> result = ServiceUtil.returnSuccess();
Boolean processResult = (Boolean) depositResp.get("processResult");
BigDecimal depositAmount = (BigDecimal) depositResp.get("amount");
String referenceNum = (String) depositResp.get("referenceNum");
result.put("refundResult", processResult);
result.put("refundRefNum", referenceNum);
result.put("refundCode", "R");
result.put("refundFlag", "1");
result.put("refundAmount", depositAmount);
return result;
}
// base account transaction services
public static Map<String, Object> finAccountWithdraw(DispatchContext dctx, Map<String, Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
Locale locale = (Locale) context.get("locale");
GenericValue userLogin = (GenericValue) context.get("userLogin");
String productStoreId = (String) context.get("productStoreId");
String finAccountId = (String) context.get("finAccountId");
String orderItemSeqId = (String) context.get("orderItemSeqId");
String reasonEnumId = (String) context.get("reasonEnumId");
String orderId = (String) context.get("orderId");
Boolean requireBalance = (Boolean) context.get("requireBalance");
BigDecimal amount = (BigDecimal) context.get("amount");
if (requireBalance == null) requireBalance = Boolean.TRUE;
final String WITHDRAWAL = "WITHDRAWAL";
String partyId = (String) context.get("partyId");
if (UtilValidate.isEmpty(partyId)) {
partyId = "_NA_";
}
String currencyUom = (String) context.get("currency");
if (UtilValidate.isEmpty(currencyUom)) {
currencyUom = EntityUtilProperties.getPropertyValue("general.properties", "currency.uom.id.default", "USD", delegator);
}
// validate the amount
if (amount.compareTo(BigDecimal.ZERO) < 0) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountMustBePositive", locale));
}
GenericValue finAccount;
try {
finAccount = EntityQuery.use(delegator).from("FinAccount").where("finAccountId", finAccountId).queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
// verify we have a financial account
if (finAccount == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", ""), locale));
}
// make sure the fin account itself has not expired
if ((finAccount.getTimestamp("thruDate") != null) && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountExpired",
UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")), locale));
}
// check the actual balance (excluding authorized amounts) and create the transaction if it is sufficient
BigDecimal previousBalance = finAccount.getBigDecimal("actualBalance");
if (previousBalance == null) {
previousBalance = FinAccountHelper.ZERO;
}
BigDecimal balance;
String refNum;
Boolean procResult;
if (requireBalance && previousBalance.compareTo(amount) < 0) {
procResult = Boolean.FALSE;
balance = previousBalance;
refNum = "N/A";
} else {
try {
refNum = FinAccountPaymentServices.createFinAcctPaymentTransaction(delegator, dispatcher, userLogin, amount,
productStoreId, partyId, orderId, orderItemSeqId, currencyUom, WITHDRAWAL, finAccountId, reasonEnumId);
finAccount.refresh();
balance = finAccount.getBigDecimal("actualBalance");
procResult = Boolean.TRUE;
} catch (GeneralException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
}
// make sure balance is not null
if (balance == null) {
balance = FinAccountHelper.ZERO;
}
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("previousBalance", previousBalance);
result.put("balance", balance);
result.put("amount", amount);
result.put("processResult", procResult);
result.put("referenceNum", refNum);
return result;
}
// base deposit service
public static Map<String, Object> finAccountDeposit(DispatchContext dctx, Map<String, Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
Locale locale = (Locale) context.get("locale");
GenericValue userLogin = (GenericValue) context.get("userLogin");
String productStoreId = (String) context.get("productStoreId");
String finAccountId = (String) context.get("finAccountId");
String orderItemSeqId = (String) context.get("orderItemSeqId");
String reasonEnumId = (String) context.get("reasonEnumId");
String orderId = (String) context.get("orderId");
Boolean isRefund = (Boolean) context.get("isRefund");
BigDecimal amount = (BigDecimal) context.get("amount");
final String DEPOSIT = isRefund == null || !isRefund ? "DEPOSIT" : "ADJUSTMENT";
String partyId = (String) context.get("partyId");
if (UtilValidate.isEmpty(partyId)) {
partyId = "_NA_";
}
String currencyUom = (String) context.get("currency");
if (UtilValidate.isEmpty(currencyUom)) {
currencyUom = EntityUtilProperties.getPropertyValue("general.properties", "currency.uom.id.default", "USD", delegator);
}
GenericValue finAccount;
try {
finAccount = EntityQuery.use(delegator).from("FinAccount").where("finAccountId", finAccountId).queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", finAccountId), locale));
}
// verify we have a financial account
if (finAccount == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", ""), locale));
}
// make sure the fin account itself has not expired
if ((finAccount.getTimestamp("thruDate") != null) && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountExpired",
UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")), locale));
}
Debug.logInfo("Deposit into financial account #" + finAccountId + " [" + amount + "]", module);
// get the previous balance
BigDecimal previousBalance = finAccount.getBigDecimal("actualBalance");
if (previousBalance == null) {
previousBalance = FinAccountHelper.ZERO;
}
// create the transaction
BigDecimal actualBalance;
String refNum;
try {
refNum = FinAccountPaymentServices.createFinAcctPaymentTransaction(delegator, dispatcher, userLogin, amount,
productStoreId, partyId, orderId, orderItemSeqId, currencyUom, DEPOSIT, finAccountId, reasonEnumId);
finAccount.refresh();
actualBalance = finAccount.getBigDecimal("actualBalance");
} catch (GeneralException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
// make sure balance is not null
if (actualBalance == null) {
actualBalance = FinAccountHelper.ZERO;
} else {
if (actualBalance.compareTo(BigDecimal.ZERO) < 0) {
// balance went below zero, set negative pending replenishment status so that no more auths or captures will go through until it is replenished
try {
Map<String, Object> rollbackCtx = UtilMisc.toMap("userLogin", userLogin, "finAccountId", finAccountId, "statusId", "FNACT_NEGPENDREPL");
dispatcher.addRollbackService("updateFinAccount", rollbackCtx, true);
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
}
}
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("previousBalance", previousBalance);
result.put("balance", actualBalance);
result.put("amount", amount);
result.put("processResult", Boolean.TRUE);
result.put("referenceNum", refNum);
return result;
}
// auto-replenish service (deposit)
public static Map<String, Object> finAccountReplenish(DispatchContext dctx, Map<String, Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
Locale locale = (Locale) context.get("locale");
GenericValue userLogin = (GenericValue) context.get("userLogin");
String productStoreId = (String) context.get("productStoreId");
String finAccountId = (String) context.get("finAccountId");
// lookup the FinAccount
GenericValue finAccount;
try {
finAccount = EntityQuery.use(delegator).from("FinAccount").where("finAccountId", finAccountId).queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
if (finAccount == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", finAccountId), locale));
}
String currency = finAccount.getString("currencyUomId");
String statusId = finAccount.getString("statusId");
// look up the type -- determine auto-replenish is active
GenericValue finAccountType;
try {
finAccountType = finAccount.getRelatedOne("FinAccountType", false);
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
String replenishEnumId = finAccountType.getString("replenishEnumId");
if (!"FARP_AUTOMATIC".equals(replenishEnumId)) {
// type does not support auto-replenish
return ServiceUtil.returnSuccess();
}
// attempt to lookup the product store from a previous deposit
if (productStoreId == null) {
productStoreId = getLastProductStoreId(delegator, finAccountId);
if (productStoreId == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountCannotBeReplenish", locale));
}
}
// get the product store settings
GenericValue finAccountSettings;
Map<String, Object> psfasFindMap = UtilMisc.<String, Object>toMap("productStoreId", productStoreId, "finAccountTypeId", finAccount.getString("finAccountTypeId"));
try {
finAccountSettings = EntityQuery.use(delegator).from("ProductStoreFinActSetting").where(psfasFindMap).cache().queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
if (finAccountSettings == null) {
Debug.logWarning("finAccountReplenish Warning: not replenishing FinAccount [" + finAccountId + "] because no ProductStoreFinActSetting record found for: " + psfasFindMap, module);
// no settings; don't replenish
return ServiceUtil.returnSuccess();
}
BigDecimal replenishThreshold = finAccountSettings.getBigDecimal("replenishThreshold");
if (replenishThreshold == null) {
Debug.logWarning("finAccountReplenish Warning: not replenishing FinAccount [" + finAccountId + "] because ProductStoreFinActSetting.replenishThreshold field was null for: " + psfasFindMap, module);
return ServiceUtil.returnSuccess();
}
BigDecimal replenishLevel = finAccount.getBigDecimal("replenishLevel");
if (replenishLevel == null || replenishLevel.compareTo(BigDecimal.ZERO) == 0) {
Debug.logWarning("finAccountReplenish Warning: not replenishing FinAccount [" + finAccountId + "] because FinAccount.replenishLevel field was null or 0", module);
// no replenish level set; this account goes not support auto-replenish
return ServiceUtil.returnSuccess();
}
// get the current balance
BigDecimal balance = finAccount.getBigDecimal("actualBalance");
// see if we are within the threshold for replenishment
if (balance.compareTo(replenishThreshold) > -1) {
Debug.logInfo("finAccountReplenish Info: Not replenishing FinAccount [" + finAccountId + "] because balance [" + balance + "] is greater than the replenishThreshold [" + replenishThreshold + "]", module);
// not ready
return ServiceUtil.returnSuccess();
}
// configure rollback service to set status to Negative Pending Replenishment
if ("FNACT_NEGPENDREPL".equals(statusId)) {
try {
Map<String, Object> rollbackCtx = UtilMisc.toMap("userLogin", userLogin, "finAccountId", finAccountId, "statusId", "FNACT_NEGPENDREPL");
dispatcher.addRollbackService("updateFinAccount", rollbackCtx, true);
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
}
String replenishMethod = finAccountSettings.getString("replenishMethodEnumId");
BigDecimal depositAmount;
if (replenishMethod == null || "FARP_TOP_OFF".equals(replenishMethod)) {
//the deposit is level - balance (500 - (-10) = 510 || 500 - (10) = 490)
depositAmount = replenishLevel.subtract(balance);
} else if ("FARP_REPLENISH_LEVEL".equals(replenishMethod)) {
//the deposit is replenish-level itself
depositAmount = replenishLevel;
} else {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountUnknownReplenishMethod", locale));
}
// get the owner party
String ownerPartyId = finAccount.getString("ownerPartyId");
if (ownerPartyId == null) {
// no owner cannot replenish; (not fatal, just not supported by this account)
Debug.logWarning("finAccountReplenish Warning: No owner attached to financial account [" + finAccountId + "] cannot auto-replenish", module);
return ServiceUtil.returnSuccess();
}
// get the payment method to use to replenish
String paymentMethodId = finAccount.getString("replenishPaymentId");
if (paymentMethodId == null) {
Debug.logWarning("finAccountReplenish Warning: No payment method (replenishPaymentId) attached to financial account [" + finAccountId + "] cannot auto-replenish", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountNoPaymentMethodAssociatedWithReplenishAccount", locale));
}
GenericValue paymentMethod;
try {
paymentMethod = EntityQuery.use(delegator).from("PaymentMethod").where("paymentMethodId", paymentMethodId).queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
if (paymentMethod == null) {
// no payment methods on file; cannot replenish
Debug.logWarning("finAccountReplenish Warning: No payment method found for ID [" + paymentMethodId + "] for party [" + ownerPartyId + "] cannot auto-replenish", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountNoPaymentMethodAssociatedWithReplenishAccount", locale));
}
// hit the payment method for the amount to replenish
Map<String, BigDecimal> orderItemMap = UtilMisc.toMap("Auto-Replenishment FA #" + finAccountId, depositAmount);
Map<String, Object> replOrderCtx = FastMap.newInstance();
replOrderCtx.put("productStoreId", productStoreId);
replOrderCtx.put("paymentMethodId", paymentMethod.getString("paymentMethodId"));
replOrderCtx.put("currency", currency);
replOrderCtx.put("partyId", ownerPartyId);
replOrderCtx.put("itemMap", orderItemMap);
replOrderCtx.put("userLogin", userLogin);
Map<String, Object> replResp;
try {
replResp = dispatcher.runSync("createSimpleNonProductSalesOrder", replOrderCtx);
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
if (ServiceUtil.isError(replResp)) {
return replResp;
}
String orderId = (String) replResp.get("orderId");
// create the deposit
Map<String, Object> depositCtx = FastMap.newInstance();
depositCtx.put("productStoreId", productStoreId);
depositCtx.put("finAccountId", finAccountId);
depositCtx.put("currency", currency);
depositCtx.put("partyId", ownerPartyId);
depositCtx.put("orderId", orderId);
depositCtx.put("orderItemSeqId", "00001"); // always one item on a replish order
depositCtx.put("amount", depositAmount);
depositCtx.put("reasonEnumId", "FATR_REPLENISH");
depositCtx.put("userLogin", userLogin);
try {
Map<String, Object> depositResp = dispatcher.runSync("finAccountDeposit", depositCtx);
if (ServiceUtil.isError(depositResp)) {
return depositResp;
}
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
// say we are in good standing again
if ("FNACT_NEGPENDREPL".equals(statusId)) {
try {
Map<String, Object> ufaResp = dispatcher.runSync("updateFinAccount", UtilMisc.<String, Object>toMap("finAccountId", finAccountId, "statusId", "FNACT_ACTIVE", "userLogin", userLogin));
if (ServiceUtil.isError(ufaResp)) {
return ufaResp;
}
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
}
return ServiceUtil.returnSuccess();
}
private static String getLastProductStoreId(Delegator delegator, String finAccountId) {
GenericValue trans = null;
try {
trans = EntityQuery.use(delegator).from("FinAccountTrans")
.where(
EntityCondition.makeCondition("finAccountTransTypeId", EntityOperator.EQUALS, "DEPOSIT"),
EntityCondition.makeCondition("finAccountId", EntityOperator.EQUALS, finAccountId),
EntityCondition.makeCondition("orderId", EntityOperator.NOT_EQUAL, null)
).orderBy("-transactionDate").queryFirst();
} catch (GenericEntityException e) {
Debug.logError(e, module);
}
if (trans != null) {
String orderId = trans.getString("orderId");
OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
return orh.getProductStoreId();
}
// none found; pick one from our set stores
try {
GenericValue store = EntityQuery.use(delegator).from("ProductStore").orderBy("productStoreId").queryFirst();
if (store != null)
return store.getString("productStoreId");
} catch (GenericEntityException e) {
Debug.logError(e, module);
}
return null;
}
private static String createFinAcctPaymentTransaction(Delegator delegator, LocalDispatcher dispatcher, GenericValue userLogin, BigDecimal amount,
String productStoreId, String partyId, String orderId, String orderItemSeqId, String currencyUom, String txType, String finAccountId, String reasonEnumId) throws GeneralException {
final String coParty = ProductStoreWorker.getProductStorePayToPartyId(productStoreId, delegator);
final String paymentMethodType = "FIN_ACCOUNT";
if (UtilValidate.isEmpty(partyId)) {
partyId = "_NA_";
}
String paymentType;
String partyIdFrom;
String partyIdTo;
BigDecimal paymentAmount;
// determine the payment type and which direction the parties should go
if ("DEPOSIT".equals(txType)) {
paymentType = "RECEIPT";
partyIdFrom = partyId;
partyIdTo = coParty;
paymentAmount = amount;
} else if ("WITHDRAWAL".equals(txType)) {
paymentType = "DISBURSEMENT";
partyIdFrom = coParty;
partyIdTo = partyId;
paymentAmount = amount;
} else if ("ADJUSTMENT".equals(txType)) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
paymentType = "DISBURSEMENT";
partyIdFrom = coParty;
partyIdTo = partyId;
paymentAmount = amount.negate(); // must be positive
} else {
paymentType = "RECEIPT";
partyIdFrom = partyId;
partyIdTo = coParty;
paymentAmount = amount;
}
} else {
throw new GeneralException("Unable to create financial account transaction!");
}
// payment amount should always be positive; adjustments may
// create the payment for the transaction
Map<String, Object> paymentCtx = UtilMisc.<String, Object>toMap("paymentTypeId", paymentType);
paymentCtx.put("paymentMethodTypeId", paymentMethodType);
paymentCtx.put("partyIdTo", partyIdTo);
paymentCtx.put("partyIdFrom", partyIdFrom);
paymentCtx.put("statusId", "PMNT_RECEIVED");
paymentCtx.put("currencyUomId", currencyUom);
paymentCtx.put("amount", paymentAmount);
paymentCtx.put("userLogin", userLogin);
paymentCtx.put("paymentRefNum", Long.toString(UtilDateTime.nowTimestamp().getTime()));
String paymentId;
Map<String, Object> payResult;
try {
payResult = dispatcher.runSync("createPayment", paymentCtx);
} catch (GenericServiceException e) {
throw new GeneralException(e);
}
if (payResult == null) {
throw new GeneralException("Unknow error in creating financial account transaction!");
}
if (ServiceUtil.isError(payResult)) {
throw new GeneralException(ServiceUtil.getErrorMessage(payResult));
}
paymentId = (String) payResult.get("paymentId");
// create the initial transaction
Map<String, Object> transCtx = UtilMisc.<String, Object>toMap("finAccountTransTypeId", txType);
transCtx.put("finAccountId", finAccountId);
transCtx.put("partyId", partyId);
transCtx.put("orderId", orderId);
transCtx.put("orderItemSeqId", orderItemSeqId);
transCtx.put("reasonEnumId", reasonEnumId);
transCtx.put("amount", amount);
transCtx.put("userLogin", userLogin);
transCtx.put("paymentId", paymentId);
Map<String, Object> transResult;
try {
transResult = dispatcher.runSync("createFinAccountTrans", transCtx);
} catch (GenericServiceException e) {
throw new GeneralException(e);
}
if (transResult == null) {
throw new GeneralException("Unknown error in creating financial account transaction!");
}
if (ServiceUtil.isError(transResult)) {
throw new GeneralException(ServiceUtil.getErrorMessage(transResult));
}
return (String) transResult.get("finAccountTransId");
}
}