2

I'm currently developing a site using payeezy/firstdata for payments. It has been quite a hassle integrating because their API docs are kind of weak.

I'm using ColdFusion and a cfhttp request. I've been following this to calculate my content digest and hmac hash: https://support.payeezy.com/hc/en-us/articles/203731149-API-Security-HMAC-Hash

I finally got the hashs to match the calculated hashs in the demo terminal but my problem is: I'm getting a strange error when sending the request. I get the error:

"Invalid signature received 'Fgx/lR############'."

where the first few characters change every time. Here's my code for the request:

postAction = https://api.demo.globalgatewaye4.firstdata.com/transaction/v19 key_id,hmac_value, content_digest have all been tested and are correct x_time = getIsoTimeString( now() )

<cfhttp url="#postAction#" method="POST">
            <cfhttpparam name="Authorization" type="header" value="CGGE4_API #key_id#:#hmac_value#">
            <cfhttpparam name="x-gge4-date" type="header" value="#x_time#">
            <cfhttpparam name="x-gge4-content-sha1" type="header" value="#LCase(content_digest)#">
            <cfhttpparam name="content-type" type="header" value="text/xml">
            <cfhttpparam name="accept" type="header" value="text/xml">      
            <cfhttpparam name="transaction_body" type="xml" value="#exact_xml#" />                
        </cfhttp>
<cfdump var="#cfhttp.fileContent#"><cfabort>

The submitted xml (without spaces or new lines)

<Transaction>
                <ExactID>#exact_id#</ExactID>
                <Password>#password#</Password>
                <Card_Number>#FORM.x_card_num#</Card_Number>
                <CardHoldersName>#FORM.x_first_name# #FORM.x_last_name#</CardHoldersName>
                <Transaction_Type>00</Transaction_Type>
                <Expiry_Date>#FORM.x_exp_date#</Expiry_Date>
                <DollarAmount>#amount#</DollarAmount>
                <Address>
                    <Address1>#FORM.x_address#</Address1>
                    <City>#FORM.x_city#</City>
                    <Zip>#FORM.x_zip#</Zip>
                </Address>
            </Transaction>

I've changed the authorization header to "Payeezy_Gateway_API #key_id#:#hmac_value#" and I get the error "Bad Authorization Header" when the hmac value and key id being used have been tested on the payeezy terminal many times.

Please, any help is much appreciated!

  • Just a guess, but are you including a character set in your calculations? IIRC, cfhttp adds "UTF-8" by default. The Payeezy docs say, "*If a character set is included in the Content Type header, it must be used in the calculation as well (there is no provision for this in the calculator)"*. Might be the issue. You use Fiddler to see what it is sending, or failing that, point cfhttp to a .cfm page on your server that does a dump of `GetHTTPRequestData()`. – Leigh Aug 29 '15 at 19:00
  • Also, are you certain it is "CGGE4_API"? A few of the examples use "GGE4_API" instead. Not sure which is correct. – Leigh Aug 29 '15 at 20:40

2 Answers2

2

Both CGGE4_API and GGE4_API work for me when testing with their sample Python code on that support page you linked, but I called their support number and they told me I should be using GGE4_API.

Apparently the support page is outdated, and using Payeezy_Gateway_API does not work at all. Here is how the sample Python code on that page should look like:

from hashlib import sha1
from time import gmtime, strftime
import base64
import hmac
import httplib

payeezy_gateway_date = strftime("%Y-%m-%dT%H:%M:%S", gmtime()) + 'Z'
uri = '/transaction/v19'
key_id = '' # Add your key id here
key = '' # Add your HMAC key here
transaction_body = '' # Add your transaction request body here
method = 'POST'
content_digest = sha1(transaction_body).hexdigest()
content_type = 'text/xml' # Change this to 'application/json' if you're using JSON
host = 'api.demo.globalgatewaye4.firstdata.com'
headers = { 'Content-Type': content_type,
        'x-gge4-content-sha1': content_digest,
        'x-gge4-date': payeezy_gateway_date,
        'Authorization': 'GGE4_API ' + key_id + ':' + base64.b64encode(hmac.new(key, method + "\n" + content_type + "\n" + content_digest + "\n" + payeezy_gateway_date + "\n" + uri.split('?')[0], sha1).digest()) }
conn = httplib.HTTPSConnection(host)
conn.request(method, uri, transaction_body, headers)
print conn.getresponse().read()

