508 lines
No EOL
20 KiB
Text
508 lines
No EOL
20 KiB
Text
<!---
|
||
$Id$
|
||
|
||
Copyright 2008 Brian Ghidinelli (http://www.ghidinelli.com/)
|
||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); you
|
||
may not use this file except in compliance with the License. You may
|
||
obtain a copy of the License at:
|
||
|
||
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
||
Unless required by applicable law or agreed to in writing, software
|
||
distributed under the License is distributed on an "AS IS" BASIS,
|
||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
See the License for the specific language governing permissions and
|
||
limitations under the License.
|
||
--->
|
||
<cfcomponent displayname="Base iTransact XML Interface" extends="cfpayment.api.gateway.base" hint="Common functionality for iTransact/PaymentClearing.com Gateways" output="false">
|
||
|
||
<cfset variables.cfpayment.GATEWAY_NAME = "iTransact" />
|
||
<cfset variables.cfpayment.GATEWAY_VERSION = "1.0" />
|
||
<cfset variables.cfpayment.GATEWAY_LIVE_URL = "https://secure.paymentclearing.com:8180/registered/GatewayServlet" />
|
||
<cfset variables.cfpayment.GATEWAY_TEST_URL = "https://secure.test.itransact.com/gateway/registered/GatewayServlet" />
|
||
|
||
<!--- shared process wrapper with gateway/transaction error handling --->
|
||
<cffunction name="process" output="false" access="private" returntype="any">
|
||
<cfargument name="payload" type="string" required="true" />
|
||
|
||
<cfset var p = structNew() />
|
||
<cfset var response = "" />
|
||
<cfset var xmlResponse = "" />
|
||
|
||
<!--- create options array; iTransact expects XML as a string under FORM variable "xml"; not sure if case-sensitive --->
|
||
<cfset p["xml"] = arguments.payload />
|
||
<cfset p["username"] = getUsername() />
|
||
<cfset p["password"] = getPassword() />
|
||
|
||
<!--- send it over the wire using the base gateway --->
|
||
<cfset response = createResponse(argumentCollection = super.process(payload = p)) />
|
||
|
||
<!--- we do some meta-checks for gateway-level errors (as opposed to auth/decline errors) --->
|
||
<cfif NOT response.hasError()>
|
||
|
||
<!--- we need to have a result we can parse; otherwise that's an error in itself --->
|
||
<cfif len(response.getResult()) AND isXML(response.getResult())>
|
||
|
||
<cfset xmlResponse = xmlParse(response.getResult()) />
|
||
|
||
<!--- store parsed result --->
|
||
<cfset response.setParsedResult(xmlResponse) />
|
||
|
||
<!--- handle common response fields --->
|
||
<cfif structKeyExists(xmlResponse.xmlRoot, "InternalId")>
|
||
<cfset response.setTransactionID(xmlResponse.xmlRoot.InternalId.XmlText) />
|
||
</cfif>
|
||
<cfif structKeyExists(xmlResponse.xmlRoot, "ErrorMessage")>
|
||
<cfset response.setMessage(xmlResponse.xmlRoot.ErrorMessage.XmlText) />
|
||
</cfif>
|
||
<cfif structKeyExists(xmlResponse.xmlRoot, "ApprovalCode")>
|
||
<cfset response.setAuthorization(xmlResponse.xmlRoot.ApprovalCode.XmlText) />
|
||
</cfif>
|
||
|
||
<cfif isTransactionResponseError(xmlResponse)>
|
||
|
||
<!--- transaction error means somethign bad happened *before* the transaction could be run but that request structure is OK (so an iTransact problem?) --->
|
||
<cfset response.setStatus(getService().getStatusFailure()) />
|
||
<cfset response.setMessage(getTransactionResponseError(xmlResponse)) />
|
||
|
||
<cfelseif isGatewayFailure(xmlResponse)>
|
||
|
||
<!--- gateway failure means something happened *before* transaction was run: doesn't contain authentication fields, can't be mapped to a valid request structure, or has some other fatal initialization issues. --->
|
||
<cfset response.setStatus(getService().getStatusFailure()) />
|
||
<cfset response.setMessage(getGatewayFailure(xmlResponse)) />
|
||
|
||
<cfelse>
|
||
|
||
<!--- handle common "success" fields --->
|
||
<cfif structKeyExists(xmlResponse.xmlRoot, "AVSResponse")>
|
||
<cfset response.setAVSCode(xmlResponse.xmlRoot.AVSResponse.XmlText) />
|
||
</cfif>
|
||
<cfif structKeyExists(xmlResponse.xmlRoot, "CVV2Response")>
|
||
<cfset response.setCVVCode(xmlResponse.xmlRoot.CVV2Response.XmlText) />
|
||
</cfif>
|
||
|
||
</cfif>
|
||
|
||
<cfelse>
|
||
|
||
<!--- this is bad, because iTransact didn't return XML. Uh oh! --->
|
||
<cfset response.setStatus(getService().getStatusUnknown()) />
|
||
|
||
</cfif>
|
||
|
||
</cfif>
|
||
|
||
<cfreturn response />
|
||
|
||
</cffunction>
|
||
|
||
|
||
<!--- shared methods between credit card and e-checks --->
|
||
<cffunction name="credit" output="false" access="public" returntype="any" hint="Credit all or part of a previous transaction">
|
||
<cfargument name="money" type="any" required="true" />
|
||
<cfargument name="transactionid" type="any" required="true" />
|
||
<cfargument name="options" type="struct" required="false" />
|
||
|
||
<cfset var xmlRequest = "" />
|
||
<cfset var xmlResponse = "" />
|
||
<cfset var response = "" />
|
||
|
||
<cfoutput>
|
||
<cfxml variable="xmlRequest">
|
||
<IdBasedCreditRequest>
|
||
<CentAmount>#arguments.money.getCents()#</CentAmount>
|
||
<InternalId>#arguments.transactionid#</InternalId>
|
||
<ExternalId>#arguments.options.ExternalId#</ExternalId>
|
||
<MerchantAccountNumber>#getMerchantAccount()#</MerchantAccountNumber>
|
||
</IdBasedCreditRequest>
|
||
</cfxml>
|
||
</cfoutput>
|
||
|
||
<!--- run thru local process routine which normalizes the response --->
|
||
<cfset response = process(toString(xmlRequest)) />
|
||
|
||
<!--- see if the response is already an error --->
|
||
<cfif NOT response.hasError() AND isXML(response.getResult())>
|
||
|
||
<!--- the raw result is XML, parse it --->
|
||
<cfset xmlResponse = xmlParse(response.getResult()) />
|
||
|
||
<!--- normalize status and responses --->
|
||
<!--- see if the response object has a successful root node: <CardAuthResponseOK> --->
|
||
<cfif isCardAuthOK(xmlResponse)>
|
||
|
||
<cfset response.setStatus(getService().getStatusSuccessful()) />
|
||
<cfset response.setAuthorization(xmlResponse.xmlRoot.ApprovalCode.XmlText) />
|
||
<cfset response.setTransactionID(xmlResponse.xmlRoot.InternalId.XmlText) />
|
||
|
||
<cfelseif isCardAuthError(xmlResponse)>
|
||
|
||
<cfset response.setStatus(getService().getStatusDeclined()) />
|
||
<cfset response.setTransactionID(xmlResponse.xmlRoot.InternalId.XmlText) />
|
||
<cfset response.setMessage(getCardAuthError(xmlResponse)) />
|
||
|
||
</cfif>
|
||
|
||
</cfif>
|
||
|
||
<cfreturn response />
|
||
</cffunction>
|
||
|
||
|
||
<cffunction name="void" output="false" access="public" returntype="any" hint="">
|
||
<cfargument name="transactionid" type="any" required="true" />
|
||
<cfargument name="options" type="struct" required="true" />
|
||
|
||
<cfset var xmlRequest = "" />
|
||
<cfset var response = "" />
|
||
|
||
<cfoutput>
|
||
<cfxml variable="xmlRequest">
|
||
<IdBasedVoidRequest>
|
||
<ExternalId>#arguments.options.ExternalId#</ExternalId>
|
||
<InternalId>#arguments.transactionid#</InternalId>
|
||
<MerchantAccountNumber>#getMerchantAccount()#</MerchantAccountNumber>
|
||
</IdBasedVoidRequest>
|
||
</cfxml>
|
||
</cfoutput>
|
||
|
||
<!--- run thru local process routine which normalizes the response --->
|
||
<cfset response = process(toString(xmlRequest)) />
|
||
|
||
<!--- see if the response is already an error --->
|
||
<cfif NOT response.hasError() AND isXML(response.getResult())>
|
||
|
||
<!--- the raw result is XML, parse it --->
|
||
<cfset xmlResponse = xmlParse(response.getResult()) />
|
||
|
||
<!--- normalize status and responses --->
|
||
<!--- see if the response object has a successful root node: <CardAuthResponseOK> --->
|
||
<cfif isVoid(xmlResponse) OR isCardAuthOK(xmlResponse)>
|
||
|
||
<cfset response.setStatus(getService().getStatusSuccessful()) />
|
||
<cfset response.setAuthorization(xmlResponse.xmlRoot.ApprovalCode.XmlText) />
|
||
<cfset response.setTransactionID(xmlResponse.xmlRoot.InternalId.XmlText) />
|
||
|
||
<cfelseif isCardAuthError(xmlResponse)>
|
||
|
||
<!--- the void failed for some reason; i'm not sure if we'll ever make it here
|
||
documentation says a CardAuthResponseError can come back but I think we only
|
||
get Gateway Failures for "transaction not found" --->
|
||
<cfset response.setStatus(getService().getStatusDeclined()) />
|
||
<cfset response.setTransactionID(xmlResponse.xmlRoot.InternalId.XmlText) />
|
||
<cfset response.setMessage(getCardAuthError(xmlResponse)) />
|
||
|
||
</cfif>
|
||
|
||
</cfif>
|
||
|
||
<cfreturn response />
|
||
</cffunction>
|
||
|
||
|
||
<cffunction name="settle" output="false" access="public" returntype="any" hint="">
|
||
<cfargument name="options" type="struct" required="true" />
|
||
|
||
<cfset var xmlRequest = "" />
|
||
<cfset var response = "" />
|
||
|
||
<cfoutput>
|
||
<cfxml variable="xmlRequest">
|
||
<SettlementRequest>
|
||
<ExternalId>#arguments.options.ExternalId#</ExternalId>
|
||
<MerchantAccountNumber>#getMerchantAccount()#</MerchantAccountNumber>
|
||
</SettlementRequest>
|
||
</cfxml>
|
||
</cfoutput>
|
||
|
||
<!--- return the response which will need to be parsed via response.getResult() --->
|
||
<cfset response = process(toString(xmlRequest)) />
|
||
|
||
<!--- see if the response is already an error --->
|
||
<cfif NOT response.hasError() AND isXML(response.getResult())>
|
||
|
||
<!--- the raw result is XML, parse it --->
|
||
<cfset xmlResponse = xmlParse(response.getResult()) />
|
||
|
||
<!--- if we have a SettlementResponseOk node, it came back ok --->
|
||
<cfif lcase(xmlResponse.xmlRoot.xmlName) EQ lcase("SettlementResponseOk")>
|
||
|
||
<cfset response.setStatus(getService().getStatusSuccessful()) />
|
||
<cfset response.setTransactionID(xmlResponse.xmlRoot.InternalId.XmlText) />
|
||
|
||
</cfif>
|
||
|
||
</cfif>
|
||
|
||
<cfreturn response />
|
||
</cffunction>
|
||
|
||
|
||
|
||
<!--- function to get a copy of the actual transaction response
|
||
|
||
Only requests that have valid structure and therefore reach the processing modules are available for this.
|
||
This means that any request that would return <GatewayResponseFail> will not be loadable.
|
||
If a ReportResponseFail is returned from this request, it is most likely that the original request resulted
|
||
in a GatewayResponseFail response.
|
||
--->
|
||
<cffunction name="status" output="false" access="public">
|
||
<cfargument name="transactionid" type="any" required="false" default="" hint="If checking status of a transaction with unknown response, this may not be known and can be blank" />
|
||
<cfargument name="options" type="any" required="false" default="#structNew()#" />
|
||
|
||
<cfset var xmlRequest = "" />
|
||
<cfset var xmlResponse = "" />
|
||
<cfset var response = "" />
|
||
|
||
<cfif NOT (len(arguments.transactionid) OR (structKeyExists(arguments.options, "ExternalID") AND len(arguments.options.ExternalID)))>
|
||
<cfthrow type="cfpayment.MissingParameter.Argument" detail="Requires either tranactionid or ExternalID to determine status" />
|
||
</cfif>
|
||
|
||
<cfoutput>
|
||
<cfxml variable="xmlRequest">
|
||
<TransactionResponseReportRequest>
|
||
<cfif structKeyExists(arguments.options, "ExternalId")>
|
||
<ExternalId>#arguments.options.ExternalID#</ExternalId>
|
||
<MerchantAccountNumber>#getMerchantAccount()#</MerchantAccountNumber>
|
||
<cfelse>
|
||
<InternalId>#arguments.transactionid#</InternalId>
|
||
</cfif>
|
||
</TransactionResponseReportRequest>
|
||
</cfxml>
|
||
</cfoutput>
|
||
|
||
<!--- run thru local process routine which normalizes the response --->
|
||
<cfset response = process(toString(xmlRequest)) />
|
||
|
||
<!--- see if the response is already an error --->
|
||
<cfif NOT response.hasError() AND isXML(response.getResult())>
|
||
|
||
<!--- the raw result is XML, parse it --->
|
||
<cfset xmlResponse = xmlParse(response.getResult()) />
|
||
|
||
<!--- normalize status and responses --->
|
||
<!--- see if the response object has a successful root node --->
|
||
<cfif listFindNoCase("CheckAuthResponseOK,CardAuthResponseOK", xmlResponse.xmlRoot.xmlName)>
|
||
|
||
<cfset response.setStatus(getService().getStatusSuccessful()) />
|
||
|
||
<cfelseif listFindNoCase("CheckAuthResponseError,CardAuthResponseError", xmlResponse.xmlRoot.xmlName)>
|
||
|
||
<cfset response.setStatus(getService().getStatusDeclined()) />
|
||
|
||
<cfelse>
|
||
|
||
<!--- usually means original transaction resulted in GatewayResponseFail --->
|
||
<cfset response.setStatus(getService().getStatusFailure()) />
|
||
<cfset response.setMessage(xmlResponse.xmlRoot.ErrorMessage.XmlText) />
|
||
|
||
</cfif>
|
||
|
||
</cfif>
|
||
|
||
<cfreturn response />
|
||
</cffunction>
|
||
|
||
|
||
|
||
<!--- PRIVATE HELPER METHODS FOR PARSING XML ------------------------------
|
||
|
||
Based upon iTransact Documentation as of 6/24/2006
|
||
Used in production through 11/8/2008
|
||
|
||
----------------------------------------------------------------------- --->
|
||
|
||
<!--- boolean test to see if the check transaction failed --->
|
||
<cffunction name="isTransactionResponseError" output="false" access="private" returntype="boolean">
|
||
<cfargument name="xmlObject" type="any" required="true" />
|
||
<!--- first format looks like:
|
||
|
||
<TransactionResponseFail>
|
||
<ErrorCategory>text</ErrorCategory>
|
||
<ErrorCode>text</ErrorCode>
|
||
<ErrorMessage>text</ErrorMessage>
|
||
<InternalId>text</InternalId>
|
||
</TransactionResponseFail>
|
||
|
||
<cfswitch expression="#xmlObject.xmlNode.ErrorMessage#">
|
||
<cftry value="EXPIRED CARD">
|
||
Expired Card
|
||
</cftry>
|
||
...
|
||
<cfcase value="PROCESSOR_CONNECTION,PROCESSOR_ERROR">
|
||
<cfset msg = "The transaction was temporarily declined because we could not obtain an answer from your bank. Please resubmit your payment in a few minutes or try an e-check.">
|
||
</cfcase>
|
||
<cftry value="REQ. EXCEEDS BALANCE">
|
||
Request exceeds available balance o ncard
|
||
</cftry>
|
||
<cfdefaultcase>
|
||
|
||
</cfdefaultcase>
|
||
</cfswitch>
|
||
|
||
--->
|
||
|
||
<!--- see if the response object has an error root node: <CardAuthResponseError> or <TransactionResponseFail> --->
|
||
<cfif lcase(xmlObject.xmlRoot.xmlName) EQ lcase("TransactionResponseFail")>
|
||
<cfreturn true />
|
||
<cfelse>
|
||
<cfreturn false />
|
||
</cfif>
|
||
</cffunction>
|
||
|
||
|
||
<!--- return a string message for the cause of the error --->
|
||
<cffunction name="getTransactionResponseError" output="false" access="private" returntype="string">
|
||
<cfargument name="xmlObject" type="any" required="true" />
|
||
|
||
<cfset var msg = "" />
|
||
<!--- first format looks like:
|
||
<TransactionResponseFail>
|
||
<ErrorCategory>text</ErrorCategory>
|
||
<ErrorCode>text</ErrorCode>
|
||
<ErrorMessage>text</ErrorMessage>
|
||
<InternalId>text</InternalId>
|
||
</TransactionResponseFail>
|
||
--->
|
||
|
||
<cfif IsDefined("xmlObject.xmlRoot.ErrorMessage.xmlText")>
|
||
|
||
<cfswitch expression="#xmlObject.xmlRoot.ErrorMessage#">
|
||
<cfcase value="PROCESSOR_CONNECTION,PROCESSOR_ERROR">
|
||
<cfset msg = "The transaction was temporarily declined because we could not obtain an answer from the bank. Please resubmit your payment in a few minutes." />
|
||
</cfcase>
|
||
<cfcase value="SYSTEM_ERROR">
|
||
<cfset msg = "The transaction was temporarily unable to be processed by the bank due to a system error. Please resubmit your payment in a few minutes." />
|
||
</cfcase>
|
||
<cfdefaultcase>
|
||
<cfset msg = "There was a transaction error connecting with the bank caused by: " & xmlObject.xmlRoot.ErrorMessage.xmlText />
|
||
</cfdefaultcase>
|
||
</cfswitch>
|
||
|
||
<cfelse>
|
||
<cfset msg = "No error description found. Please contact the administrators for help. The error object is: #htmlEditFormat(toString(xmlObject))#" />
|
||
</cfif>
|
||
|
||
<!--- return msg --->
|
||
<cfreturn msg />
|
||
</cffunction>
|
||
|
||
|
||
<!--- boolean test to see if the gateway had a failure --->
|
||
<cffunction name="isGatewayFailure" output="false" access="private" returntype="boolean">
|
||
<cfargument name="xmlObject" type="any" required="true" />
|
||
<!--- format looks like:
|
||
<GatewayResponseFail>
|
||
<ErrorCategory>DATA_VALIDATION</ErrorCategory>
|
||
<ErrorCode>castor.Unmarshal.unmarshal.2</ErrorCode>
|
||
<ErrorMessage>unable to add text content to CardAccountNumber due to the following error: java.lang.IllegalStateException: Field access error: account(java.lang.String) access resulted in exception: null</ErrorMessage>
|
||
</GatewayResponseFail>
|
||
--->
|
||
|
||
<!--- see if the response object has an error root node: <CardAuthResponseError> --->
|
||
<cfif lcase(xmlObject.xmlRoot.xmlName) EQ lcase("GatewayResponseFail")>
|
||
<cfreturn true />
|
||
<cfelse>
|
||
<cfreturn false />
|
||
</cfif>
|
||
</cffunction>
|
||
|
||
|
||
<!--- method to return the gateway failure error --->
|
||
<cffunction name="getGatewayFailure" output="false" access="private" returntype="string">
|
||
<cfargument name="xmlObject" type="any" required="true" />
|
||
|
||
<cfset var msg = "" />
|
||
<!--- format looks like:
|
||
<GatewayResponseFail>
|
||
<ErrorCategory>DATA_VALIDATION</ErrorCategory>
|
||
<ErrorCode>castor.Unmarshal.unmarshal.2</ErrorCode>
|
||
<ErrorMessage>unable to add text content to CardAccountNumber due to the following error: java.lang.IllegalStateException: Field access error: account(java.lang.String) access resulted in exception: null</ErrorMessage>
|
||
</GatewayResponseFail>
|
||
|
||
ErrorCategory:
|
||
DATABASE - Database Error
|
||
<09> DATA_VALIDATION - Error validating data within code. Not request specific.
|
||
<09> PROCESSOR_CONNECTION - There was an error while connecting to the processing network.
|
||
<09> PROCESSOR_DENIED - The processor denied the authorization.
|
||
<09> PROCESSOR_ERROR - The processing network reported an internal problem.
|
||
<09> REQUEST_VALIDATION - The request could not be processed because of a problem with the
|
||
request. This could be becouse of formatting, missing data, invalid data, etc.
|
||
<09> SECURITY - The request could not be processed because it would violate system security constraints.
|
||
<09> SYSTEM_CONFIG - The iTransact Gateway is not configured correctly, please report.
|
||
<09> SYSTEM_ERROR - There was a general system error while processing the request, please report.
|
||
<09> UNSPECIFIED - There was an unexpected error processing the transaction.
|
||
--->
|
||
|
||
<cfif IsDefined("xmlObject.xmlRoot.ErrorMessage.xmlText")>
|
||
<cfswitch expression="#xmlObject.xmlRoot.ErrorMessage.xmlText#">
|
||
<!--- cfcase value="DATABASE">
|
||
<cfset msg = "">
|
||
</cfcase>
|
||
<cfcase value="DATA_VALIDATION">
|
||
<cfset msg = "">
|
||
</cfcase>
|
||
<cfcase value="PROCESSOR_CONNECTION">
|
||
<cfset msg = "">
|
||
</cfcase --->
|
||
<cfdefaultcase>
|
||
<cfset msg = "The transaction was declined by the bank because: #xmlObject.xmlRoot.ErrorMessage.xmlText#" />
|
||
</cfdefaultcase>
|
||
</cfswitch>
|
||
<cfelse>
|
||
<cfset msg = "No error description found. Please contact the administrators for help. The error object is: #toString(xmlObject)#" />
|
||
</cfif>
|
||
|
||
<!--- see if the response object has an error root node: <CardAuthResponseError> --->
|
||
<cfreturn msg />
|
||
</cffunction>
|
||
|
||
|
||
<cffunction name="isTransaction" output="false" access="private" returntype="boolean">
|
||
<cfargument name="xmlObject" type="any" required="true" />
|
||
<!---
|
||
If a transaction does not exist in the DB, this is the error we'll get:
|
||
<?xml version="1.0"?>
|
||
<ReportResponseFail>
|
||
<ErrorCategory>DATABASE</ErrorCategory>
|
||
<ErrorCode>trans.RequestResponseMap.DBLoadByExternalTransactionId.1</ErrorCode>
|
||
<ErrorMessage>No record for externalId 18317065 and merchantAccountNumber 13588</ErrorMessage>
|
||
<InternalId>21794289</InternalId>
|
||
</ReportResponseFail>
|
||
--->
|
||
|
||
<!--- see if the response object has an error root node: <ReportResponseFail> --->
|
||
<cfif lcase(xmlObject.xmlRoot.xmlName) EQ lcase("ReportResponseFail")>
|
||
<cfreturn false />
|
||
<cfelse>
|
||
<cfreturn true />
|
||
</cfif>
|
||
</cffunction>
|
||
|
||
|
||
<cffunction name="isVoid" output="false" access="private" returntype="boolean">
|
||
<cfargument name="xmlObject" type="any" required="true" />
|
||
<!---
|
||
If a transaction has been voided, its previously successful response will come back as a VOID:
|
||
|
||
<CardAuthResponseOk>
|
||
<ApprovalCode> SALE</ApprovalCode>
|
||
<BatchNumber>877</BatchNumber>
|
||
<CVV2Response></CVV2Response>
|
||
<InternalId>21795510</InternalId>
|
||
<ProcessorTransactionId>010</ProcessorTransactionId>
|
||
</CardAuthResponseOk>
|
||
--->
|
||
|
||
<!--- see if the response object has root node: <CardAuthResponseOk> --->
|
||
<!--- The first character or two in " SALE" is NOT a space, it's some other character --->
|
||
<cfif lcase(xmlObject.xmlRoot.xmlName) EQ lcase("CardAuthResponseOk") AND lcase(trim(right(xmlObject.xmlRoot.ApprovalCode.XmlText, 4))) EQ "sale">
|
||
<cfreturn true />
|
||
<cfelse>
|
||
<cfreturn false />
|
||
</cfif>
|
||
</cffunction>
|
||
|
||
|
||
</cfcomponent> |