Authorize.net 的接受托管 iframe 中没有 action=transactResponse

no action=transactResponse in Accept Hosted iframe for Authorize.net

我正在尝试在 Coldfusion 应用程序中实施 Authorize.net Accept Hosted 托管结帐解决方案。

(对于所有 ColdFusion 开发人员,此代码 运行 在 BlueDragon.NET 9 上是一个 CF9 实现。cfscript 堆栈跟踪支持很糟糕,这就是为什么此代码在标签中而不是在 cfscript 中。 )

使用我们的沙盒 authorize.net 帐户,显示 iframe 托管的结帐页面。我可以填写信用卡信息并提交。我收到了收据,来自 authorize.net 的 2 封收据电子邮件,并被发送到确认页面,但 AuthorizeNetIFrame.onReceiveCommunication 中的案例“transactResponse”从未被触发。我在 javascript 函数中放置了一个警报,以查看进入 iframe 的所有查询字符串。

除了用于验证和获取令牌的 coldfusion 代码(这似乎有效),我从 Authorize.net 文档中获得了其余代码。

知道为什么我没有看到 action=transactResponse 查询字符串与来自 authorize.net 信用卡交易的响应一起进入 iframe。

我也使用实时 authorize.net 帐户尝试过此操作,但我在 iframe 支付页面上得到了一个 'User authentication failed due to invalid authentication values.',尽管我可以成功验证并获得 iframe 的令牌。我错过了什么。任何帮助将不胜感激。

代码的敏感部分(即凭据)、电子邮件和地址已被编辑。

这是主要代码文件(包括 iframe):

<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<head>
    <title>HostedPayment Test Page</title>
    <script src="https://code.jquery.com/jquery-3.6.0.js"
            integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk="
            crossorigin="anonymous"></script>
    <script type="text/javascript">

        $(function () {

            $("#btnOpenAuthorizeNetIFrame").click(function () {
                $("#add_payment").show();
                $("#send_token").attr({ "action": "https://test.authorize.net/payment/payment", "target": "add_payment" }).submit();
                $(window).scrollTop($('#add_payment').offset().top - 50);
            });

        });


</script>
</head>
<body>

<cfif NOT IsDefined("url.CFID") or NOT IsDefined("url.CFTOKEN")>
    <p style="color: red;">Error: CFID and CFTOKEN required as URL parameters...</p>
    <cfabort />
</cfif>

<cfinclude template="../#client.custom_path#/constants.cfm" />

<cfset variables.LOGIN_TOKEN_URL = "https://apitest.authorize.net/xml/v1/request.api" />
<cfset variables.API_LOGIN_ID = "ZZZZZZZZZZZ" />
<cfset variables.TRANSACTION_KEY = "ZZZZZZZZZZZZZZ" />

<h2>Authenticate</h2>

<cfoutput>
<cfsavecontent variable="variables.soapBody">
<authenticateTestRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
    <merchantAuthentication>
        <name>#variables.API_LOGIN_ID#</name>
        <transactionKey>#variables.TRANSACTION_KEY#</transactionKey>
    </merchantAuthentication>
</authenticateTestRequest>
</cfsavecontent>
</cfoutput>

<cfhttp url="#variables.LOGIN_TOKEN_URL#"
    method="post"
    result="variables.result1">
    <cfhttpparam
        type="xml"
        value="#Trim( variables.soapBody )#"
        />
</cfhttp>

<cfdump var="#variables.result1#" />

<cfset variables.response1 = XMLParse(Trim(Right(variables.result1.filecontent, Len(variables.result1.filecontent)-1))) />
<cfset variables.response = StructNew() />

<cfset variables.response.resultCode = xmlSearch(Trim(Right(variables.result1.filecontent, Len(variables.result1.filecontent)-1)),
    "//*[ local-name() = 'resultCode' ]"
    ) />
<cfif IsDefined("variables.response.resultCode[1].XmlText")>
    <cfset variables.response.resultCode = variables.response.resultCode[1].XmlText />
</cfif>

