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;
  }
Beispiel #2
0
  public static Map<String, Object> doAuthorization(
      DispatchContext dctx, Map<String, Object> context) {
    Delegator delegator = dctx.getDelegator();
    String orderId = (String) context.get("orderId");
    BigDecimal processAmount = (BigDecimal) context.get("processAmount");
    GenericValue payPalPaymentMethod = (GenericValue) context.get("payPalPaymentMethod");
    OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
    GenericValue payPalConfig =
        getPaymentMethodGatewayPayPal(dctx, context, PaymentGatewayServices.AUTH_SERVICE_TYPE);
    Locale locale = (Locale) context.get("locale");

    NVPEncoder encoder = new NVPEncoder();
    encoder.add("METHOD", "DoAuthorization");
    encoder.add("TRANSACTIONID", payPalPaymentMethod.getString("transactionId"));
    encoder.add("AMT", processAmount.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
    encoder.add("TRANSACTIONENTITY", "Order");
    String currency = (String) context.get("currency");
    if (currency == null) {
      currency = orh.getCurrency();
    }
    encoder.add("CURRENCYCODE", currency);

    NVPDecoder decoder = null;
    try {
      decoder = sendNVPRequest(payPalConfig, encoder);
    } catch (PayPalException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }

    if (decoder == null) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(resource, "AccountingPayPalUnknownError", locale));
    }

    Map<String, Object> result = ServiceUtil.returnSuccess();
    Map<String, String> errors = getErrorMessageMap(decoder);
    if (UtilValidate.isNotEmpty(errors)) {
      result.put("authResult", false);
      result.put("authRefNum", "N/A");
      result.put("processAmount", BigDecimal.ZERO);
      if (errors.size() == 1) {
        Map.Entry<String, String> error = errors.entrySet().iterator().next();
        result.put("authCode", error.getKey());
        result.put("authMessage", error.getValue());
      } else {
        result.put(
            "authMessage",
            "Multiple errors occurred, please refer to the gateway response messages");
        result.put("internalRespMsgs", errors);
      }
    } else {
      result.put("authResult", true);
      result.put("processAmount", new BigDecimal(decoder.get("AMT")));
      result.put("authRefNum", decoder.get("TRANSACTIONID"));
    }
    // TODO: Look into possible PAYMENTSTATUS and PENDINGREASON return codes, it is unclear what
    // should be checked for this type of transaction
    return result;
  }
Beispiel #3
0
  private static GenericValue getPaymentMethodGatewayPayPal(
      DispatchContext dctx,
      Map<String, ? extends Object> context,
      String paymentServiceTypeEnumId) {
    Delegator delegator = dctx.getDelegator();
    String paymentGatewayConfigId = (String) context.get("paymentGatewayConfigId");
    GenericValue payPalGatewayConfig = null;

    if (paymentGatewayConfigId == null) {
      String productStoreId = null;
      GenericValue orderPaymentPreference = (GenericValue) context.get("orderPaymentPreference");
      if (orderPaymentPreference != null) {
        OrderReadHelper orh =
            new OrderReadHelper(delegator, orderPaymentPreference.getString("orderId"));
        productStoreId = orh.getProductStoreId();
      } else {
        ShoppingCart cart = (ShoppingCart) context.get("cart");
        if (cart != null) {
          productStoreId = cart.getProductStoreId();
        }
      }
      if (productStoreId != null) {
        GenericValue payPalPaymentSetting =
            ProductStoreWorker.getProductStorePaymentSetting(
                delegator, productStoreId, "EXT_PAYPAL", paymentServiceTypeEnumId, true);
        if (payPalPaymentSetting != null) {
          paymentGatewayConfigId = payPalPaymentSetting.getString("paymentGatewayConfigId");
        }
      }
    }
    if (paymentGatewayConfigId != null) {
      try {
        payPalGatewayConfig =
            EntityQuery.use(delegator)
                .from("PaymentGatewayPayPal")
                .where("paymentGatewayConfigId", paymentGatewayConfigId)
                .cache()
                .queryOne();
      } catch (GenericEntityException e) {
        Debug.logError(e, module);
      }
    }
    return payPalGatewayConfig;
  }
