svn commit: r571249 - /ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/authorizedotnet/AIMPaymentServices.java

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

svn commit: r571249 - /ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/authorizedotnet/AIMPaymentServices.java

sichen
Author: sichen
Date: Thu Aug 30 10:26:25 2007
New Revision: 571249

URL: http://svn.apache.org/viewvc?rev=571249&view=rev
Log:
Fix authorize.net so it can refund and release correctly using void transactions.

Modified:
    ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/authorizedotnet/AIMPaymentServices.java

Modified: ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/authorizedotnet/AIMPaymentServices.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/authorizedotnet/AIMPaymentServices.java?rev=571249&r1=571248&r2=571249&view=diff
==============================================================================
--- ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/authorizedotnet/AIMPaymentServices.java (original)
+++ ofbiz/trunk/applications/accounting/src/org/ofbiz/accounting/thirdparty/authorizedotnet/AIMPaymentServices.java Thu Aug 30 10:26:25 2007
@@ -19,21 +19,50 @@
 
 package org.ofbiz.accounting.thirdparty.authorizedotnet;
 
-import java.util.*;
-
+import org.ofbiz.accounting.payment.PaymentGatewayServices;
 import org.ofbiz.base.util.*;
-import org.ofbiz.entity.*;
-import org.ofbiz.service.*;
+import org.ofbiz.entity.GenericDelegator;
+import org.ofbiz.entity.GenericEntityException;
+import org.ofbiz.entity.GenericValue;
+import org.ofbiz.service.DispatchContext;
+import org.ofbiz.service.ModelService;
+import org.ofbiz.service.ServiceUtil;
 
-import org.ofbiz.accounting.payment.PaymentGatewayServices;
+import java.sql.Timestamp;
+import java.util.*;
 
 
 public class AIMPaymentServices {
 
     public static final String module = AIMPaymentServices.class.getName();
 
+    // TODO: Reformat the comments below to fit JavaDocs specs
+
+    // The list of refund failure response codes that would cause the ccRefund service
+    // to attempt to void the refund's associated authorization transaction.  This list
+    // contains the responses where the voiding does not need to be done within a certain
+    // time limit
+    private static final List VOIDABLE_RESPONSES_NO_TIME_LIMIT = UtilMisc.toList("50");
+
+    // A list of refund failure response codes that would cause the ccRefund service
+    // to first check whether the refund's associated authorization transaction has occurred
+    // within a certain time limit, and if so, cause it to void the transaction
+    private static final List VOIDABLE_RESPONSES_TIME_LIMIT = UtilMisc.toList("54");
+
+    // The number of days in the time limit when one can safely consider an unsettled
+    // transaction to be still valid
+    private static int TIME_LIMIT_VERIFICATION_DAYS = 120;
+
     private static Properties AIMProperties = null;
 
+    // A routine to check whether a given refund failure response code will cause the
+    // ccRefund service to attempt to void the refund's associated authorization transaction
+    private static boolean isVoidableResponse(String responseCode) {
+        return
+            VOIDABLE_RESPONSES_NO_TIME_LIMIT.contains(responseCode) ||
+            VOIDABLE_RESPONSES_TIME_LIMIT.contains(responseCode);
+    }
+
     public static Map ccAuth(DispatchContext ctx, Map context) {
         Map results = ServiceUtil.returnSuccess();
         Map request = new HashMap();
@@ -112,13 +141,6 @@
         return results;
     }
 
-    public static Map ccRelease(DispatchContext ctx, Map context) {
-        Map results = new HashMap();
-        results.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_ERROR);
-        results.put(ModelService.ERROR_MESSAGE, "Authorize.net ccRelease unsupported with version 3.0");
-        return results;
-    }
-
     public static Map ccRefund(DispatchContext ctx, Map context) {
         GenericDelegator delegator = ctx.getDelegator();
         GenericValue orderPaymentPreference = (GenericValue) context.get("orderPaymentPreference");
@@ -160,11 +182,110 @@
         }
 
         Map reply = processCard(request, props);