<cfset variables.response.code = xmlSearch(Trim(Right(variables.result1.filecontent, Len(variables.result1.filecontent)-1)),
    "//*[ local-name() = 'code' ]"
    ) />
<cfif IsDefined("variables.response.code[1].XmlText")>
    <cfset variables.response.code = variables.response.code[1].XmlText />
</cfif>

<cfset variables.response.text = xmlSearch(Trim(Right(variables.result1.filecontent, Len(variables.result1.filecontent)-1)),
    "//*[ local-name() = 'text' ]"
    ) />
<cfif IsDefined("variables.response.text[1].XmlText")>
    <cfset variables.response.text = variables.response.text[1].XmlText />
</cfif>

<cfif IsDefined("variables.response.text") AND FindNoCase("Successful", variables.response.text)>
    <p style="color: green; font-weight: bold;">Success</p>
<cfelse>
    <p style="color: red; font-weight: bold;">Failure</p>
</cfif>

<cfdump var="#variables.response#" />
<cfdump var="#variables.response1#" />

<h2>Get token</h2>

<cfoutput>
<cfsavecontent variable="variables.soapBody2">
<getHostedPaymentPageRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
  <merchantAuthentication>
    <name>#variables.API_LOGIN_ID#</name>
    <transactionKey>#variables.TRANSACTION_KEY#</transactionKey>
  </merchantAuthentication>
  <transactionRequest>
    <transactionType>authCaptureTransaction</transactionType>
    <amount>0.01</amount>
    <customer>
      <email>ZZZZZZZZ@ZZZZZZZZ.com</email>
    </customer>
    <billTo>
        <firstName>ZZZZZZZZ</firstName>
        <lastName>Tester1</lastName>
        <company></company>
        <address>123 Main Street</address>
        <city>ZZZZZZZZ</city>
        <state>ZZ</state>
        <zip>22222</zip>
        <country>US</country>
    </billTo>
  </transactionRequest>
  <hostedPaymentSettings>
    <setting>
      <settingName>hostedPaymentReturnOptions</settingName>
      <settingValue>{"showReceipt": true, "url": "https://ZZZZZZZZ.com/test/hosted_pages/cart_hosted_confirm.cfm", "urlText": "Continue", "cancelUrl": "https://ZZZZZZZZ.com/test/hosted_pages/cart_hosted_cancel.cfm", "cancelUrlText": "Cancel"}</settingValue>
    </setting>
    <setting>
      <settingName>hostedPaymentButtonOptions</settingName>
      <settingValue>{"text": "Pay"}</settingValue>
    </setting>
    <setting>
      <settingName>hostedPaymentStyleOptions</settingName>
      <settingValue>{"bgColor": "blue"}</settingValue>
    </setting>
    <setting>
      <settingName>hostedPaymentPaymentOptions</settingName>
      <settingValue>{"cardCodeRequired": true, "showCreditCard": true, "showBankAccount": false}</settingValue>
    </setting>
    <setting>
      <settingName>hostedPaymentSecurityOptions</settingName>
      <settingValue>{"captcha": false}</settingValue>
    </setting>
    <setting>
      <settingName>hostedPaymentShippingAddressOptions</settingName>
      <settingValue>{"show": false, "required": false}</settingValue>
    </setting>
    <setting>
      <settingName>hostedPaymentBillingAddressOptions</settingName>
      <settingValue>{"show": true, "required":true}</settingValue>
    </setting>
    <setting>
      <settingName>hostedPaymentCustomerOptions</settingName>
      <settingValue>{"showEmail": true, "requiredEmail": true, "addPaymentProfile": false}</settingValue>
    </setting>
    <setting>
      <settingName>hostedPaymentOrderOptions</settingName>
      <settingValue>{"show": true, "merchantName": "Test Company, LLC"}</settingValue>
    </setting>
    <setting>
      <settingName>hostedPaymentIFrameCommunicatorUrl</settingName>
      <settingValue>{"url": "https://ZZZZZZZZ.com/test/hosted_pages/IFrameCommunicator.html"}</settingValue>
    </setting>
  </hostedPaymentSettings>