Beispiel #4
0
  public static Map<String, Object> assembleCrmsfaOrderFormMergeContext(
      Delegator delegator, String orderId) {
    Map<String, Object> templateContext = new HashMap<String, Object>();
    if (UtilValidate.isNotEmpty(orderId)) {
      try {
        OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
        templateContext.put("orderId", orderId);
        templateContext.put("externalOrderId", orh.getOrderHeader().get("externalId"));
        templateContext.put("orderDate", orh.getOrderHeader().getTimestamp("orderDate"));

        GenericValue billingParty = orh.getBillToParty();
        if (UtilValidate.isNotEmpty(billingParty)) {
          if ("Person".equalsIgnoreCase(billingParty.getEntityName())) {
            templateContext.put("orderBillingFirstName", billingParty.get("firstName"));
            templateContext.put("orderBillingLastName", billingParty.get("lastName"));
          }
          templateContext.put("orderPartyId", billingParty.get("partyId"));
          templateContext.put(
              "orderBillingFullName",
              org.ofbiz.party.party.PartyHelper.getPartyName(billingParty, false));
        }

        templateContext.put("orderSubtotal", orh.getOrderItemsSubTotal());
        templateContext.put("orderTaxTotal", orh.getTaxTotal());
        templateContext.put("orderShippingTotal", orh.getShippingTotal());
        templateContext.put("orderGrandTotal", orh.getOrderGrandTotal());
        templateContext.put(
            "orderPaymentTotal",
            orh.getOrderGrandTotal().subtract(UtilOrder.getOrderOpenAmount(orh)));

        GenericValue shippingParty = orh.getShipToParty();
        if (UtilValidate.isNotEmpty(shippingParty)) {
          if ("Person".equalsIgnoreCase(shippingParty.getEntityName())) {
            templateContext.put("orderShippingFirstName", shippingParty.get("firstName"));
            templateContext.put("orderShippingLastName", shippingParty.get("lastName"));
          } else {
            templateContext.put("orderShippingCompanyName", shippingParty.get("groupName"));
          }
          templateContext.put(
              "orderShippingFullName",
              org.ofbiz.party.party.PartyHelper.getPartyName(shippingParty, false));
        }

        List<GenericValue> orderItemVals = orh.getOrderItems();
        List<Map<String, Object>> orderItems = new ArrayList<Map<String, Object>>();
        for (GenericValue orderItemVal : orderItemVals) {
          Map<String, Object> orderItem = orderItemVal.getAllFields();
          GenericValue product = orderItemVal.getRelatedOne("Product");
          if (UtilValidate.isEmpty(product)) {
            continue;
          }
          for (String fieldName : (Set<String>) product.keySet()) {
            orderItem.put(fieldName, product.get(fieldName));
          }
          orderItems.add(orderItem);
        }
        templateContext.put("orderItems", orderItems);

      } catch (GenericEntityException e) {
        Debug.logError(e, MODULE);
      }
    }
    return templateContext;
  }