+        results.putAll( processRefundTransResult(reply) );
+
+        boolean refundResult = ((Boolean)results.get("refundResult")).booleanValue();
+        String refundFlag = (String)results.get("refundFlag");
+
+        // Since the refund failed, we are going to void the previous authorization against
+        // which ccRefunds attempted to issue the refund.  This happens because Authorize.NET requires
+        // that settled transactions need to be voided the same day.  unfortunately they provide no method for
+        // determining what transactions can be voided and what can be refunded, so we'll have to try it with timestamps
+        if (!refundResult && isVoidableResponse(refundFlag)) {
+            boolean canDoVoid = false;
+
+            if (VOIDABLE_RESPONSES_TIME_LIMIT.contains(refundFlag)) {
+                // We are calculating the timestamp that is at the beginning of a time limit,
+                // since we can safely assume that, within this time limit, an unsettled transaction
+                // can still be considered valid
+                Calendar startCalendar = UtilDateTime.toCalendar(UtilDateTime.nowTimestamp());
+                startCalendar.add(Calendar.DATE, -TIME_LIMIT_VERIFICATION_DAYS);
+                Timestamp startTimestamp = new java.sql.Timestamp(startCalendar.getTime().getTime());
+
+                Timestamp authTimestamp = authTransaction.getTimestamp("transactionDate");
+
+                if (startTimestamp.before(authTimestamp)) {
+                    canDoVoid = true;
+                }
+            } else {
+                // Since there's no time limit to check, the voiding of the transaction will go
+                // through as usual
+                canDoVoid = true;
+            }
+
+            if (canDoVoid) {
+                Double authAmountObj = authTransaction.getDouble("amount");
+                Double refundAmountObj = (Double)context.get("refundAmount");
+
+                double authAmount = authAmountObj != null? authAmountObj.doubleValue() : 0.0;
+                double refundAmount = refundAmountObj != null? refundAmountObj.doubleValue() : 0.0;
+
+                if (authAmount == refundAmount) {
+                    reply = voidTransaction(authTransaction, context);
+                    if (ServiceUtil.isError(reply)) return reply;
+                    
+                    results = ServiceUtil.returnSuccess();
+                    results.putAll( processRefundTransResult(reply) );
+                    return results;
+                } else {
+                    // TODO: Modify the code to (a) do a void of the whole transaction, and (b)
+                    // create a new auth-capture of the difference.
+                    return ServiceUtil.returnError("Cannot perform a VOID transaction: authAmount [" + authAmount + "] is different than refundAmount [" + refundAmount + "]");
+                }
+            }
+        }
 
-        processRefundTransResult(reply,results);
         return results;
     }
 
+    public static Map ccRelease(DispatchContext ctx, Map context) {
+        GenericValue orderPaymentPreference = (GenericValue) context.get("orderPaymentPreference");
+
+        GenericValue creditCard = null;
+        try {
+            creditCard = orderPaymentPreference.getRelatedOne("CreditCard");
+        } catch (GenericEntityException e) {
+            Debug.logError(e, module);
+            return ServiceUtil.returnError("Unable to obtain cc information from payment preference [ID = " + orderPaymentPreference.getString("orderPaymentPreferenceId") + "]");
+        }
+
+        GenericValue authTransaction = PaymentGatewayServices.getAuthTransaction(orderPaymentPreference);
+        if (authTransaction == null) {
+            return ServiceUtil.returnError("No authorization transaction found for the OrderPaymentPreference [ID = " + orderPaymentPreference.getString("orderPaymentPreferenceId") + "]; cannot void");
+        }
+
+        Map reply = voidTransaction(authTransaction, context);
+        if (ServiceUtil.isError(reply)) return reply;
+
+        Map results = ServiceUtil.returnSuccess();
+        results.putAll( processReleaseTransResult(reply) );
+        return results;
+    }
+
+    private static Map voidTransaction(GenericValue authTransaction, Map context) {
+        context.put("authTransaction",authTransaction);
+        Map results = ServiceUtil.returnSuccess();
+        Map request = new HashMap();
+
+        Properties props = buildAIMProperties(context);
+        buildMerchantInfo(context,props,request);
+        buildGatewayResponeConfig(context,props,request);
+        buildEmailSettings(context,props,request);
+        props.put("transType","VOID");
+        buildVoidTransaction(context,props,request);
+
+        Map validateResults = validateRequest(context,props,request);
+        String respMsg = (String)validateResults.get(ModelService.RESPONSE_MESSAGE);
+        if(respMsg != null) {
+            if(respMsg.equals(ModelService.RESPOND_ERROR)) {
+                results.put(ModelService.ERROR_MESSAGE, "Validation Failed - invalid values");
+                return results;
+            }
+        }
+
+        return processCard(request, props);
+    }
+
     public static Map ccCredit(DispatchContext ctx, Map context) {
         Map results = new HashMap();
         results.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_ERROR);