</getHostedPaymentPageRequest>
</cfsavecontent>
</cfoutput>

<cfhttp url="#variables.LOGIN_TOKEN_URL#"
    method="post"
    result="variables.result2">
    <cfhttpparam
        type="xml"
        value="#Trim( variables.soapBody2 )#"
        />
</cfhttp>

<cfdump var="#variables.result2#" />

<cfset variables.response2 = XMLParse(Trim(Right(variables.result2.filecontent, Len(variables.result2.filecontent)-1))) />
<cfset variables.responseToken = StructNew() />

<cfset variables.responseToken.resultCode = xmlSearch(Trim(Right(variables.result2.filecontent, Len(variables.result2.filecontent)-1)),
    "//*[ local-name() = 'resultCode' ]"
    ) />
<cfif IsDefined("variables.responseToken.resultCode[1].XmlText")>
    <cfset variables.responseToken.resultCode = variables.responseToken.resultCode[1].XmlText />
</cfif>

<cfset variables.responseToken.code = xmlSearch(Trim(Right(variables.result2.filecontent, Len(variables.result2.filecontent)-1)),
    "//*[ local-name() = 'code' ]"
    ) />
<cfif IsDefined("variables.responseToken.code[1].XmlText")>
    <cfset variables.responseToken.code = variables.responseToken.code[1].XmlText />
</cfif>

<cfset variables.responseToken.text = xmlSearch(Trim(Right(variables.result2.filecontent, Len(variables.result2.filecontent)-1)),
    "//*[ local-name() = 'text' ]"
    ) />
<cfif IsDefined("variables.responseToken.text[1].XmlText")>
    <cfset variables.responseToken.text = variables.responseToken.text[1].XmlText />
</cfif>

<cfif IsDefined("variables.responseToken.text") AND FindNoCase("Successful", variables.responseToken.text)>
    <p style="color: green; font-weight: bold;">Success</p>
    
    <cfset variables.responseToken.token = xmlSearch(Trim(Right(variables.result2.filecontent, Len(variables.result2.filecontent)-1)),
    "//*[ local-name() = 'token' ]"
    ) />
    <cfif IsDefined("variables.responseToken.token[1].XmlText")>
        <cfset variables.responseToken.token = variables.responseToken.token[1].XmlText />
    </cfif>
<cfelse>
    <p style="color: red; font-weight: bold;">Failure</p>
</cfif>

<cfdump var="#variables.response2#" />
<cfdump var="#variables.responseToken#" />

<h2>Hosted Page</h2>
<cfif IsDefined("variables.responseToken.token") AND Len(Trim(variables.responseToken.token)) GT 0>

    <div>
    Open Authorize.net in an iframe to complete transaction
    <button id="btnOpenAuthorizeNetIFrame" onclick="">Show Payment Form</button>
    </div>
    <div id="iframe_holder" class="center-block" style="width:90%;max-width: 1000px">
    <iframe id="add_payment" class="embed-responsive-item panel" name="add_payment" width="100%"  frameborder="0" scrolling="no" hidden="true">
    </iframe>
    </div>
    <cfoutput>
    <form id="send_token" action="" method="post" target="add_payment">
    <input type="hidden" name="token" value="#variables.responseToken.token#" />
    </form> 
    </cfoutput>
    
    <script type="text/javascript">
        (function () {
            if (!window.AuthorizeNetIFrame) window.AuthorizeNetIFrame = {};
                AuthorizeNetIFrame.onReceiveCommunication = function (querystr) {
                
                    alert('processing 1:'+querystr);
                    
                    var params = parseQueryString(querystr);
                        switch (params["action"]) {
                            case "successfulSave":
                                break;
                            case "cancel":
                                break;
                            case "resizeWindow":
                                var w = parseInt(params["width"]);
                                var h = parseInt(params["height"]);
                                var ifrm = document.getElementById("add_payment");
                                ifrm.style.width = w.toString() + "px";
                                ifrm.style.height = h.toString() + "px";
                                break;
                            case "transactResponse":
                                var ifrm = document.getElementById("add_payment");
                                ifrm.style.display = 'none';
                                
                                var formData = { gatewayResponse: params["response"] };
                                $.ajax({
                                    url: "cart_hosted_async_log.cfm?<cfoutput>#URLTOKEN#</cfoutput>",
                                    type: "POST",
                                    data: formData,
                                    success: function(data, textStatus, jqXHR)
                                    {},
                                    error: function (jqXHR, textStatus, errorThrown)
                                    {}
                                });
                                break;
                            }
                    };

                function parseQueryString(str) {
                    var vars = [];
                    var arr = str.split('&');
                    var pair;
                    for (var i = 0; i < arr.length; i++) {
                        pair = arr[i].split('=');
                        vars.push(pair[0]);
                        vars[pair[0]] = unescape(pair[1]);
                        }
                    return vars;
                    }
        }());
    </script>