Beispiel #5
0
  public static Map<String, Object> assembleCrmsfaShipmentFormMergeContext(
      Delegator delegator,
      String orderId,
      String shipGroupSeqId,
      String shipmentId,
      Locale locale) {
    Map<String, Object> templateContext = new HashMap<String, Object>();

    try {

      // Prefer shipment data if shipmentId is provided
      if (UtilValidate.isNotEmpty(shipmentId)) {

        GenericValue shipment =
            delegator.findByPrimaryKey("Shipment", UtilMisc.toMap("shipmentId", shipmentId));
        if (UtilValidate.isNotEmpty(shipment)) {

          GenericValue shipLoc = shipment.getRelatedOne("DestinationPostalAddress");
          if (UtilValidate.isNotEmpty(shipLoc)) {
            templateContext.put("orderShippingAddress1", shipLoc.get("address1"));
            templateContext.put("orderShippingAddress2", shipLoc.get("address2"));
            templateContext.put("orderShippingCity", shipLoc.get("city"));
            templateContext.put("orderShippingPostalCode", shipLoc.get("postalCode"));

            GenericValue stateProvGeo = shipLoc.getRelatedOne("StateProvinceGeo");
            if (UtilValidate.isNotEmpty(stateProvGeo)) {
              templateContext.put("orderShippingStateProvince", stateProvGeo.get("geoName"));
            }
            GenericValue countryGeo = shipLoc.getRelatedOne("CountryGeo");
            if (UtilValidate.isNotEmpty(countryGeo)) {
              templateContext.put("orderShippingCountry", countryGeo.get("geoName"));
            }
          }

          GenericValue phoneNumber = shipment.getRelatedOne("DestinationTelecomNumber");
          if (UtilValidate.isNotEmpty(phoneNumber)) {

            String phoneNumberString =
                UtilValidate.isEmpty(phoneNumber.getString("countryCode"))
                    ? ""
                    : phoneNumber.getString("countryCode") + " ";
            if (UtilValidate.isNotEmpty(phoneNumber.getString("areaCode"))) {
              phoneNumberString += phoneNumber.getString("areaCode") + " ";
            }
            if (UtilValidate.isNotEmpty(phoneNumber.getString("contactNumber"))) {
              phoneNumberString += phoneNumber.getString("contactNumber");
            }
            templateContext.put("orderShippingPhone", phoneNumberString);
          }

          GenericValue statusItem = shipment.getRelatedOne("StatusItem");
          if (UtilValidate.isNotEmpty(statusItem)) {
            templateContext.put("shipmentStatus", statusItem.get("description", locale));
          }
        }

      } else if (UtilValidate.isNotEmpty(orderId)) {

        OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
        GenericValue shipGroup = orh.getOrderItemShipGroup(shipGroupSeqId);
        if (UtilValidate.isEmpty(shipGroup)) {

          // Default to the first ship group if no shipGroupSeqId is provided
          List shipGroups = orh.getOrderItemShipGroups();
          if (UtilValidate.isNotEmpty(shipGroups)) {
            shipGroup = (GenericValue) shipGroups.get(0);
          }
        }

        if (UtilValidate.isNotEmpty(shipGroup)) {
          GenericValue shipLoc = shipGroup.getRelatedOne("PostalAddress");
          if (UtilValidate.isNotEmpty(shipLoc)) {
            templateContext.put("orderShippingAddress1", shipLoc.get("address1"));
            templateContext.put("orderShippingAddress2", shipLoc.get("address2"));
            templateContext.put("orderShippingCity", shipLoc.get("city"));
            templateContext.put("orderShippingPostalCode", shipLoc.get("postalCode"));

            GenericValue stateProvGeo = shipLoc.getRelatedOne("StateProvinceGeo");
            if (UtilValidate.isNotEmpty(stateProvGeo)) {
              templateContext.put("orderShippingStateProvince", stateProvGeo.get("geoName"));
            }
            GenericValue countryGeo = shipLoc.getRelatedOne("CountryGeo");
            if (UtilValidate.isNotEmpty(countryGeo)) {
              templateContext.put("orderShippingCountry", countryGeo.get("geoName"));
            }
          }

          GenericValue phoneNumber = shipGroup.getRelatedOne("TelecomTelecomNumber");
          if (UtilValidate.isNotEmpty(phoneNumber)) {

            String phoneNumberString =
                UtilValidate.isEmpty(phoneNumber.getString("countryCode"))
                    ? ""
                    : phoneNumber.getString("countryCode") + " ";
            if (UtilValidate.isNotEmpty(phoneNumber.getString("areaCode"))) {
              phoneNumberString += phoneNumber.getString("areaCode") + " ";
            }
            if (UtilValidate.isNotEmpty(phoneNumber.getString("contactNumber"))) {
              phoneNumberString += phoneNumber.getString("contactNumber");
            }
            templateContext.put("orderShippingPhone", phoneNumberString);
          }

          // Find any shipments relating to this ship group
          List<GenericValue> shipments =
              delegator.findByAnd(
                  "Shipment",
                  UtilMisc.toMap(
                      "primaryOrderId",
                      orderId,
                      "primaryShipGroupSeqId",
                      shipGroup.getString("shipGroupSeqId")),
                  UtilMisc.toList("createdStamp DESC"));
          GenericValue shipment = EntityUtil.getFirst(shipments);
          if (UtilValidate.isNotEmpty(shipment)) {
            GenericValue statusItem = shipment.getRelatedOne("StatusItem");
            if (UtilValidate.isNotEmpty(statusItem)) {
              templateContext.put("shipmentStatus", statusItem.get("description", locale));
            }
          }
        }
      }
    } catch (GenericEntityException e) {
      Debug.logError(e, MODULE);
    }
    return templateContext;
  }