@@ -460,6 +581,19 @@
         Debug.logInfo("buildCaptureTransaction. " + at.toString(),module);
     }
 
+    private static void buildVoidTransaction(Map params, Properties props, Map AIMRequest) {
+        GenericValue at = (GenericValue)params.get("authTransaction");
+        String currency = (String) params.get("currency");
+
+        AIMRequest.put("x_Currency_Code",currency);
+        AIMRequest.put("x_Method", props.getProperty("method"));
+        AIMRequest.put("x_Type", props.getProperty("transType"));
+        AIMRequest.put("x_Trans_ID",at.get("referenceNum"));
+        AIMRequest.put("x_Auth_Code",at.get("gatewayCode"));
+
+        Debug.logInfo("buildVoidTransaction. " + at.toString(),module);
+    }
+
     private static Map validateRequest(Map params, Properties props, Map AIMRequest) {
         Map result = new HashMap();
         result.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_SUCCESS);
@@ -510,23 +644,45 @@
         Debug.logInfo("processCaptureTransResult: " + results.toString(),module);
     }
 
-    private static void processRefundTransResult(Map reply, Map results) {
+    private static Map processRefundTransResult(Map reply) {
+        Map results = new HashMap();
         AuthorizeResponse ar = (AuthorizeResponse)reply.get("authorizeResponse");
         Boolean captureResult = (Boolean)reply.get("authResult");
         results.put("refundResult", new Boolean(captureResult.booleanValue()));
         results.put("refundFlag",ar.getReasonCode());
         results.put("refundMessage",ar.getReasonText());
+        results.put("refundRefNum", ar.getResponseField(AuthorizeResponse.TRANSACTION_ID));
 
         if(captureResult.booleanValue()) { //passed
             results.put("refundCode", ar.getResponseField(AuthorizeResponse.AUTHORIZATION_CODE));
-            results.put("refundRefNum", ar.getResponseField(AuthorizeResponse.TRANSACTION_ID));
             results.put("refundAmount", new Double(ar.getResponseField(AuthorizeResponse.AMOUNT)));
         } else {
             results.put("refundAmount", new Double("0.00"));
-
         }
 
         Debug.logInfo("processRefundTransResult: " + results.toString(),module);
+        return results;
+    }
+
+    private static Map processReleaseTransResult(Map reply) {
+        Map results = new HashMap();
+        AuthorizeResponse ar = (AuthorizeResponse)reply.get("authorizeResponse");
+        Boolean captureResult = (Boolean)reply.get("authResult");
+        results.put("releaseResult", new Boolean(captureResult.booleanValue()));
+        results.put("releaseFlag",ar.getReasonCode());
+        results.put("releaseMessage",ar.getReasonText());
+        results.put("releaseRefNum", ar.getResponseField(AuthorizeResponse.TRANSACTION_ID));
+
+        if(captureResult.booleanValue()) { //passed
+            results.put("releaseCode", ar.getResponseField(AuthorizeResponse.AUTHORIZATION_CODE));
+            results.put("releaseAmount", new Double(ar.getResponseField(AuthorizeResponse.AMOUNT)));
+        } else {
+            results.put("releaseAmount", new Double("0.00"));
+
+        }
+
+        Debug.logInfo("processReleaseTransResult: " + results.toString(),module);
+        return results;
     }
 
     private static void processAuthCaptureTransResult(Map reply, Map results) {