</cfif>

</body>
</html>

这是 IFrameCommunicator.html 代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Iframe Communicator</title>
    <script type="text/javascript">
        //<![CDATA[
            function callParentFunction(str) {
                if (str && str.length > 0 
                    && window.parent 
                    && window.parent.parent
                    && window.parent.parent.AuthorizeNetIFrame 
                    && window.parent.parent.AuthorizeNetIFrame.onReceiveCommunication)
                    {
// Errors indicate a mismatch in domain between the page containing the iframe and this page.
                        window.parent.parent.AuthorizeNetIFrame.onReceiveCommunication(str);
                    }
                }

            function receiveMessage(event) {
                if (event && event.data) {
                    callParentFunction(event.data);
                    }
                }

                if (window.addEventListener) {
                    window.addEventListener("message", receiveMessage, false);
                    } else if (window.attachEvent) {
                        window.attachEvent("onmessage", receiveMessage);
                    }

                if (window.location.hash && window.location.hash.length > 1) {
                    callParentFunction(window.location.hash.substring(1));
                    }
        //]]/>
    </script>
</head>
<body>
</body>
</html>

https://developer.authorize.net/api/reference/features/accept_hosted.html, 在交易响应下是以下注释:

Important: If you host the form in an iframe, you must include the iframe communicator URL in the hostedPaymentIFrameCommunicatorUrl parameter of your getHostedPaymentPageRequest API call. To ensure that you receive a response code, you must also set showReceipt to false.

粗体是我加的。将 showReceipt 设置为 false 后,我收到了针对我的 Sandbox 帐户的响应。

对于错误:

'User authentication failed due to invalid authentication values.'

我从 Authorize.net 支持人员那里找到了答案。当您的 site/software 通过 API 向 Authorize.Net 发送付款或创建个人资料的请求,而我们无法识别 API 登录 ID 和交易密钥时,就会发生这种情况已在请求中提交。此错误只有三种可能的原因:

  1. 您的 site/software 在 https://apitest.authorize.net/xml/v1/request.api -- For live accounts, please ensure your site/software is posting to https://api.authorize.net/xml/v1/request.api 使用测试环境 URL 时正在向真实账户的 API 登录 ID 和交易密钥发布。

  2. 您的 site/software 正在使用 https://api.authorize.net/xml/v1/request.api -- For test accounts, please post to https://apitest.authorize.net/xml/v1/request.api 上的实时环境发布到测试帐户的 API 登录 ID 和交易密钥。

  3. 您的 site/software 使用了不正确的 API 登录 ID 或交易密钥。如果是这种情况,那么我们建议在 Authorize.Net 门户上验证您的 API 登录 ID,或者在需要时生成新的交易密钥。您也可以使用此网站了解如何 verify/generate 这些:https://support.authorize.net/s/article/What-Are-the-API-Login-ID-and-Transaction-Key

在我的例子中,我发现令牌被发送到 https://test.authorize.net/payment/payment not https://accept.authorize.net/payment/payment。更改它,现在代码可以工作了。