in applications/order/src/main/java/org/apache/ofbiz/order/order/OrderReturnServices.java [1171:1541]
public static Map<String, Object> processRefundReturn(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
String returnId = (String) context.get("returnId");
String returnTypeId = (String) context.get("returnTypeId");
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
GenericValue returnHeader = null;
List<GenericValue> returnItems = null;
try {
returnHeader = EntityQuery.use(delegator).from("ReturnHeader").where("returnId", returnId).queryOne();
if (returnHeader != null) {
returnItems = returnHeader.getRelated("ReturnItem", UtilMisc.toMap("returnTypeId", returnTypeId), null, false);
}
} catch (GenericEntityException e) {
Debug.logError(e, "Problems looking up return information", MODULE);
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderErrorGettingReturnHeaderItemInformation", locale));
}
BigDecimal adjustments = getReturnAdjustmentTotal(delegator, UtilMisc.toMap("returnId", returnId, "returnTypeId", returnTypeId));
if (returnHeader != null && (UtilValidate.isNotEmpty(returnItems) || adjustments.compareTo(ZERO) > 0)) {
Map<String, List<GenericValue>> itemsByOrder = new HashMap<>();
Map<String, BigDecimal> totalByOrder = new HashMap<>();
// make sure total refunds on a return don't exceed amount of returned orders
Map<String, Object> serviceResult = null;
try {
serviceResult = dispatcher.runSync("checkPaymentAmountForRefund", UtilMisc.toMap("returnId", returnId));
} catch (GenericServiceException e) {
Debug.logError(e, "Problem running the checkPaymentAmountForRefund service", MODULE);
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderProblemsWithCheckPaymentAmountForRefund", locale));
}
if (ServiceUtil.isError(serviceResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(serviceResult));
}
groupReturnItemsByOrder(returnItems, itemsByOrder, totalByOrder, delegator, returnId, returnTypeId);
// process each one by order
for (Map.Entry<String, List<GenericValue>> entry : itemsByOrder.entrySet()) {
String orderId = entry.getKey();
List<GenericValue> items = entry.getValue();
BigDecimal orderTotal = totalByOrder.get(orderId);
// get order header & payment prefs
GenericValue orderHeader = null;
List<GenericValue> orderPayPrefs = null;
try {
orderHeader = EntityQuery.use(delegator).from("OrderHeader").where("orderId", orderId).queryOne();
// sort these desending by maxAmount
orderPayPrefs = orderHeader.getRelated("OrderPaymentPreference", null, UtilMisc.toList("-maxAmount"), false);
List<EntityExpr> exprs = UtilMisc.toList(EntityCondition.makeCondition("statusId", EntityOperator.EQUALS, "PAYMENT_SETTLED"),
EntityCondition.makeCondition("statusId", EntityOperator.EQUALS, "PAYMENT_RECEIVED"));
orderPayPrefs = EntityUtil.filterByOr(orderPayPrefs, exprs);
// Check for replacement order
if (UtilValidate.isEmpty(orderPayPrefs)) {
GenericValue orderItemAssoc = EntityQuery.use(delegator).from("OrderItemAssoc")
.where("toOrderId", orderId, "orderItemAssocTypeId", "REPLACEMENT")
.queryFirst();
if (orderItemAssoc != null) {
String originalOrderId = orderItemAssoc.getString("orderId");
orderHeader = EntityQuery.use(delegator).from("OrderHeader").where("orderId", originalOrderId).queryOne();
orderPayPrefs = orderHeader.getRelated("OrderPaymentPreference", null, UtilMisc.toList("-maxAmount"), false);
orderPayPrefs = EntityUtil.filterByOr(orderPayPrefs, exprs);
orderId = originalOrderId;
}
}
} catch (GenericEntityException e) {
Debug.logError(e, "Cannot get Order details for #" + orderId, MODULE);
continue;
}
OrderReadHelper orderReadHelper = new OrderReadHelper(delegator, orderId);
// Determine the fall-through refund paymentMethodId from the PartyAcctgPreference of the owner of the productStore for the order
GenericValue productStore = orderReadHelper.getProductStore();
if (UtilValidate.isEmpty(productStore) || UtilValidate.isEmpty(productStore.get("payToPartyId"))) {
Debug.logError("No payToPartyId found for orderId " + orderId, MODULE);
} else {
GenericValue orgAcctgPref = null;
Map<String, Object> acctgPreferencesResult = null;
try {
acctgPreferencesResult = dispatcher.runSync("getPartyAccountingPreferences", UtilMisc.toMap("organizationPartyId",
productStore.get("payToPartyId"), "userLogin", userLogin));
if (ServiceUtil.isError(acctgPreferencesResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(acctgPreferencesResult));
}
} catch (GenericServiceException e) {
Debug.logError(e, "Error retrieving PartyAcctgPreference for partyId " + productStore.get("payToPartyId"), MODULE);
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderProblemsWithGetPartyAcctgPreferences", locale));
}
orgAcctgPref = (GenericValue) acctgPreferencesResult.get("partyAccountingPreference");
if (orgAcctgPref != null) {
try {
orgAcctgPref.getRelatedOne("PaymentMethod", false);
} catch (GenericEntityException e) {
Debug.logError("Error retrieving related refundPaymentMethod from PartyAcctgPreference for partyId "
+ productStore.get("payToPartyId"), MODULE);
}
}
}
// now; for all timestamps
Timestamp now = UtilDateTime.nowTimestamp();
// Assemble a map of orderPaymentPreferenceId -> list of maps of (OPP and availableAmountForRefunding)
// where availableAmountForRefunding = receivedAmount - alreadyRefundedAmount
// We break the OPPs down this way because we need to process the refunds to payment methods in a particular order
Map<String, BigDecimal> receivedPaymentTotalsByPaymentMethod = orderReadHelper.getReceivedPaymentTotalsByPaymentMethod();
Map<String, BigDecimal> refundedTotalsByPaymentMethod = orderReadHelper.getReturnedTotalsByPaymentMethod();
// getOrderPaymentPreferenceTotalByType has been called because getReceivedPaymentTotalsByPaymentMethod does not
// return payments captured from Billing Account.This is because when payment is captured from Billing Account
// then no entry is maintained in Payment entity.
BigDecimal receivedPaymentTotalsByBillingAccount = orderReadHelper.getOrderPaymentPreferenceTotalByType("EXT_BILLACT");
/*
* Go through the OrderPaymentPreferences and determine how much remains to be refunded for each.
* Then group these refund amounts and orderPaymentPreferences by paymentMethodTypeId. That is,
* the intent is to get the refundable amounts per orderPaymentPreference, grouped by payment method type.
*/
Map<String, List<Map<String, Object>>> prefSplitMap = new HashMap<>();
for (GenericValue orderPayPref : orderPayPrefs) {
String paymentMethodTypeId = orderPayPref.getString("paymentMethodTypeId");
String orderPayPrefKey = orderPayPref.getString("paymentMethodId") != null ? orderPayPref.getString("paymentMethodId")
: orderPayPref.getString("paymentMethodTypeId");
// See how much we can refund to the payment method
BigDecimal orderPayPrefReceivedTotal = ZERO;
if (receivedPaymentTotalsByPaymentMethod.containsKey(orderPayPrefKey)) {
orderPayPrefReceivedTotal = orderPayPrefReceivedTotal.add(receivedPaymentTotalsByPaymentMethod.get(orderPayPrefKey))
.setScale(DECIMALS, ROUNDING);
}
if (receivedPaymentTotalsByBillingAccount != null) {
orderPayPrefReceivedTotal = orderPayPrefReceivedTotal.add(receivedPaymentTotalsByBillingAccount);
}
BigDecimal orderPayPrefRefundedTotal = ZERO;
if (refundedTotalsByPaymentMethod.containsKey(orderPayPrefKey)) {
orderPayPrefRefundedTotal = orderPayPrefRefundedTotal.add(refundedTotalsByPaymentMethod.get(orderPayPrefKey))
.setScale(DECIMALS, ROUNDING);
}
BigDecimal orderPayPrefAvailableTotal = orderPayPrefReceivedTotal.subtract(orderPayPrefRefundedTotal);
// add the refundable amount and orderPaymentPreference to the paymentMethodTypeId map
if (orderPayPrefAvailableTotal.compareTo(ZERO) > 0) {
Map<String, Object> orderPayPrefDetails = new HashMap<>();
orderPayPrefDetails.put("orderPaymentPreference", orderPayPref);
orderPayPrefDetails.put("availableTotal", orderPayPrefAvailableTotal);
if (prefSplitMap.containsKey(paymentMethodTypeId)) {
List<Map<String, Object>> paymentMethodTypeIds = prefSplitMap.get(paymentMethodTypeId);
paymentMethodTypeIds.add(orderPayPrefDetails);
} else {
prefSplitMap.put(paymentMethodTypeId, UtilMisc.toList(orderPayPrefDetails));
}
}
}
// Keep a decreasing total of the amount remaining to refund
BigDecimal amountLeftToRefund = orderTotal.setScale(DECIMALS, ROUNDING);
// This can be extended to support additional electronic types
List<String> electronicTypes = UtilMisc.<String>toList("CREDIT_CARD", "EFT_ACCOUNT", "FIN_ACCOUNT", "GIFT_CARD");
// Figure out if EXT_PAYPAL should be considered as an electronic type
if (productStore != null) {
ExpressCheckoutEvents.CheckoutType payPalType = ExpressCheckoutEvents.determineCheckoutType(delegator,
productStore.getString("productStoreId"));
if (!payPalType.equals(ExpressCheckoutEvents.CheckoutType.NONE)) {
electronicTypes.add("EXT_PAYPAL");
}
}
// This defines the ordered part of the sequence of refund processing
List<String> orderedRefundPaymentMethodTypes = new LinkedList<>();
orderedRefundPaymentMethodTypes.add("EXT_BILLACT");
orderedRefundPaymentMethodTypes.add("FIN_ACCOUNT");
orderedRefundPaymentMethodTypes.add("GIFT_CARD");
orderedRefundPaymentMethodTypes.add("CREDIT_CARD");
orderedRefundPaymentMethodTypes.add("EFT_ACCOUNT");
// Add all the other paymentMethodTypes, in no particular order
List<GenericValue> otherPaymentMethodTypes;
try {
otherPaymentMethodTypes = EntityQuery.use(delegator).from("PaymentMethodType")
.where(EntityCondition.makeCondition("paymentMethodTypeId", EntityOperator.NOT_IN, orderedRefundPaymentMethodTypes))
.cache(true).queryList();
} catch (GenericEntityException e) {
Debug.logError(e, "Cannot get PaymentMethodTypes", MODULE);
return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
"OrderOrderPaymentPreferencesCannotGetPaymentMethodTypes",
UtilMisc.toMap("errorString", e.toString()), locale));
}
List<String> fieldList = EntityUtil.getFieldListFromEntityList(otherPaymentMethodTypes, "paymentMethodTypeId", true);
orderedRefundPaymentMethodTypes.addAll(fieldList);
// Iterate through the specified sequence of paymentMethodTypes, refunding to the correct OrderPaymentPreferences
// as long as there's a positive amount remaining to refund
Iterator<String> orpmtit = orderedRefundPaymentMethodTypes.iterator();
while (orpmtit.hasNext() && amountLeftToRefund.compareTo(ZERO) == 1) {
String paymentMethodTypeId = orpmtit.next();
if (prefSplitMap.containsKey(paymentMethodTypeId)) {
List<Map<String, Object>> paymentMethodDetails = prefSplitMap.get(paymentMethodTypeId);
// Iterate through the OrderPaymentPreferences of this type
Iterator<Map<String, Object>> pmtppit = paymentMethodDetails.iterator();
while (pmtppit.hasNext() && amountLeftToRefund.compareTo(ZERO) == 1) {
Map<String, Object> orderPaymentPrefDetails = pmtppit.next();
GenericValue orderPaymentPreference = (GenericValue) orderPaymentPrefDetails.get("orderPaymentPreference");
BigDecimal orderPaymentPreferenceAvailable = (BigDecimal) orderPaymentPrefDetails.get("availableTotal");
GenericValue refundOrderPaymentPreference = null;
// Refund up to the maxAmount for the paymentPref, or whatever is left to refund if that's less than the maxAmount
BigDecimal amountToRefund = orderPaymentPreferenceAvailable.min(amountLeftToRefund);
// The amount actually refunded for the paymentPref, default to requested amount
BigDecimal amountRefunded = amountToRefund;
String paymentId = null;
String returnItemStatusId = "RETURN_COMPLETED"; // generally, the return item will be considered complete after this
// Call the refund service to refund the payment
if (electronicTypes.contains(paymentMethodTypeId)) {
try {
Map<String, Object> serviceContext = UtilMisc.toMap("orderId", orderId, "userLogin", context.get("userLogin"));
serviceContext.put("paymentMethodId", orderPaymentPreference.getString("paymentMethodId"));
serviceContext.put("paymentMethodTypeId", orderPaymentPreference.getString("paymentMethodTypeId"));
serviceContext.put("statusId", orderPaymentPreference.getString("statusId"));
serviceContext.put("maxAmount", amountToRefund.setScale(DECIMALS, ROUNDING));
String orderPaymentPreferenceNewId = null;
Map<String, Object> result = dispatcher.runSync("createOrderPaymentPreference", serviceContext);
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(result));
}
orderPaymentPreferenceNewId = (String) result.get("orderPaymentPreferenceId");
try {
refundOrderPaymentPreference = EntityQuery.use(delegator).from("OrderPaymentPreference")
.where("orderPaymentPreferenceId", orderPaymentPreferenceNewId).queryOne();
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR, "OrderProblemsWithTheRefundSeeLogs",
locale));
}
serviceResult = dispatcher.runSync("refundPayment", UtilMisc.<String, Object>toMap("orderPaymentPreference",
refundOrderPaymentPreference, "refundAmount", amountToRefund.setScale(DECIMALS, ROUNDING), "userLogin",
userLogin));
if (ServiceUtil.isError(serviceResult) || ServiceUtil.isFailure(serviceResult)) {
Debug.logError("Error in refund payment: " + ServiceUtil.getErrorMessage(serviceResult), MODULE);
continue;
}
// for electronic types such as CREDIT_CARD and EFT_ACCOUNT, use refundPayment service
paymentId = (String) serviceResult.get("paymentId");
amountRefunded = (BigDecimal) serviceResult.get("refundAmount");
} catch (GenericServiceException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR, "OrderProblemsWithTheRefundSeeLogs", locale));
}
} else if ("EXT_BILLACT".equals(paymentMethodTypeId)) {
try {
// for Billing Account refunds
serviceResult = dispatcher.runSync("refundBillingAccountPayment",
UtilMisc.<String, Object>toMap("orderPaymentPreference", orderPaymentPreference, "refundAmount",
amountToRefund.setScale(DECIMALS, ROUNDING), "userLogin", userLogin));
if (ServiceUtil.isError(serviceResult) || ServiceUtil.isFailure(serviceResult)) {
Debug.logError("Error in refund payment: " + ServiceUtil.getErrorMessage(serviceResult), MODULE);
continue;
}
paymentId = (String) serviceResult.get("paymentId");
} catch (GenericServiceException e) {
Debug.logError(e, "Problem running the refundPayment service", MODULE);
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderProblemsWithTheRefundSeeLogs", locale));
}
} else {
// handle manual refunds
try {
Map<String, Object> input = UtilMisc.<String, Object>toMap("userLogin", userLogin, "amount", amountLeftToRefund,
"statusId", "PMNT_NOT_PAID");
input.put("partyIdTo", returnHeader.get("fromPartyId"));
input.put("partyIdFrom", returnHeader.get("toPartyId"));
input.put("paymentTypeId", "CUSTOMER_REFUND");
input.put("paymentMethodId", orderPaymentPreference.get("paymentMethodId"));
input.put("paymentMethodTypeId", orderPaymentPreference.get("paymentMethodTypeId"));
input.put("paymentPreferenceId", orderPaymentPreference.get("orderPaymentPreferenceId"));
serviceResult = dispatcher.runSync("createPayment", input);
if (ServiceUtil.isError(serviceResult) || ServiceUtil.isFailure(serviceResult)) {
Debug.logError("Error in refund payment: " + ServiceUtil.getErrorMessage(serviceResult), MODULE);
continue;
}
paymentId = (String) serviceResult.get("paymentId");
returnItemStatusId = "RETURN_MAN_REFUND"; // however, in this case we should flag it as a manual refund
} catch (GenericServiceException e) {
return ServiceUtil.returnError(e.getMessage());
}
}
// Fill out the data for the new ReturnItemResponse
Map<String, Object> response = new HashMap<>();
if (refundOrderPaymentPreference != null) {
response.put("orderPaymentPreferenceId", refundOrderPaymentPreference.getString("orderPaymentPreferenceId"));
} else {
response.put("orderPaymentPreferenceId", orderPaymentPreference.getString("orderPaymentPreferenceId"));
}
response.put("responseAmount", amountRefunded.setScale(DECIMALS, ROUNDING));
response.put("responseDate", now);
response.put("userLogin", userLogin);
response.put("paymentId", paymentId);
if ("EXT_BILLACT".equals(paymentMethodTypeId)) {
response.put("billingAccountId", orderReadHelper.getBillingAccount().getString("billingAccountId"));
}
Map<String, Object> serviceResults = null;
try {
serviceResults = dispatcher.runSync("createReturnItemResponse", response);
if (ServiceUtil.isError(serviceResults)) {
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderProblemsCreatingReturnItemResponseEntity", locale), null, null, serviceResults);
}
} catch (GenericServiceException e) {
Debug.logError(e, "Problems creating new ReturnItemResponse entity", MODULE);
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderProblemsCreatingReturnItemResponseEntity", locale));
}
String responseId = (String) serviceResults.get("returnItemResponseId");
// Set the response on each item
for (GenericValue item : items) {
Map<String, Object> returnItemMap = UtilMisc.<String, Object>toMap("returnItemResponseId", responseId, "returnId",
item.get("returnId"),
"returnItemSeqId", item.get("returnItemSeqId"), "statusId", returnItemStatusId, "userLogin", userLogin);
try {
serviceResults = dispatcher.runSync("updateReturnItem", returnItemMap);
if (ServiceUtil.isError(serviceResults)) {
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderProblemUpdatingReturnItemReturnItemResponseId", locale), null, null, serviceResults);
}
} catch (GenericServiceException e) {
Debug.logError("Problem updating the ReturnItem entity", MODULE);
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderProblemUpdatingReturnItemReturnItemResponseId", locale));
}
}
// Create the payment applications for the return invoice
try {
serviceResults = dispatcher.runSync("createPaymentApplicationsFromReturnItemResponse",
UtilMisc.<String, Object>toMap("returnItemResponseId", responseId, "userLogin", userLogin));
if (ServiceUtil.isError(serviceResults)) {
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderProblemUpdatingReturnItemReturnItemResponseId", locale), null, null, serviceResults);
}
} catch (GenericServiceException e) {
Debug.logError(e, "Problem creating PaymentApplication records for return invoice", MODULE);
return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
"OrderProblemUpdatingReturnItemReturnItemResponseId", locale));
}
// Update the amount necessary to refund
amountLeftToRefund = amountLeftToRefund.subtract(amountRefunded);
}
}
}
}
}
return ServiceUtil.returnSuccess();
}