Paypal Payflow透明重定向,使用AJAX进行SecureToken?

时间:2015-02-26 19:16:52

标签: ajax paypal asp.net-ajax token payflowpro

我正在尝试使用Payflow Pro(https://pilot-payflowpro.paypal.com)尝试成为PCI兼容的C#VS2012 Framework 4.5 MVC应用程序。我们多年来一直在使用PayflowPro,这就是我必须使用的。从我的阅读中看来,我似乎应该使用透明重定向,所以我没有在我的网络服务器上发布任何私密内容,但我不知道我是否需要这些以及我希望如何处理这一点。我也有几个问题......

我认为这一切都有效: 我的理解是你需要一个securetoken(与Paypal的通信,旅程1)。然后您发布安全数据(CC,exp,安全代码),包括securetoken(与Paypal的通信,旅程2)并接收销售的授权和交易ID。

我希望如何做到这一点: 我打算拥有一个包含所有信息(用户详细信息,运送详细信息和CC信息)的表单,当用户按下购买按钮时,我将使用AJAX处理我的旅程1服务器(没有发送安全的用户信息)。在这里,我将创建URL + params并发送paypal我的un / pw信息以检索令牌(全部来自我的服务器)。响应将返回给客户端,如果成功,我将直接通过AJAX与Paypal的网关服务器进行通信,这次发送安全的CC信息+令牌(行程#2)。根据对旅行#2的回复,我将让用户了解他们的购买行为。旅行2不应该需要我的Paypal UN / PW信息,因为它可以很容易地在客户端看到,我包括应该识别原始交易的SecureToken。根据我的解释,我不认为需要透明重定向。或者我在这里遗漏了什么?

另外,我想使用什么交易类型?创建“授权”'对于旅行#1,然后是' Sale'旅行#2?

所以这里有一些非常粗糙的编码类型: 对于我的R& D测试,我构建自己的名称/值对参数字符串(见下文),并通过Webbox通过他们的沙箱/测试网址(pilot-payflowpro.paypal.com)与网关服务器进行通信。我得到了一个成功的回复和SECURETOKEN回来了。安全令牌的初始请求(如下所示)为TRXTYPE = A(授权),不发送卡信息。我想先授权吗?

以下是我的参数(也可能包含发货信息,但未在下面列出):

USER=myAuthUserName
&VENDOR=myAuthUserName
&PARTNER=myPartner
&PWD=myPassword
&AMT=21.43
&BILLTOFIRSTNAME=FName
&BILLTOLASTNAME=LName
&BILLTOSTREET=123 Main Street
&BILLTOSTREET2=Apt 203B
&BILLTOCITY=MyCity
&BILLTOSTATE=CA
&BILLTOZIP=77777
&BILLTOPHONENUM=4444444444
&EMAIL=myemail@somedomain.com
&CURRENCY=USD
**&TRXTYPE=A**
&SILENTTRAN=TRUE
&CREATESECURETOKEN=Y
&SECURETOKENID=a99998afe2474b1b82c8214c0824df99

正如我所说,我得到了一个成功的回复并转到下一步发送安全数据(CC#,EXPDATE,安全码)。当我从params中删除我的UN / PW / VENDOR /合作伙伴信息时,由于无效的用户身份验证而出现错误。但是,看到我动态建立第二个电话,我无法在那里使用我的PayPal / pw。我错过了什么?任何人都可以提供上述此问题或其他问题的帮助吗?

如果我需要添加任何说明,请告诉我。提前感谢您的时间!

2 个答案:

答案 0 :(得分:6)

在Paypal工程师花了很多时间之后,我成功找到了Paypal的Payflow透明重定向解决方案,没有托管页面(有自己的支付页面)。同样,按照工程师的说法,这里的文档非常令人困惑:Payflow API Documentation。此外,代码没有优化,因为它只是一个R& D应用程序,但作为一个整体,它对我有用。只是一个例子和解释,我相信有更好的方法来做个别的步骤。希望这会有所帮助,并允许您绕过一些减慢Paypal Payflow集成速度的障碍。

是的,它符合PCI标准,因为没有安全的客户数据会打到您自己的服务器上。请记住,PCI合规性非常复杂且涉及,但这是其中很重要的一部分。好的,所以我将解释我在MVC C#环境中做了哪些工作。我将解释这里的步骤,然后包括下面的代码。

  1. 客户:客户端完成向购物车添加商品并按下“购买”按钮。 Javascript处理按钮单击,不提交,并带您进入下一步。
  2. 客户 - > SERVER:AJAX函数POSTS到服务器方法联系Paypal获取一次性安全令牌。此通信使用您的身份验证,唯一的交易ID(guid)以及有关交易的非安全详细信息(总计,账单信息,送货信息,返回URL详细信息)将您(商家)识别为paypal。这样,您的所有商家个人信息都是安全的(Web服务器到Paypal)。
  3. 服务器 - >客户端:从上面的事务中,您将收到一个包含安全令牌的参数字符串(除其他内容外,请参阅示例方法)。使用这条信息,我动态创建我最终需要在客户端上用于透明重定向部分的URL,并将url字符串发送回客户端。
  4. 客户端:使用步骤3中返回的网址,我通过使用jQuery添加所需的信用卡参数来完成网址。
  5. 客户 - > PAYPAL:这是我不明白该做什么的地方。虽然步骤#2是一个帖子,但这一步骤将是一个REDIRECT。当然,这看起来很合适,因为它被称为“透明重定向”,但这部分对我来说没有意义。因此,一旦您的整个网址完成,您就会将窗口重定向到Paypal以处理您的交易。
  6. PAYPAL - >服务器:PayPal回发到您在步骤2中包含的其中一个URL(到我的一个控制器上的公共方法),然后我读取响应对象并解析参数。
  7. 简单,对吗?或许,但对我而言,第5步给我带来了很大的问题。我正在使用POST,但不明白为什么我一直在回复错误。这是一个关于无效商家或身份验证的HTML页面。请记住重定向,而不是发布第5步。

    <强> CODE

    第1步:按钮上的onclick属性调用GetToken函数。

    第2步和第3步

    客户端:

    function GetToken() {
    $.ajax({
        url: '@Url.Action("GetToken", "MyController")',
        type: 'POST',
        cache: 'false',
        contentType: 'application/json; charset=utf-8',
        dataType: 'text',
        success: function (data) {
            // data is already formatted in parameter string
            SendCCDetailsToPaypal(data);
        },
        //error: 
        //TODO Handle the BAD stuff 
    });}
    

    服务器端:

    我有单独的方法用于构建令牌请求所需的所有参数值。前三个构建:身份验证,事务详细信息,透明重定向。我在web.config文件中保留urls和payflow acct信息。最后一个方法ProcessTokenTransaction通过WebRequest完成与Paypal联系的所有繁重工作,然后将其解析为将发送回客户端的URL。这种方法应该重构为更清洁的交付,但我会留给你。 ParseResponse是一种填充我创建的简单模型的方法,并返回该模型。

    令牌(沙盒)的网址: https://pilot-payflowpro.paypal.com

    这与令牌网址不同!!用于PaypalTranactionAPI配置值。

    交易网址:(沙盒) https://pilot-payflowlink.paypal.com

    private  string PrepareApiAuthenticationParams()        
        {
            var paypalUser = ConfigurationManager.AppSettings["PaypalUser"];
            var paypalVendor = ConfigurationManager.AppSettings["PaypalVendor"];
            var paypalPartner = ConfigurationManager.AppSettings["PaypalPartner"];
            var paypalPw = ConfigurationManager.AppSettings["PaypalPwd"];
    
            //var amount = (decimal)19.53;
    
            var apiParams = @"USER=" + paypalUser
                            + "&VENDOR=" + paypalVendor
                            + "&PARTNER=" + paypalPartner
                            + "&PWD=" + paypalPw
                            + "&TENDER=C"
                            + "&TRXTYPE=A"
                            + "&VERBOSITY=HIGH";
    
            // find more appropriate place for this param
            //+ "&VERBOSITY=HIGH";
    
            return apiParams;
        }
    
    
        private  string PrepareTransactionParams(CustomerDetail detail)
        {
            var currencyType = "USD";
    
            var transactionParams = @"&BILLTOFIRSTNAME=" + detail.FirstName
                                    + "&BILLTOLASTNAME=" + detail.LastName
                                    + "&BILLTOSTREET=" + detail.Address1
                                    + "&BILLTOSTREET2=" + detail.Address2
                                    + "&BILLTOCITY=" + detail.City
                                    + "&BILLTOSTATE=" + detail.State
                //+ "&BILLTOCOUNTRY=" + detail.Country +  // NEEDS 3 digit country code
                                    + "&BILLTOZIP=" + detail.Zip
                                    + "&BILLTOPHONENUM=" + detail.PhoneNum
                                    + "&EMAIL=" + detail.Email
                                    + "&CURRENCY=" + currencyType
                                    + "&AMT=" + GET_VALUE_FROM_DB
                                    + "&ERRORURL= " + HostUrl + "/Checkout/Error"
                                    + "&CANCELURL=" + HostUrl + "/Checkout/Cancel"
                                    + "&RETURNURL=" + HostUrl + "/Checkout/Success";   
    
            // ADD SHIPTO info for address validation
    
            return transactionParams;
        }
    
    
    private  string PrepareTransparentParams(string requestId, string transType)
        {
            var transparentParams = @"&TRXTYPE=" + transType +
                                   "&SILENTTRAN=TRUE" +
                                   "&CREATESECURETOKEN=Y" +
                                   "&SECURETOKENID=" + requestId;
    
            return transparentParams;
        }
    
    
        // Method to build parameter string, and create webrequest object
    public string ProcessTokenTransaction()
        {
            var result = "RESULT=0"; // default failure response
            var transactionType = "A";
            var secureToken = string.Empty;
            var requestId = Guid.NewGuid().ToString().Replace("-", string.Empty);
    
            var baseUrl = ConfigurationManager.AppSettings["PaypalGatewayAPI"];            
    
            var apiAuthenticationParams = PrepareApiAuthenticationParams();
    
            // Create url parameter name/value parameter string
            var apiTransactionParams = PrepareTransactionParams(detail);
    
            // PCI compliance, Create url parameter name/value parameter string specific to TRANSAPARENT PROCESSING 
            var transparentParams = PrepareTransparentParams(requestId, transactionType);
    
            var url = baseUrl;
            var parameters = apiAuthenticationParams + apiTransactionParams + transparentParams;
    
    
            // base api url + required 
            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "text/name"; // Payflow?
            request.Headers.Add("X-VPS-REQUEST-ID", requestId);
    
            byte[] bytes = Encoding.UTF8.GetBytes(parameters);
            request.ContentLength = bytes.Length;
    
            Stream requestStream = request.GetRequestStream();
            requestStream.Write(bytes, 0, bytes.Length);
            requestStream.Close();
    
    
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();
            StreamReader reader = new StreamReader(stream);
    
            try
            {
    
                // sample successful response
                // RESULT=0&RESPMSG=Approved&SECURETOKEN=9pOyyUMAwRUWmmv9nMn7zhQ0h&SECURETOKENID=5e3c50a4c3d54ef8b412e358d24c8915
    
                result = reader.ReadToEnd();
    
                var token = ParseResponse(result, requestId, transactionType);
    
                var transactionUrl = ConfigurationManager.AppSettings["PaypalTransactionAPI"];
                secureToken = transactionUrl + "?SECURETOKEN=" + token.SecureToken + "&SECURETOKENID=" + requestId;
    
                //ameValueCollection parsedParams = HttpUtility.ParseQueryString(result);                
    
                stream.Dispose();
                reader.Dispose();
            }
            catch (WebException ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.Message);
    
            }
            finally { request.Abort(); }
    
            return secureToken;
        }
    
    
    private TokenResponse ParseResponse(string response, string requestId, string transactionType)
        {
            var nameValues = HttpUtility.ParseQueryString(response);
    
            int result = -999;  // invalid result to guarantee failure
    
            int.TryParse(nameValues.Get(TokenResponse.ResponseParameters.RESULT.ToString()), out result);
    
            // retrieving response message
            var responseMessage = nameValues.Get(TokenResponse.ResponseParameters.RESPMSG.ToString());
    
            // retrieving token value, if any
            var secureToken = nameValues.Get(TokenResponse.ResponseParameters.SECURETOKEN.ToString());
    
            var reference = nameValues.Get(TokenResponse.ResponseParameters.PNREF.ToString());
    
            var authCode = nameValues.Get(TokenResponse.ResponseParameters.AUTHCODE.ToString());
    
            var cscMatch = nameValues.Get(TokenResponse.ResponseParameters.CSCMATCH.ToString());
    
            // populating model with values
            var tokenResponse = new TokenResponse
            {
                Result = result,
                ResponseMessage = responseMessage,
                SecureToken = secureToken,
                TransactionIdentifierToken = requestId,
                TransactionType = transactionType,
                ReferenceCode = reference,
                AuthorizationCode = authCode,
                CSCMatch = cscMatch
            };
    
            return tokenResponse;
        }
    

    第4步和第5步:

    返回客户端:

    在这里,我使用前面步骤构建的URL,使用jQuery添加最终需要的参数(安全信用卡信息),然后REDIRECT到Paypal。

     function SendCCDetailsToPaypal(secureParm) {
    
        //alert('in SendCCDetailsToPaypal:' + secureParm);
    
        var secureInfo = '&ACCT=' + $('#ccNumber').val() + '&EXPDATE=' + $("#expMonth").val() + $("#expYear").val() + "&CSC=" + $('#ccSecurityCode').val();
        secureInfo = secureParm + secureInfo;
    
        window.location.replace(secureInfo);               
    }
    

    第6步:

    Paypal将回发到以下方法之一:取消,错误或返回(在令牌请求中将方法命名为任何内容)。解析响应并查看从Paypal返回的变量,特别是RESULT和RESPMSG。阅读文档了解具体细节,因为您可以合并地址验证和一系列其他功能。根据响应,显示适当的内容。

    服务器端:

     public ActionResult Cancel()
        {
            var result = ParseRequest(HttpUtility.UrlDecode(Request.Params.ToString()));
    
            //return View("Return", result);
        }
    
    
        public ActionResult Error()
        {
    
            var result = ParseRequest(HttpUtility.UrlDecode(Request.Params.ToString()));
    
            return View("Return", result);
        }
    
    
        public ActionResult Return()
        {
            var result = ParseRequest(HttpUtility.UrlDecode(Request.Params.ToString()));
    
            return View("Return", result);
        }
    

    希望这有帮助,祝你好运!我会尽可能地回答澄清问题。感谢您查看此内容,并记得向前付款。

答案 1 :(得分:4)

我能够使用RichieMN的答案来实现透明重定向。但是,在 SendCCDetailsToPaypal 函数中使用 window.location.replace 进行重定向的问题是您在GET字符串上传递数据。

这适用于PayFlow网关端,但当他们将客户的浏览器发送回您的ResponseURL时,您的Apache日志将显示整个payflowlink.paypal.com网址,包括GET字符串作为Apache访问日志中的引荐来源!该GET字符串包含信用卡号,现在您刚刚失去了PCI合规性!

要解决此问题,您可以将SecureToken和SecureTokenID放入信用卡输入表单,然后将其直接发送到payflowlink.paypal.com,或者您可以重写 SendCCDetailsToPaypal 功能来构建表格并提交,如下:

function SendCCDetailsToPaypal() {
    var parameters = {
        "SECURETOKEN": secureToken,
        "SECURETOKENID": secureTokenID,
        "ACCT": $("#ccNumber").val(),
        "EXPDATE": $("#expMonth").val() + $("#expYear").val(),
        "CSC": $("#ccSecurityCode").val()
    };
    var form = $('<form></form>');
    form.attr("method", "post");
    form.attr("action", "https://pilot-payflowlink.paypal.com");
    $.each(parameters, function(key, value) {
        var field = $('<input></input>');
        field.attr("type", "hidden");
        field.attr("name", key);
        field.attr("value", value);
        form.append(field);
    });
    $(document.body).append(form);
    form.submit();
}

由于该表单通过POST传输数据,当您的服务器返回结果POST时,引用者不包含任何敏感数据,并且您的PCI合规性得以维持。