In my application, I was also receiving the Invalid signature received. It turned out that I was adding charset=utf-8 to my Content-Type header, (credit to Leigh's comment). Removing that made my requests get accepted.

Community
  • 1
  • 1
Rahat Ahmed
  • 2,191
  • 2
  • 30
  • 40
  • Yes, I had already tested out the python and C# examples on [coding ground](http://www.tutorialspoint.com/codingground.htm). Since they both worked, it suggested an issue with the text prefix or extra charset in the cfhttp headers, but without a confirm from the asker it did not seem definitive enough to post as an answer. Hopefully, they will post back with a "yea or nay". – Leigh Sep 10 '15 at 00:41
  • In other words, sounds like we both came to the same conclusion ;-) – Leigh Sep 10 '15 at 19:06
  • To be honest, most of what I wrote derived from your comment. I just wanted to add a more definitive answer, after confirming the details in my support calls, for future visitors. So props to you, @Leigh. – Rahat Ahmed Sep 10 '15 at 22:47
  • Well hopefully it will prompt a response from the asker, because honestly after reviewing and testing the examples, I am a bit curious about the solution to the problem :-) – Leigh Sep 11 '15 at 00:28
  • Unrelated note but someone's sure to run into it. If you get a nondescript 500 error, it may be due to malformed JSON in your request body. At least, that's what happened to me. – Rahat Ahmed Dec 16 '15 at 20:59
-1

I was also trying to solve this problem with Payeezy. I'm probably a day late and a dollar short, however I got this to work. I made several changes to what I was having trouble with and/or with Nick's original code:

  1. Removed the charset declaration in the transaction xml;
  2. Changed 'text' to 'application'
  3. For me to get the encryption to match the website's testing encryption, I changed the carriage return to char(10), and
  4. Changed the first header entry from 'CGGE4_API' to 'GGE_API'

Just FYI, if you're using v11, you don't need any headers or hashing code, just the xml.

Here's my entire testing code, including some code to break down the returning xml.

Request Code:

<cfset hmac_key="WO9QVjnis6eBb5oOYmA_DSShc82gteFw">
<cfset trans="<?xml version='1.0' ?><Transaction><ExactID>XX55555-55</ExactID><Password>testtest11</Password><Card_Number>5454545454545454</Card_Number><CardHoldersName>Bix Dirigible</CardHoldersName><Transaction_Type>00</Transaction_Type><Expiry_Date>0916</Expiry_Date><DollarAmount>12.03</DollarAmount></Transaction>">

<cfset key_id="555555">
<cfset content_digest=lcase(Hash(trans,"SHA"))>

<cfset curDate = Now()> 
<cfset utcDate = DateConvert("local2utc", curDate)> 
<cfset udate=dateformat(utcdate,"yyyy-mm-dd")><cfset utime=timeformat(utcdate,"HH:mm:ss")>
<cfset x_time=udate&"T"&utime&"Z" >

<cfset submitfinalhmac="POST"&chr(10)&"application/xml"&chr(10)&content_digest&chr(10)&x_time&chr(10)&"/transaction/v12">


<cfoutput>
    content_digest: #content_digest#<BR />
    <BR />

    <!--- Ben Nadel's encrypting code, http://www.bennadel.com/blog/1971-authenticating-twilio-request-signatures-using-coldfusion-and-hmac-sha1-hashing.htm --->
    <cfset secretKeySpec = createObject("java", "javax.crypto.spec.SecretKeySpec" ).init( toBinary( toBase64( hmac_key ) ), "HmacSHA1" )/>
    <cfset mac = createObject( "java", "javax.crypto.Mac" ).getInstance( "HmacSHA1" )/> 
    <cfset mac.init( secretKeySpec ) />
    <cfset encryptedBytes = mac.doFinal( toBinary( toBase64( submitfinalhmac ) )  ) /> 
    <cfset secureSignature = createObject( "java", "org.apache.commons.codec.binary.Base64" ).encodeBase64( encryptedBytes ) /> 
    <cfset hmac_value = toString( secureSignature ) />
    #hmac_value#
    <BR /><BR />
 </cfoutput>    

 <cfhttp method="Post" url="https://api.demo.globalgatewaye4.firstdata.com/transaction/v12"
    useragent="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.1599.69 Safari/537.36">
     <cfhttpparam name="Authorization" type="header" value="GGE4_API #key_id#:#hmac_value#">
     <cfhttpparam name="x-gge4-date" type="header" value="#x_time#">
     <cfhttpparam name="x-gge4-content-sha1" type="header" value="#content_digest#">
     <cfhttpparam name="content-type" type="header" value="application/xml">
     <cfhttpparam name="accept" type="header" value="application/xml">
     <cfhttpparam name="transaction_body" type="xml" value="#trans#" />  
</cfhttp>

Response code:

<cfset postresult=HTMLEditFormat(cfhttp.fileContent)>
<cfset postresult=replace(postresult,"&lt;","<","all")>
<cfset postresult=replace(postresult,"&gt;",">","all")>
<cfset postresult=replace(postresult,"##","-","all")>