Beispiel #6
0
  // Note we're not doing a lot of error checking here as this method is really only used
  // to confirm the order with PayPal, the subsequent authorizations will handle any errors
  // that may occur.
  public static Map<String, Object> doExpressCheckout(
      DispatchContext dctx, Map<String, Object> context) {
    LocalDispatcher dispatcher = dctx.getDispatcher();
    Delegator delegator = dctx.getDelegator();
    GenericValue userLogin = (GenericValue) context.get("userLogin");
    GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
    OrderReadHelper orh = new OrderReadHelper(delegator, paymentPref.getString("orderId"));
    Locale locale = (Locale) context.get("locale");

    GenericValue payPalPaymentSetting = getPaymentMethodGatewayPayPal(dctx, context, null);
    GenericValue payPalPaymentMethod = null;
    try {
      payPalPaymentMethod = paymentPref.getRelatedOne("PaymentMethod", false);
      payPalPaymentMethod = payPalPaymentMethod.getRelatedOne("PayPalPaymentMethod", false);
    } catch (GenericEntityException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    BigDecimal processAmount = paymentPref.getBigDecimal("maxAmount");

    NVPEncoder encoder = new NVPEncoder();
    encoder.add("METHOD", "DoExpressCheckoutPayment");
    encoder.add("TOKEN", payPalPaymentMethod.getString("expressCheckoutToken"));
    encoder.add("PAYMENTACTION", "Order");
    encoder.add("PAYERID", payPalPaymentMethod.getString("payerId"));
    // set the amount
    encoder.add("AMT", processAmount.setScale(2).toPlainString());
    encoder.add("CURRENCYCODE", orh.getCurrency());
    BigDecimal grandTotal = orh.getOrderGrandTotal();
    BigDecimal shippingTotal = orh.getShippingTotal().setScale(2, BigDecimal.ROUND_HALF_UP);
    BigDecimal taxTotal = orh.getTaxTotal().setScale(2, BigDecimal.ROUND_HALF_UP);
    BigDecimal subTotal =
        grandTotal.subtract(shippingTotal).subtract(taxTotal).setScale(2, BigDecimal.ROUND_HALF_UP);
    encoder.add("ITEMAMT", subTotal.toPlainString());
    encoder.add("SHIPPINGAMT", shippingTotal.toPlainString());
    encoder.add("TAXAMT", taxTotal.toPlainString());

    NVPDecoder decoder = null;
    try {
      decoder = sendNVPRequest(payPalPaymentSetting, encoder);
    } catch (PayPalException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    if (decoder == null) {
      return ServiceUtil.returnError(
          UtilProperties.getMessage(resource, "AccountingPayPalUnknownError", locale));
    }

    Map<String, String> errorMessages = getErrorMessageMap(decoder);
    if (UtilValidate.isNotEmpty(errorMessages)) {
      if (errorMessages.containsKey("10417")) {
        // "The transaction cannot complete successfully,  Instruct the customer to use an
        // alternative payment method"
        // I've only encountered this once and there's no indication of the cause so the temporary
        // solution is to try again
        boolean retry = context.get("_RETRY_") == null || (Boolean) context.get("_RETRY_");
        if (retry) {
          context.put("_RETRY_", false);
          return PayPalServices.doExpressCheckout(dctx, context);
        }
      }
      return ServiceUtil.returnError(UtilMisc.toList(errorMessages.values()));
    }

    Map<String, Object> inMap = FastMap.newInstance();
    inMap.put("userLogin", userLogin);
    inMap.put("paymentMethodId", payPalPaymentMethod.get("paymentMethodId"));
    inMap.put("transactionId", decoder.get("TRANSACTIONID"));

    Map<String, Object> outMap = null;
    try {
      outMap = dispatcher.runSync("updatePayPalPaymentMethod", inMap);
    } catch (GenericServiceException e) {
      Debug.logError(e, module);
      return ServiceUtil.returnError(e.getMessage());
    }
    if (ServiceUtil.isError(outMap)) {
      Debug.logError(ServiceUtil.getErrorMessage(outMap), module);
      return outMap;
    }
    return ServiceUtil.returnSuccess();
  }
  // 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> 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 = new HashMap<String, Object>();
    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;
  }
  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 = new HashMap<String, Object>();
    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;
  }