Payeezy/Firstdata Получена неверная подпись

В настоящее время я разрабатываю сайт, используя payeezy/firstdata для платежей. Интеграция была довольно сложной, потому что их документация по API довольно слабая.

Я использую ColdFusion и запрос cfhttp. Я следил за этим, чтобы вычислить мой дайджест контента и хэш hmac: https://support.payeezy.com/hc/en-us/articles/203731149-API-Security-HMAC-Hash

Наконец-то я получил хэши, соответствующие рассчитанным хэшам в демонстрационном терминале, но моя проблема: я получаю странную ошибку при отправке запроса. Я получаю сообщение об ошибке:

«Получена неверная подпись 'Fgx/lR#############'».

где первые несколько символов меняются каждый раз. Вот мой код для запроса:

postAction = https://api.demo.globalgatewaye4.firstdata.com/transaction/v19 key_id, hmac_value, content_digest были проверены и верны 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>

Представленный xml (без пробелов и новых строк)

<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>

Я изменил заголовок авторизации на «Payeezy_Gateway_API #key_id#:#hmac_value#» и получаю сообщение об ошибке «Неверный заголовок авторизации», когда используемое значение hmac и идентификатор ключа многократно тестировались на терминале payeezy.

Пожалуйста, любая помощь очень ценится!


person Nick Bechtold    schedule 27.08.2015    source источник
comment
Просто предположение, но включаете ли вы набор символов в свои расчеты? IIRC, cfhttp по умолчанию добавляет UTF-8. В документации Payeezy говорится: Если набор символов включен в заголовок Content Type, он также должен использоваться в расчетах (в калькуляторе это не предусмотрено). Может быть проблема. Вы используете Fiddler, чтобы увидеть, что он отправляет, или, если это не удается, указываете cfhttp на страницу .cfm на вашем сервере, которая делает дамп GetHTTPRequestData().   -  person Leigh    schedule 29.08.2015
comment
Кроме того, вы уверены, что это CGGE4_API? В некоторых примерах вместо этого используется GGE4_API. Не уверен, что правильно.   -  person Leigh    schedule 29.08.2015


Ответы (2)


И CGGE4_API, и GGE4_API работают для меня при тестировании с их образцом кода Python на той странице поддержки, на которую вы ссылаетесь, но я позвонил в их номер службы поддержки, и они сказали мне, что я должен использовать GGE4_API.

Видимо страница поддержки устарела, а использование Payeezy_Gateway_API вообще не работает. Вот как должен выглядеть пример кода Python на этой странице:

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()

В моем приложении я также получал недействительную подпись. Оказалось, что я добавлял charset=utf-8 в свой заголовок Content-Type (кредит комментарий Ли). Удаление этого сделало мои запросы принятыми.

person Rahat Ahmed    schedule 09.09.2015
comment
Да, я уже протестировал примеры Python и C# на полигоне программирования. Поскольку они оба работали, это предполагало проблему с текстовым префиксом или дополнительной кодировкой в ​​заголовках cfhttp, но без подтверждения спрашивающего это не казалось достаточно окончательным, чтобы публиковать его в качестве ответа. Надеюсь, они ответят да или нет. - person Leigh; 10.09.2015
comment
Другими словами, звучит так, будто мы оба пришли к одному и тому же выводу ;-) - person Leigh; 10.09.2015
comment
Честно говоря, большая часть того, что я написал, взято из вашего комментария. Я просто хотел добавить более точный ответ после подтверждения деталей в моих звонках в службу поддержки для будущих посетителей. Так что респект тебе, @Leigh. - person Rahat Ahmed; 11.09.2015
comment
Что ж, надеюсь, это вызовет ответ от спрашивающего, потому что, честно говоря, после просмотра и тестирования примеров мне немного любопытно, как решить проблему :-) - person Leigh; 11.09.2015
comment
Не относящаяся к делу заметка, но кто-то обязательно с ней столкнется. Если вы получаете неописуемую ошибку 500, это может быть связано с неправильным форматом JSON в теле вашего запроса. По крайней мере, так случилось со мной. - person Rahat Ahmed; 16.12.2015

Я также пытался решить эту проблему с помощью Payeezy. Я, вероятно, опаздываю на день и не хватает доллара, однако я заставил это работать. Я внес несколько изменений в то, с чем у меня возникли проблемы, и/или в исходном коде Ника:

  1. Удалено объявление кодировки в транзакции xml;
  2. Изменен «текст» на «приложение».
  3. Чтобы шифрование соответствовало тестовому шифрованию веб-сайта, я изменил возврат каретки на char(10) и
  4. Изменена первая запись заголовка с «CGGE4_API» на «GGE_API».

Просто к вашему сведению, если вы используете v11, вам не нужны никакие заголовки или хэш-код, только xml.

Вот весь мой тестовый код, включая некоторый код для разбивки возвращаемого xml.

Код запроса:

<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>

Код ответа:

<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>
person Steven Janow    schedule 19.11.2015
comment
Всегда приятно слышать, что проблема решена. Итак, какие шаги имели значение? - person Leigh; 19.11.2015
comment
Привет, я внес несколько изменений в то, с чем у меня возникли проблемы и/или с исходным кодом Ника: 1) удалил объявление кодировки в транзакции xml; 2) изменил «текст» на «приложение», 3) чтобы я получил хэш, соответствующий тестовому хешу веб-сайта, я изменил возврат каретки на char (10) и 4) изменил первую запись заголовка с «CGGE4_API» на 'GGE_API' -- просто к вашему сведению, если вы используете v11, вам не нужны никакие заголовки или хэш-код, только xml. - person Steven Janow; 19.11.2015
comment
(Изменить) Что касается обработки ответов, зачем использовать HTMLEditFormat(cfhttp.fileContent)? Просто предположение, но если ответ представляет собой XML, может быть проще разобрать его в документ, т.е. XMLParse. Тогда вы могли бы немного упростить извлечение соответствующих частей, используя точечную нотацию, т.е. yourDoc.TransactionResult.someKeyName :) - person Leigh; 20.11.2015