svn commit: r441736 - in /incubator/ofbiz/trunk/applications: accounting/entitydef/ accounting/servicedef/ accounting/src/org/ofbiz/accounting/payment/ order/data/ order/entitydef/

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r441736 - in /incubator/ofbiz/trunk/applications: accounting/entitydef/ accounting/servicedef/ accounting/src/org/ofbiz/accounting/payment/ order/data/ order/entitydef/

jonesde
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"