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) { |
Free forum by Nabble | Edit this page |