<cfoutput> 
    #postresult#<BR />
    <cfset badtransaction=0><cfset badtransactionmessage="">
    <cfset rawerror="">
    <cfif findnocase("bad request",postresult)><cfset rawerror=trim(gettoken(postresult,2,"-"))>
        <cfset badtransactionmessage=badtransactionmessage&rawerror>
        <cfset badtransaction=1>
    </cfif>
    <cfif findnocase("unauthorized request",postresult)><cfset rawerror=trim(gettoken(postresult,2,"."))>
        <cfset badtransactionmessage=badtransactionmessage&rawerror>
        <cfset badtransaction=1>
    </cfif>

    <cfset resultarray=arraynew(2)>
    <cfset line=1>
    <cfset enterflag=0>
    <cfset startflag=0>
    <cfloop index="getchar" from="1" to="#len(postresult)-22#">
        <cfif mid(postresult,getchar,9) is "<exactid>" ><cfset startflag=1></cfif>
        <cfif mid(postresult,getchar,19) is "</TransactionResult>" ><cfset startflag=0></cfif>

        <cfif startflag is 1>
            <cfif enterflag is 2>
                <cfif mid(postresult,getchar,1)  is "<"><cfset enterflag=0><cfset line++>
            <cfelse>
                <cfset resultarray[line][2]=resultarray[line][2]&mid(postresult,getchar,1)>
            </cfif>
        </cfif>

        <cfif enterflag is 1>
            <cfif mid(postresult,getchar,1)  is ">" >
                <cfset enterflag=2>
            <cfelse>
                <cfset resultarray[line][1]=resultarray[line][1]&mid(postresult,getchar,1)>
            </cfif>
        </cfif>

        <cfif enterflag is 0>
            <cfif mid(postresult,getchar,1)  is "<" and mid(postresult,getchar+1,1) is not "/">
                <cfset enterflag=1>
                <cfset resultarray[line][1]="">
                <cfset resultarray[line][2]="">
            </cfif>
        </cfif>
    </cfif>
   </cfloop>

    <cfdump var="#resultarray#">

    <cfset transactiontag="">
    <cfset authorizationnum="">
    <cfset transactionapproved="">
    <cfset exactmessage="">
    <cfset exactresponsecode="">
    <cfset sequenceno="">
    <cfset retrievalrefno="">
    <cfset cardtype="">
    <cfset bankmessage="">

    <cfloop index="getresponses" from="1" to ="#arraylen(resultarray)#">
        <cfif resultarray[getresponses][1] is "Transaction_Tag"><cfset transactiontag=resultarray[getresponses][2]></cfif>
        <cfif resultarray[getresponses][1] is "Authorization_Num"><cfset AuthorizationNum=resultarray[getresponses][2]></cfif>
        <cfif resultarray[getresponses][1] is "Transaction_Approved"><cfset TransactionApproved=resultarray[getresponses][2]></cfif>
        <cfif resultarray[getresponses][1] is "EXact_Message"><cfset EXactMessage=resultarray[getresponses][2]></cfif>
        <cfif resultarray[getresponses][1] is "EXact_Resp_Code"><cfset EXactResponseCode=resultarray[getresponses][2]></cfif>
        <cfif resultarray[getresponses][1] is "SequenceNo"><cfset SequenceNo=resultarray[getresponses][2]></cfif>
        <cfif resultarray[getresponses][1] is "Retrieval_Ref_No"><cfset RetrievalRefNo=resultarray[getresponses][2]></cfif>
        <cfif resultarray[getresponses][1] is "CardType"><cfset CardType=resultarray[getresponses][2]></cfif>
        <cfif resultarray[getresponses][1] is "bank_message"><cfset bankmessage=resultarray[getresponses][2]></cfif>
  </cfloop>

    <BR />
    #transactiontag#<BR />
    #authorizationnum# <BR />
    #transactionapproved# <BR />
    #exactmessage#<BR />
    #exactresponsecode#<BR />
    #sequenceno#<BR />
    #retrievalrefno#<BR />
    #cardtype#<BR />
    #bankmessage#<BR />

    <cfif trim(transactionapproved) is not "true" and trim(transactionapproved) is not "">
        <cfset badtransaction=2>
        <cfset badtransactionmessage=badtransactionmessage&bankmessage>
    </cfif>

    <cfif badtransaction gt 0>
        ---#badtransactionmessage#<BR />
    </cfif>

</cfoutput>
  • Always good to hear the problem is solved. So which step(s) made the difference? – Leigh Nov 19 '15 at 18:24
  • Hi, I made several changes to what I was having trouble with and/or with Nick's original code: 1) removed the charset declaration in the transaction xml; 2) changed 'text' to 'application', 3) for me to get the hash to match the website's testing hash, I changed the carriage return to char(10), and 4) changed the first header entry from 'CGGE4_API' to 'GGE_API' --just FYI, if you're using v11, you don't need any headers or hashing code, just the xml. – Steven Janow Nov 19 '15 at 19:33
  • (Edit) Regarding the response handling, why use HTMLEditFormat(cfhttp.fileContent)? Just guessing, but if the response is XML, it might be easier to parse it into a document ie `XMLParse`. Then you could extract the relevant parts a little more easily using dot notation ie `yourDoc.TransactionResult.someKeyName` :) – Leigh Nov 19 '15 at 21:09