Paypal Payflow 透明重定向,带 AJAX 的 SecureToken?

Paypal Payflow Transparent Redirect, SecureToken with AJAX?

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

我认为这一切如何运作: 我的理解是您需要一个安全令牌(与 Paypal 通信,行程 1)。然后您 post 安全数据(CC、exp、安全代码),包括安全令牌(与 Paypal 的通信,行程 2)并接收销售的授权和交易 ID。

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

此外,我想使用哪种交易类型?为行程 #1 创建一个 'Authorization',然后为行程 #2 创建一个 'Sale'?

下面是具体的编码类型内容: 对于我的研发测试,我正在构建自己的 name/value 对参数字符串(见下文)并通过 WebRequest 通过它们的 sandbox/test url (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、安全代码)的下一步。当我从参数中删除我的 UN/PW/VENDOR/Partner 信息时,由于用户身份验证无效,我收到错误消息。但是,看到我正在动态构建这个 2nd 调用,我不能在那里使用我的 paypal un/pw。我错过了什么?有人就这个问题或上面的其他问题提供帮助吗?

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

在与一位 Paypal 工程师交流了很多时间后,我成功地找到了一个没有托管页面(有自己的支付页面)的 Paypal Payflow Transparent Redirect 的解决方案。同样,根据工程师的说法,这里的文档非常混乱:Payflow API Documentation。此外,代码没有优化,因为它只是一个研发应用程序,但作为一个整体,它对我有用。只是一个例子和解释,我相信有更好的方法来完成各个步骤。希望这有助于并让您绕过一些阻碍 Paypal Payflow 集成的障碍。

是的,它符合 PCI,因为没有安全的客户数据会访问您自己的服务器。请记住,PCI 合规性非常复杂且涉及面广,但这是其中的重要部分。好的,所以我将解释我为在 MVC C# 环境中完成这项工作所做的工作。我将在此处解释这些步骤,然后在下面包含代码。

  1. 客户:客户将商品添加到购物车并按下“购买”按钮。 Javascript 处理按钮点击,不提交,并带您进入下一步。
  2. CLIENT --> SERVER: AJAX function POSTS 到服务器方法以联系 Paypal 以获得一次性安全令牌。此通信通过您的身份验证、唯一的交易 ID (GUID) 和有关交易的非安全详细信息(总计、账单信息、运输信息、return URL 详细信息将您(商家)标识为贝宝).这样,您所有的商家个人帐户信息都是安全的(网络服务器到 Paypal)。
  3. SERVER --> CLIENT:从上面的事务中,您将收到一个包含安全令牌的参数字符串(除其他内容外,请参阅示例方法)。使用这条信息,我动态创建了我最终需要在客户端上用于透明重定向部分的 url,并将 url 字符串发送回客户端。
  4. 客户:使用在第 3 步中 return 编辑的 url,我通过使用 jQuery 添加所需的信用卡参数来完成 URL。
  5. CLIENT --> PAYPAL:这是我不知道该怎么做的地方。虽然步骤 #2 是 post,但此步骤将是重定向。当然,看到它被称为 'transparent redirect' 似乎很合适,但那部分对我来说没有意义。因此,一旦您的整个 URL 完成,您将直接将 window 重定向到 Paypal 以处理您的交易。
  6. PAYPAL --> 服务器:PayPal posts 返回到您在步骤 2 中包含的 URLs 之一(返回到我的控制器之一上的 public 方法),然后我读取响应对象并解析参数。

简单吧?也许吧,但对我来说,第 5 步给我带来了很大的问题。我使用的是 POST 并且不明白为什么我总是收到响应错误。这是一个 html 页面,其中包含有关无效商家或身份验证的信息。请记住重定向,而不是步骤 #5 的 post。

代码:

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

第 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 是一种填充我创建的简单模型的方法,return 是该模型。

URL 对于令牌(沙盒): https://pilot-payflowpro.paypal.com

这与令牌 URL 不同!!在 PaypalTranactionAPI 配置值中使用。

URL 交易:(沙盒) 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 添加最终需要的参数(安全信用卡信息),然后重定向到 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 将 post 返回到以下方法之一:取消、错误或 Return(在令牌请求中将方法命名为您想要的任何名称)。解析响应并查看来自 Paypal 的变量 return,尤其是 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);
    }

希望这对您有所帮助,祝您好运!我会尽可能回答澄清问题。感谢您查看此内容,并记得转发。

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

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

为了缓解这个问题,您可以将 SecureToken 和 SecureTokenID 放入您的信用卡输入表单中,然后 POST 直接将其 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 合规性得到维护。