Author: jonesde
Date: Fri Sep 8 21:15:54 2006 New Revision: 441736 URL: http://svn.apache.org/viewvc?view=rev&rev=441736 Log: Added functionality for picking out and retrying CC auths for NSF failures, meant for auto-orders to get better chance of successful charge for subscriptions and such; tries once a week for the number of times specified on the ProductStore Modified: incubator/ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml incubator/ofbiz/trunk/applications/accounting/servicedef/services_paymentmethod.xml incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java incubator/ofbiz/trunk/applications/order/data/OrderScheduledServices.xml incubator/ofbiz/trunk/applications/order/entitydef/entitymodel.xml Modified: incubator/ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml?view=diff&rev=441736&r1=441735&r2=441736 ============================================================================== --- incubator/ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml (original) +++ incubator/ofbiz/trunk/applications/accounting/entitydef/entitymodel.xml Fri Sep 8 21:15:54 2006 @@ -1898,6 +1898,8 @@ <field name="contactMechId" type="id-ne"><description>The Billing PostalAddress</description></field> <field name="consecutiveFailedAuths" type="numeric"></field> <field name="lastFailedAuthDate" type="date-time"></field> + <field name="consecutiveFailedNsf" type="numeric"></field> + <field name="lastFailedNsfDate" type="date-time"></field> <prim-key field="paymentMethodId"/> <relation type="one" fk-name="CREDCARD_PMNTMETH" rel-entity-name="PaymentMethod"> <key-map field-name="paymentMethodId"/> @@ -2597,6 +2599,10 @@ <field name="gatewayScoreResult" type="short-varchar"></field> <field name="gatewayMessage" type="long-varchar"></field> <field name="transactionDate" type="date-time"></field> + <field name="resultDeclined" type="indicator"></field> + <field name="resultNsf" type="indicator"></field> + <field name="resultBadExpire" type="indicator"></field> + <field name="resultBadCardNumber" type="indicator"></field> <prim-key field="paymentGatewayResponseId"/> <relation type="one" fk-name="PAYGATR_PSTENUM" title="ServiceType" rel-entity-name="Enumeration"> <key-map field-name="paymentServiceTypeEnumId" rel-field-name="enumId"/> Modified: incubator/ofbiz/trunk/applications/accounting/servicedef/services_paymentmethod.xml URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/accounting/servicedef/services_paymentmethod.xml?view=diff&rev=441736&r1=441735&r2=441736 ============================================================================== --- incubator/ofbiz/trunk/applications/accounting/servicedef/services_paymentmethod.xml (original) +++ incubator/ofbiz/trunk/applications/accounting/servicedef/services_paymentmethod.xml Fri Sep 8 21:15:54 2006 @@ -209,15 +209,20 @@ <description>(Batch) Retries failed authorizations due to processor/connection problems</description> <!-- this service has no parameters IN or OUT --> </service> - <service name="retryFailedOrderAuth" engine="java" location="org.ofbiz.accounting.payment.PaymentGatewayServices" invoke="retryFailedOrderAuth" auth="true"> - <description>Retries failed authorizations due to processor/connection problems for an order</description> + <description>Retries failed authorization due to processor/connection problems, or NSF (Not Sufficient Funds) failure, for an order</description> <attribute name="orderId" type="String" mode="IN" optional="false"/> <attribute name="processResult" type="String" mode="OUT" optional="true"/> <attribute name="authResultMsgs" type="List" mode="OUT" optional="true"/> </service> + <service name="retryFailedAuthNsfs" engine="java" + location="org.ofbiz.accounting.payment.PaymentGatewayServices" invoke="retryNsfFailedAuths" auth="true"> + <description>(Batch) Retries failed authorizations due to NSF (Not Sufficient Funds); these are for auto-orders</description> + <!-- this service has no parameters IN or OUT --> + </service> + <service name="authOrderPaymentPreference" engine="java" location="org.ofbiz.accounting.payment.PaymentGatewayServices" invoke="authOrderPaymentPreference" auth="true"> <description>Process (authorizes/re-authorizes) a single payment for an order with an optional overrideAmount</description> Modified: incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java?view=diff&rev=441736&r1=441735&r2=441736 ============================================================================== --- incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java (original) +++ incubator/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/payment/PaymentGatewayServices.java Fri Sep 8 21:15:54 2006 @@ -46,6 +46,7 @@ import org.ofbiz.entity.condition.EntityExpr; import org.ofbiz.entity.condition.EntityJoinOperator; import org.ofbiz.entity.condition.EntityOperator; +import org.ofbiz.entity.model.ModelEntity; import org.ofbiz.entity.util.EntityListIterator; import org.ofbiz.entity.util.EntityUtil; import org.ofbiz.order.order.OrderChangeHelper; @@ -61,10 +62,6 @@ /** * PaymentGatewayServices - * - * @author <a href="mailto:[hidden email]">Andy Zeneski</a> - * @version $Rev$ - * @since 2.0 */ public class PaymentGatewayServices { @@ -103,8 +100,8 @@ // validate overrideAmount if its available if (overrideAmount != null) { - if (overrideAmount.doubleValue() < 0) return ServiceUtil.returnError("Amount entered (" + overrideAmount + ") is negative."); - if (overrideAmount.doubleValue() == 0) return ServiceUtil.returnError("Amount entered (" + overrideAmount + ") is zero."); + if (overrideAmount.doubleValue() < 0) return ServiceUtil.returnError("Amount entered (" + overrideAmount + ") is negative."); + if (overrideAmount.doubleValue() == 0) return ServiceUtil.returnError("Amount entered (" + overrideAmount + ") is zero."); } GenericValue orderHeader = null; @@ -181,6 +178,11 @@ if (processResult) { results.put("processAmount", thisAmount); results.put("finished", Boolean.TRUE); + } else { + // if we are doing an NSF retry then also + //boolean needsNsfRetry = needsNsfRetry(orderPaymentPreference, processorResult, delegator); + // TODO: what do we do with this? we need to fail the auth but still allow the order through so it can be fixed later + // NOTE: this is called through a different path for auto re-orders, so it should be good to go... will leave this comment here just in case... } } catch (GeneralException e) { String errMsg = "Error saving and processing payment authorization results: " + e.toString(); @@ -1399,6 +1401,13 @@ } try { + String paymentMethodId = orderPaymentPreference.getString("paymentMethodId"); + GenericValue paymentMethod = delegator.findByPrimaryKey("PaymentMethod", UtilMisc.toMap("paymentMethodId", paymentMethodId)); + GenericValue creditCard = null; + if ("CREDIT_CARD".equals(paymentMethod.getString("paymentMethodTypeId"))) { + creditCard = paymentMethod.getRelatedOne("CreditCard"); + } + // create the PaymentGatewayResponse String responseId = delegator.getNextSeqId("PaymentGatewayResponse"); GenericValue response = delegator.makeValue("PaymentGatewayResponse", null); @@ -1422,7 +1431,13 @@ response.set("gatewayFlag", context.get("authFlag")); response.set("gatewayMessage", context.get("authMessage")); response.set("transactionDate", UtilDateTime.nowTimestamp()); - delegator.create(response); + + if (Boolean.TRUE.equals((Boolean) context.get("resultDeclined"))) response.set("resultDeclined", "Y"); + if (Boolean.TRUE.equals((Boolean) context.get("resultNsf"))) response.set("resultNsf", "Y"); + if (Boolean.TRUE.equals((Boolean) context.get("resultBadExpire"))) response.set("resultBadExpire", "Y"); + if (Boolean.TRUE.equals((Boolean) context.get("resultBadCardNumber"))) response.set("resultBadCardNumber", "Y"); + + response.create(); // create the internal messages List messages = (List) context.get("internalRespMsgs"); @@ -1452,24 +1467,49 @@ } else { orderPaymentPreference.set("statusId", "PAYMENT_ERROR"); } + + boolean needsNsfRetry = needsNsfRetry(orderPaymentPreference, context, delegator); + if (needsNsfRetry) { + orderPaymentPreference.set("needsNsfRetry", "Y"); + } else { + orderPaymentPreference.set("needsNsfRetry", "N"); + } + orderPaymentPreference.store(); // if the payment was declined and this is a CreditCard, save that information on the CreditCard entity - if (context != null && !authResult.booleanValue()) { - String paymentMethodId = orderPaymentPreference.getString("paymentMethodId"); - GenericValue paymentMethod = delegator.findByPrimaryKey("PaymentMethod", UtilMisc.toMap("paymentMethodId", paymentMethodId)); - if ("CREDIT_CARD".equals(paymentMethod.getString("paymentMethodTypeId"))) { - GenericValue creditCard = paymentMethod.getRelatedOne("CreditCard"); - + if (!authResult.booleanValue()) { + if (creditCard != null) { Long consecutiveFailedAuths = creditCard.getLong("consecutiveFailedAuths"); if (consecutiveFailedAuths == null) { creditCard.set("consecutiveFailedAuths", new Long(1)); } else { creditCard.set("consecutiveFailedAuths", new Long(consecutiveFailedAuths.longValue() + 1)); } - creditCard.set("lastFailedAuthDate", nowTimestamp); + if (Boolean.TRUE.equals((Boolean) context.get("resultNsf"))) { + Long consecutiveFailedNsf = creditCard.getLong("consecutiveFailedNsf"); + if (consecutiveFailedNsf == null) { + creditCard.set("consecutiveFailedNsf", new Long(1)); + } else { + creditCard.set("consecutiveFailedNsf", new Long(consecutiveFailedNsf.longValue() + 1)); + } + creditCard.set("lastFailedNsfDate", nowTimestamp); + } + + creditCard.store(); + } + } + + // auth was successful, to clear out any failed auth or nsf info + if (authResult.booleanValue()) { + if ((creditCard != null) && (creditCard.get("lastFailedAuthDate") != null)) { + creditCard.set("consecutiveFailedAuths", new Long(0)); + creditCard.set("lastFailedAuthDate", null); + creditCard.set("consecutiveFailedNsf", new Long(0)); + creditCard.set("lastFailedNsfDate", null); + creditCard.store(); } } @@ -1481,6 +1521,35 @@ return ServiceUtil.returnSuccess(); } + + private static boolean needsNsfRetry(GenericValue orderPaymentPreference, Map processContext, GenericDelegator delegator) throws GenericEntityException { + boolean needsNsfRetry = false; + if (Boolean.TRUE.equals((Boolean) processContext.get("resultNsf"))) { + // only track this for auto-orders, since we will only not fail and re-try on those + GenericValue orderHeader = orderPaymentPreference.getRelatedOne("OrderHeader"); + if (UtilValidate.isNotEmpty(orderHeader.getString("autoOrderShoppingListId"))) { + GenericValue productStore = orderHeader.getRelatedOne("ProductStore"); + if ("Y".equals(productStore.getString("autoOrderCcTryLaterNsf"))) { + // one last condition: make sure there have been less than ProductStore.autoOrderCcTryLaterMax + // PaymentGatewayResponse records with the same orderPaymentPreferenceId and paymentMethodId (just in case it has changed) + // and that have resultNsf = Y, ie only consider other NSF responses + Long autoOrderCcTryLaterMax = productStore.getLong("autoOrderCcTryLaterMax"); + if (autoOrderCcTryLaterMax != null) { + long failedTries = delegator.findCountByAnd("PaymentGatewayResponse", + UtilMisc.toMap("orderPaymentPreferenceId", orderPaymentPreference.get("orderPaymentPreferenceId"), + "paymentMethodId", orderPaymentPreference.get("paymentMethodId"), + "resultNsf", "Y")); + if (failedTries < autoOrderCcTryLaterMax.longValue()) { + needsNsfRetry = true; + } + } else { + needsNsfRetry = true; + } + } + } + } + return needsNsfRetry; + } private static GenericValue processAuthRetryResult(DispatchContext dctx, Map result, GenericValue userLogin, GenericValue paymentPreference) throws GeneralException { processAuthResult(dctx, result, userLogin, paymentPreference); @@ -1925,6 +1994,7 @@ } } + public static Map retryFailedOrderAuth(DispatchContext dctx, Map context) { GenericDelegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); @@ -1948,6 +2018,7 @@ // check the current order status if (!"ORDER_CREATED".equals(orderHeader.getString("statusId"))) { // if we are out of the created status; then we were either cancelled, rejected or approved + Debug.logWarning("Was re-trying a failed auth for orderId [" + orderId + "] but it is not in the ORDER_CREATED status, so skipping.", module); return ServiceUtil.returnSuccess(); } @@ -1975,7 +2046,6 @@ if ("FAILED".equals(authResp)) { // declined; update the order status OrderChangeHelper.rejectOrder(dispatcher, userLogin, orderId); - } else if ("APPROVED".equals(authResp)) { // approved; update the order status OrderChangeHelper.approveOrder(dispatcher, userLogin, orderId); @@ -1988,6 +2058,7 @@ return result; } + public static Map retryFailedAuths(DispatchContext dctx, Map context) { GenericDelegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); @@ -2001,38 +2072,87 @@ try { eli = delegator.findListIteratorByCondition("OrderPaymentPreference", new EntityConditionList(exprs, EntityOperator.AND), null, UtilMisc.toList("orderId")); + List processList = new ArrayList(); + if (eli != null) { + Debug.logInfo("Processing failed order re-auth(s)", module); + GenericValue value = null; + while (((value = (GenericValue) eli.next()) != null)) { + String orderId = value.getString("orderId"); + if (!processList.contains(orderId)) { // just try each order once + try { + // each re-try is independent of each other; if one fails it should not effect the others + dispatcher.runAsync("retryFailedOrderAuth", UtilMisc.toMap("orderId", orderId, "userLogin", userLogin)); + processList.add(orderId); + } catch (GenericServiceException e) { + Debug.logError(e, module); + } + } + } + } } catch (GenericEntityException e) { Debug.logError(e, module); + } finally { + if (eli != null) { + try { + eli.close(); + } catch (GenericEntityException e) { + Debug.logError(e, module); + } + } } - List processList = new ArrayList(); - if (eli != null) { - Debug.logInfo("Processing failed order re-auth(s)", module); - GenericValue value; - while (((value = (GenericValue) eli.next()) != null)) { - String orderId = value.getString("orderId"); - if (!processList.contains(orderId)) { // just try each order once - try { - // each re-try is independent of each other; if one fails it should not effect the others - dispatcher.runAsync("retryFailedOrderAuth", UtilMisc.toMap("orderId", orderId, "userLogin", userLogin)); - processList.add(orderId); - } catch (GenericServiceException e) { - Debug.logError(e, module); + return ServiceUtil.returnSuccess(); + } + + public static Map retryFailedAuthNsfs(DispatchContext dctx, Map context) { + GenericDelegator delegator = dctx.getDelegator(); + LocalDispatcher dispatcher = dctx.getDispatcher(); + GenericValue userLogin = (GenericValue) context.get("userLogin"); + + // get the date/time for one week before now since we'll only retry once a week for NSFs + Calendar calcCal = Calendar.getInstance(); + calcCal.setTimeInMillis(System.currentTimeMillis()); + calcCal.add(Calendar.WEEK_OF_YEAR, -1); + Timestamp oneWeekAgo = new Timestamp(calcCal.getTimeInMillis()); + + EntityListIterator eli = null; + try { + eli = delegator.findListIteratorByCondition("OrderPaymentPreference", + new EntityExpr(new EntityExpr("needsNsfRetry", EntityOperator.EQUALS, "Y"), EntityOperator.AND, new EntityExpr(ModelEntity.STAMP_FIELD, EntityOperator.LESS_THAN_EQUAL_TO, oneWeekAgo)), + null, UtilMisc.toList("orderId")); + + List processList = new ArrayList(); + if (eli != null) { + Debug.logInfo("Processing failed order re-auth(s)", module); + GenericValue value = null; + while (((value = (GenericValue) eli.next()) != null)) { + String orderId = value.getString("orderId"); + if (!processList.contains(orderId)) { // just try each order once + try { + // each re-try is independent of each other; if one fails it should not effect the others + dispatcher.runAsync("retryFailedOrderAuthNsf", UtilMisc.toMap("orderId", orderId, "userLogin", userLogin)); + processList.add(orderId); + } catch (GenericServiceException e) { + Debug.logError(e, module); + } } } } - - try { - eli.close(); - } catch (GenericEntityException e) { - Debug.logError(e, module); + } catch (GenericEntityException e) { + Debug.logError(e, module); + } finally { + if (eli != null) { + try { + eli.close(); + } catch (GenericEntityException e) { + Debug.logError(e, module); + } } } - processList = null; return ServiceUtil.returnSuccess(); } - + public static GenericValue getCaptureTransaction(GenericValue orderPaymentPreference) { GenericValue capTrans = null; try { @@ -2139,6 +2259,7 @@ } // manual processing service + public static Map processManualCcTx(DispatchContext dctx, Map context) { GenericValue userLogin = (GenericValue) context.get("userLogin"); LocalDispatcher dispatcher = dctx.getDispatcher(); @@ -2254,6 +2375,7 @@ // Test Services // **************************************************** + /** * Simple test processor; declines all orders < 100.00; approves all orders > 100.00 */ @@ -2281,6 +2403,7 @@ return result; } + /** * Simple test processor; declines all orders < 100.00; approves all orders > 100.00 */ @@ -2314,6 +2437,7 @@ return result; } + /** * Always approve processor. */ @@ -2351,6 +2475,7 @@ result.put("authMessage", "This is a test processor; no payments were captured or authorized."); return result; } + /** * Always decline processor Modified: incubator/ofbiz/trunk/applications/order/data/OrderScheduledServices.xml URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/order/data/OrderScheduledServices.xml?view=diff&rev=441736&r1=441735&r2=441736 ============================================================================== --- incubator/ofbiz/trunk/applications/order/data/OrderScheduledServices.xml (original) +++ incubator/ofbiz/trunk/applications/order/data/OrderScheduledServices.xml Fri Sep 8 21:15:54 2006 @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - Copyright 2001-2006 The Apache Software Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -20,11 +19,13 @@ <RecurrenceRule recurrenceRuleId="200" untilDateTime="" frequency="DAILY" intervalNumber="1" countNumber="-1"/> <RecurrenceInfo recurrenceInfoId="200" startDateTime="2000-01-01 00:00:00.000" recurrenceRuleId="200" recurrenceCount="0"/> <RecurrenceInfo recurrenceInfoId="201" startDateTime="2000-01-01 01:00:00.000" recurrenceRuleId="200" recurrenceCount="0"/> - <RecurrenceInfo recurrenceInfoId="202" startDateTime="2000-01-01 03:00:00.000" recurrenceRuleId="200" recurrenceCount="0"/> - <RecurrenceInfo recurrenceInfoId="203" startDateTime="2000-01-01 04:00:00.000" recurrenceRuleId="200" recurrenceCount="0"/> - + <RecurrenceInfo recurrenceInfoId="202" startDateTime="2000-01-01 02:00:00.000" recurrenceRuleId="200" recurrenceCount="0"/> + <RecurrenceInfo recurrenceInfoId="203" startDateTime="2000-01-01 03:00:00.000" recurrenceRuleId="200" recurrenceCount="0"/> + <RecurrenceInfo recurrenceInfoId="204" startDateTime="2000-01-01 04:00:00.000" recurrenceRuleId="200" recurrenceCount="0"/> + <JobSandbox jobId="8000" jobName="BackOrder Notification" runTime="2000-01-01 00:00:00.000" serviceName="checkInventoryAvailability" poolId="pool" runAsUser="system" recurrenceInfoId="200"/> <JobSandbox jobId="8001" jobName="Re-Try Failed Auths" runTime="2000-01-01 01:00:00.000" serviceName="retryFailedAuths" poolId="pool" runAsUser="system" recurrenceInfoId="201"/> <JobSandbox jobId="8002" jobName="Order Auto-Cancel" runTime="2000-01-01 03:00:00.000" serviceName="autoCancelOrderItems" poolId="pool" runAsUser="system" recurrenceInfoId="202"/> <JobSandbox jobId="8003" jobName="Run Auto-Reorders" runTime="2000-01-01 03:00:00.000" serviceName="runShoppingListAutoReorder" poolId="pool" runAsUser="system" recurrenceInfoId="203"/> + <JobSandbox jobId="8004" jobName="Re-Try Failed Auths NSF" runTime="2000-01-01 01:00:00.000" serviceName="retryFailedAuthNsfs" poolId="pool" runAsUser="system" recurrenceInfoId="204"/> </entity-engine-xml> Modified: incubator/ofbiz/trunk/applications/order/entitydef/entitymodel.xml URL: http://svn.apache.org/viewvc/incubator/ofbiz/trunk/applications/order/entitydef/entitymodel.xml?view=diff&rev=441736&r1=441735&r2=441736 ============================================================================== --- incubator/ofbiz/trunk/applications/order/entitydef/entitymodel.xml (original) +++ incubator/ofbiz/trunk/applications/order/entitydef/entitymodel.xml Fri Sep 8 21:15:54 2006 @@ -850,6 +850,7 @@ <field name="manualAuthCode" type="short-varchar"></field> <field name="manualRefNum" type="short-varchar"></field> <field name="statusId" type="id"></field> + <field name="needsNsfRetry" type="indicator"></field> <field name="createdDate" type="date-time"></field> <field name="createdByUserLogin" type="id-vlong"></field> <prim-key field="orderPaymentPreferenceId"/> @@ -884,6 +885,9 @@ <relation type="one-nofk" rel-entity-name="GiftCard"> <key-map field-name="paymentMethodId"/> </relation> + <index name="NSF_RETRY_CHECK"> + <index-field name="needsNsfRetry"/> + </index> </entity> <entity entity-name="OrderRole" package-name="org.ofbiz.order.order" |
Free forum by Nabble | Edit this page |