<!---
	$Id$

	Copyright 2011 Shawn Mckee (http://www.clinicapps.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="GoEMerchant Gateway" extends="cfpayment.api.gateway.base" output="false" hint="Provides credit card process for GoEMerchant">

	<!--- gateway specific variables --->
	<cfset variables.cfpayment.GATEWAYID = "1" />
	<cfset variables.cfpayment.GATEWAY_NAME = "GoEmerchant Gateway" />
	<cfset variables.cfpayment.GATEWAY_VERSION = "1" />
	<cfset variables.cfpayment.GATEWAY_LIVE_URL = "https://secure.goemerchant.com/secure/gateway/xmlgateway.aspx" />
	<cfset variables.cfpayment.GATEWAY_TEST_URL = variables.cfpayment.GATEWAY_LIVE_URL /> <!--- test mode determined by processor, mid, and tid --->
	<cfset variables.cfpayment.MerchantAccount = "" />
	<cfset variables.cfpayment.Username = "" />
	<cfset variables.cfpayment.Password = "" />
	<cfset variables.cfpayment.TestMode = true />
	<cfset variables.cfpayment.processor = "">
	<cfset variables.cfpayment.mid = "" />
	<cfset variables.cfpayment.tid = "" />


	<cffunction name="init" access="public" output="false" returntype="any">
		<cfset super.init(argumentCollection = arguments) />

		<cfif variables.cfpayment.TestMode>
			<cfset variables.cfpayment.processor = "sandbox">
			<cfset variables.cfpayment.mid = arguments.config.mid />
			<cfset variables.cfpayment.tid = arguments.config.tid />
		</cfif>

		<cfreturn this />
	</cffunction>


	<!--- implemented functions --->
    <cffunction name="process" access="private" returntype="struct" output="true">
		<cfargument name="payload" type="string" required="true" />
		<cfargument name="options" type="struct" required="true" />

		<cfset var local = structNew() />
		<cfoutput>
			<cfsavecontent variable="local.strXML">
				<?xml version="1.0" encoding="UTF-8"?>
			    <TRANSACTION>
			      <FIELDS>
			        <FIELD KEY="transaction_center_id">#variables.cfpayment.userName#</FIELD>
			        <FIELD KEY="gateway_id">#variables.cfpayment.MerchantAccount#</FIELD>
			       	<FIELD KEY="mid">#variables.cfpayment.mid#</FIELD>
			        <FIELD KEY="tid">#variables.cfpayment.tid#</FIELD>
			        <FIELD KEY="processor">#variables.cfpayment.processor#</FIELD>
			        <FIELD KEY="operation_type">#arguments.options.operationType#</FIELD>
					<cfoutput>
						#arguments.payload#
					</cfoutput>
			        <cfif !find("::1", cgi.REMOTE_ADDR)>
			            <FIELD KEY="remote_ip_address">#cgi.REMOTE_ADDR#</FIELD>
			       	<cfelse>
			            <FIELD KEY="remote_ip_address">127.0.0.1</FIELD>
					</cfif>
			      </FIELDS>
			    </TRANSACTION>
			</cfsavecontent>
		</cfoutput>

		<cfset local.h = { "Content-Type" = "text/xml" } />
		<cfset local.p = local.strXML.trim()>
		<cfset local.processorData = createResponse(argumentCollection = super.process(payload = local.p, headers=local.h)) />


		<cfif !local.processorData.hasError()>
	        <cfset local.processorXMLData = XmlParse(local.processorData.getResult())>

			<cfset local.fieldNodes = xmlSearch(local.processorXMLData,'/RESPONSE/FIELDS/FIELD')>

		    <cfloop index="local.field" array="#local.fieldNodes#">
		        <cfset local.response[field.XmlAttributes.Key] = field.XmlText />
		    </cfloop>
			<cfif structKeyExists(local.response, "status")>
				<cfif local.response.status EQ 0>
					<cfset local.processorData.setStatus(getService().getStatusUnprocessed()) />
					<cfset local.processorData.setAuthorization(local.response.auth_code) />
					<cfset local.processorData.setTransactionID(local.response.reference_number) /> <!--- generated by GoEMerchant --->
					<cfset local.processorData.setTokenID(local.response.order_id) /> <!--- your order ID sent to GoEMerchant --->
					<cfset local.processorData.setMessage(local.response.error) />

				<cfelseif local.response.status EQ 1>

					<cfset local.processorData.setStatus(getService().getStatusSuccessful()) />
					<cfset local.processorData.setAuthorization(local.response.auth_code) />
					<cfset local.processorData.setTransactionID(local.response.reference_number) /> <!--- generated by GoEMerchant --->
					<cfset local.processorData.setTokenID(local.response.order_id) /> <!--- your order ID sent to GoEMerchant --->

					<!--- leave avs response blank if no answer is returned --->
					<cfif len(trim(local.response.avs_code))>
						<cfset local.processorData.setAVSCode(local.response.avs_code) />
					</cfif>

					<!--- leave cvv2 response blank if no answer is returned --->
					<cfif len(trim(local.response.cvv2_code))>
						<cfset local.processorData.setCVVCode(local.response.cvv2_code) />
					</cfif>

					<cfset local.processorData.setMessage(local.response.auth_response) />

				<cfelseif local.response.status EQ 2>
					<cfif trim(local.response.auth_code) EQ "">
						<cfset local.processorData.setStatus(getService().getStatusDeclined()) />
						<cfset local.processorData.setAuthorization(local.response.auth_code) />
						<cfset local.processorData.setTransactionID(local.response.reference_number) /> <!--- generated by GoEMerchant --->
						<cfset local.processorData.setTokenID(local.response.order_id) /> <!--- your order ID sent to GoEMerchant --->
						<cfset local.processorData.setMessage("Declined") />
					<cfelse>
						<cfset local.processorData.setStatus(getService().getStatusFailure()) />
						<cfset local.processorData.setAuthorization(local.response.auth_code) />
						<cfset local.processorData.setTransactionID(local.response.reference_number) /> <!--- generated by GoEMerchant --->
						<cfset local.processorData.setTokenID(local.response.order_id) /> <!--- your order ID sent to GoEMerchant --->
						<cfset local.processorData.setMessage("Auth Code: '#local.response.auth_code#' - Auth Response: '#local.response.auth_response#' - Error: '#local.response.error#'") />
					</cfif>
				<cfelse>
					<cfset local.processorData.setStatus(getService().getStatusUnknown()) />
					<cfset local.processorData.setTokenID(arguments.options.order_ID) />
					<cfset local.processorData.setMessage("GoEMerchant status unrecognized: #local.response.status# ERROR: #local.response.error#") />
				</cfif>
			<cfelseif structKeyExists(local.response, "status1")> <!--- void, credit or settle --->
				<cfif local.response.status1 EQ 1>
					<cfset local.processorData.setStatus(getService().getStatusSuccessful()) />
					<cfif structKeyExists(local.response, "batch_number1")>
						<cfset local.processorData.setTransactionID(local.response.batch_number1) /> <!--- generated by GoEMerchant for settle --->
					</cfif>
					<cfset local.processorData.setTokenID(arguments.options.order_ID) /> <!--- your order ID sent to GoEMerchant --->
				<cfelseif local.response.status1 EQ 2>
					<cfset local.processorData.setStatus(getService().getStatusFailure()) />
					<cfset local.processorData.setTransactionID(local.response.reference_number1) /> <!--- generated by GoEMerchant --->
					<cfset local.processorData.setTokenID(arguments.options.order_ID) /> <!--- your order ID sent to GoEMerchant --->
					<cfset local.processorData.setMessage(local.response.response1) />
				<cfelse>
					<cfset local.processorData.setStatus(getService().getStatusUnknown()) />
					<cfset local.processorData.setTokenID(arguments.options.order_ID) />
					<cfset local.processorData.setMessage("Error: '#local.response.error1#' - Response: 'local.response.response1'") />
				</cfif>
			<cfelse>
				<cfset local.processorData.setStatus(getService().getStatusUnknown()) />
				<cfset local.processorData.setTransactionID(arguments.options.order_ID) />
				<cfset local.processorData.setMessage("GoEMerchant failed to return status") />
			</cfif>

		<cfelse>

			<cfset local.processorData.setStatus(getService().getStatusUnknown()) />
			<cfset local.processorData.setTransactionID(arguments.options.order_ID) />
			<cfset local.processorData.setMessage("System error") />

		</cfif>

		<cfreturn local.processorData>

	</cffunction>


	<cffunction name="purchase" output="false" access="public" returntype="any" hint="Perform an authorization immediately followed by a capture">
		<cfargument name="money"   type="struct" required="true" />
		<cfargument name="account" type="struct" required="true" />
		<cfargument name="options" type="struct" required="true" />

		<cfset var local = structNew() />
		<cfset arguments.options.operationType = "sale" /> <!--- used in process method --->
		<cfset local.ccNum = arguments.account.getAccount() />
		<cfset local.payload = '<FIELD KEY="order_id">#arguments.options.order_ID#</FIELD>
			            <FIELD KEY="total">#numberFormat(arguments.money.getAmount(),'9999.99')#</FIELD>
			            <FIELD KEY="card_name">#getCardType(local.ccNum).shortName#</FIELD>
			            <FIELD KEY="card_number">#local.ccNum#</FIELD>
			            <FIELD KEY="card_exp"> #numberFormat(arguments.account.getMonth(), "00")##right(arguments.account.getYear(), 2)#</FIELD>
			            <FIELD KEY="owner_name">#arguments.account.getFirstName()# #arguments.account.getLastName()#</FIELD>
			            <FIELD KEY="owner_street"></FIELD>
			            <FIELD KEY="owner_city"></FIELD>
			            <FIELD KEY="owner_state"></FIELD>
			            <FIELD KEY="owner_zip">#arguments.account.getPostalCode()#</FIELD>
			            <FIELD KEY="owner_country"></FIELD>
			            <FIELD KEY="owner_email"></FIELD>
			            <FIELD KEY="owner_phone"></FIELD>' />

		<cfif trim(arguments.account.getVerificationValue()) NEQ "">
            <cfset local.payload = local.payload & '<FIELD KEY="cvv2">#arguments.account.getVerificationValue()#</FIELD>' />
		</cfif>

		<cfset local.response = process(local.payload, options) />

		<cfreturn local.response />

	</cffunction>


	<cffunction name="authorize" output="false" access="public" returntype="any" hint="Verifies payment details with merchant bank">
		<cfargument name="money"   type="struct" required="true" />
		<cfargument name="account" type="struct" required="true" />
		<cfargument name="options" type="struct" required="true" />

		<cfset var local = structNew() />
		<cfset arguments.options.operationType = "auth" /> <!--- used in process method --->
		<cfset local.ccNum = arguments.account.getAccount() />
		<cfset local.payload = '<FIELD KEY="order_id">#arguments.options.order_ID#</FIELD>
			            <FIELD KEY="total">#numberFormat(arguments.money.getAmount(),'9999.99')#</FIELD>
			            <FIELD KEY="card_name">#getCardType(local.ccNum).shortName#</FIELD>
			            <FIELD KEY="card_number">#local.ccNum#</FIELD>
			            <FIELD KEY="card_exp"> #numberFormat(arguments.account.getMonth(), "00")##right(arguments.account.getYear(), 2)#</FIELD>
			            <FIELD KEY="owner_name">#arguments.account.getFirstName()# #arguments.account.getLastName()#</FIELD>
			            <FIELD KEY="owner_street"></FIELD>
			            <FIELD KEY="owner_city"></FIELD>
			            <FIELD KEY="owner_state"></FIELD>
			            <FIELD KEY="owner_zip">#arguments.account.getPostalCode()#</FIELD>
			            <FIELD KEY="owner_country"></FIELD>
			            <FIELD KEY="owner_email"></FIELD>
			            <FIELD KEY="owner_phone"></FIELD>' />
		<cfif trim(arguments.account.getVerificationValue()) NEQ "">
            <cfset local.payload = local.payload & '<FIELD KEY="cvv2">#arguments.account.getVerificationValue()#</FIELD>' />
		</cfif>
		<cfset local.response = process(local.payload, options) />

		<cfreturn local.response />
	</cffunction>


	<!--- based on the assumption that credits and captures (settles) are done as singletons. The GoEMerchant interface can process mutliple
		 credits and captures in a single transaction but this code does not support that. --->
	<cffunction name="capture" output="false" access="public" returntype="any" hint="Confirms an authorization with direction to charge the account">
		<cfargument name="money"   type="struct" required="true" />
		<cfargument name="authorization" type="numeric" required="true" hint="Transaction ID" />
		<cfargument name="options" type="struct" required="true" hint="Must include order_id" />

		<cfset var local = structNew() />
		<cfset arguments.options.operationType = "settle" /> <!--- used in process method --->
		<cfset local.payload = '<FIELD KEY="total_number_transactions">1</FIELD><FIELD KEY="reference_number1">#arguments.authorization#</FIELD><FIELD KEY="settle_amount1">#numberFormat(arguments.money.getAmount(),'9999.99')#</FIELD>'>
		<cfset local.response = process(local.payload, arguments.options) />

		<cfreturn local.response />

	</cffunction>


	<cffunction name="credit" output="false" access="public" returntype="any" hint="">
		<cfargument name="money"   type="struct" required="true" />
		<cfargument name="transactionid" type="numeric" required="true" />
		<cfargument name="options" type="struct" required="true" hint="Must include order_id" />

		<cfset var local = structNew() />
		<cfset arguments.options.operationType = "credit" /> <!--- used in process method --->
		<cfset local.payload = '<FIELD KEY="total_number_transactions">1</FIELD><FIELD KEY="reference_number1">#arguments.transactionid#</FIELD><FIELD KEY="credit_amount1">#numberFormat(arguments.money.getAmount(),'9999.99')#</FIELD>' />
		<cfset local.response = process(local.payload, arguments.options) />

		<cfreturn local.response />

	</cffunction>


	<cffunction name="void" output="false" access="public" returntype="any" hint="">
		<cfargument name="transactionid" type="numeric" required="true" />
		<cfargument name="options" type="struct" required="true" hint="Must include order_id" />

		<cfset var local = structNew() />
		<cfset arguments.options.operationType = "void" /> <!--- used in process method --->
		<cfset local.payload = '<FIELD KEY="total_number_transactions">1</FIELD><FIELD KEY="reference_number1">#arguments.transactionid#</FIELD>' />
		<cfset local.response = process(local.payload, arguments.options) />

		<cfreturn local.response />

	</cffunction>

	<!--- this is a credit card gateway --->
	<cffunction name="getIsCCEnabled" output="false" access="public" returntype="boolean" hint="determine whether or not this gateway can accept credit card transactions">
		<cfreturn true />
	</cffunction>

	<!---
		// CardTypes              Prefix          Width
		// American Express       34, 37            15
		// Diners Club            300 to 305, 36    14
		// Carte Blanche          38                14
		// Discover               6011              16
		// EnRoute                2014, 2149        15
		// JCB                    3                 16
		// JCB                    2131, 1800        15
		// Master Card            51 to 55          16
		// Visa                   4                 13, 16
		// http://www.beachnet.com/~hstiles/cardtype.html
		// http://www.ros-soft.net/otros/cakephp_blog/creditcard.php.txt
		// http://www.rgagnon.com/javadetails/java-0034.html

		// Most comprehensive seems to be Wikipedia: http://en.wikipedia.org/wiki/Credit_card_number
	--->
	<cffunction name="getCardType" access="private" returntype="struct" output="false" hint="Card Type short/long name required by GoEmerchant gateway">
		<cfargument name="ccNum" type="numeric" required="true" />

		<cfscript>
			var local = structNew();

			switch(len(arguments.ccNum))
			{
				case 16:
					if(left(arguments.ccNum, 1) == 4)
					{
						local.returnStruct.shortName = "Visa";
						local.returnStruct.longName = "Visa";
					}
					else if(listFind("51,52,53,54,55", left(arguments.ccNum, 2)))
					{
						local.returnStruct.shortName = "MasterCard";
						local.returnStruct.longName = "Master Card";
					}
					else if(left(arguments.ccNum, 4) == 6011)
					{
						local.returnStruct.shortName = "JCB";
						local.returnStruct.longName = "JCB";
					}
					else if(left(arguments.ccNum, 1) == 3)
					{
						local.returnStruct.shortName = "JCB";
						local.returnStruct.longName = "JCB";
					}
				break;

				case 15:
					if(listFind("34,37", left(arguments.ccNum, 2)))
					{
						local.returnStruct.shortName = "Amex";
						local.returnStruct.longName = "American Express";
					}
					else if(listFind("2131,1800", left(arguments.ccNum, 4)))
					{
						local.returnStruct.shortName = "JCB";
						local.returnStruct.longName = "JCB";
					}
					else if(listFind("2014,2149", left(arguments.ccNum, 4)))
					{
						local.returnStruct.shortName = "EnRoute";
						local.returnStruct.longName = "EnRoute";
					}
				break;

				case 14:
					if(left(arguments.ccNum, 2) == 36 OR listFind("301,302,303,304,305", left(arguments.ccNum, 3)))
					{
						local.returnStruct.shortName = "DinersClub";
						local.returnStruct.longName = "Diners Club";
					}
					else if(left(arguments.ccNum, 2) == 38)
					{
						local.returnStruct.shortName = "CarteBlanche";
						local.returnStruct.longName = "Carte Blanche";
					}
				break;

				case 13:
					if(left(arguments.ccNum, 1) == 4)
					{
						local.returnStruct.shortName = "Visa";
						local.returnStruct.longName = "Visa";
					}
				break;
			}

			return local.returnStruct;

		</cfscript>

	</cffunction>

</cfcomponent>