Я работаю над приложением MVC C # VS2012 Framework 4.5, которое пытается стать совместимым с PCI с помощью Payflow Pro (https: //pilot-payflowpro.paypal .com). Мы используем PayflowPro годами, и это то, что мне нужно использовать. Из моего чтения кажется, что я должен использовать Transparent Redirect, поэтому я не размещаю ничего личного на своем веб-сервере, хотя я не знаю, нужно ли мне это с тем, как я надеюсь справиться с этим. Еще у меня есть несколько вопросов ...

Как я думаю, все это работает: Насколько я понимаю, вам нужен securetoken (связь с Paypal, поездка 1). Затем вы публикуете защищенные данные (CC, exp, код безопасности), включая securetoken (связь с Paypal, поездка 2), и получаете авторизацию и идентификатор транзакции продажи.

Как я надеюсь это сделать: Я собираюсь создать форму, в которой будет вся информация (данные пользователя, сведения о доставке и информация CC), и когда пользователь нажимает кнопку покупки, я буду использовать AJAX для обработки поездки 1 на мой сервер (нет безопасного информация о пользователе отправлена). Здесь я создам URL + params и отправлю PayPal мою информацию un / pw, чтобы получить токен (все с моего сервера). Ответ будет возвращен клиенту, и, в случае успеха, я буду напрямую общаться через AJAX с сервером шлюза Paypal, на этот раз отправив защищенный токен CC info + (поездка № 2). Основываясь на ответе на поездку №2, я сообщу пользователю, что случилось с его покупкой. Для поездки 2 не нужна моя информация Paypal UN / PW, поскольку ее можно легко увидеть на клиенте, и я включаю SecureToken, который ДОЛЖЕН идентифицировать исходную транзакцию. Из того, что я объяснил, я не вижу необходимости в прозрачном перенаправлении. Или мне что-то здесь не хватает?

Кроме того, какой тип транзакции я хочу использовать? Создать «Авторизацию» для поездки №1, а затем «Распродажу» для поездки №2?

Итак, вот самые мелкие детали типа кодирования: Для своих исследований и разработок я создаю свою собственную строку параметров пары имя / значение (см. Ниже) и связываюсь с сервером шлюза через WebRequest через их песочницу / тестовый 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 из параметров, я получаю сообщение об ошибке из-за неправильной аутентификации пользователя. Но, видя, что я динамически создаю этот второй вызов, у меня не может быть там мой PayPal un / pw. Что мне не хватает? Кто-нибудь предлагает помощь с этим или другими вопросами сверху?

Пожалуйста, дайте мне знать, если мне нужно добавить какие-либо пояснения. Спасибо заранее за ваше время!

8
RichieMN 26 Фев 2015 в 22:16

2 ответа

Лучший ответ

Я смог использовать ответ RichieMN, чтобы получить работающее прозрачное перенаправление. Однако проблема с перенаправлением с помощью window.location.replace в функции SendCCDetailsToPaypal заключается в том, что вы передаете данные в строке GET.

Это работает на стороне шлюза PayFlow, но когда они отправляют браузер клиента обратно на ваш ResponseURL, в ваших журналах Apache будет отображаться весь URL-адрес 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 сохраняется.

4
Reverend Pete 9 Апр 2016 в 20:12

Проведя кучу времени с инженером Paypal, я успешно нашел решение для Payflow Transparent Redirect без размещенных страниц (с собственной платежной страницей). Опять же, вот документация, которая, по мнению инженера, довольно запутанная: Документация по API Payflow.. Кроме того, код не оптимизирован, поскольку это было всего лишь приложение для исследований и разработок, но в целом оно работает на меня. Просто пример и объяснение, и я уверен, что есть более эффективные способы выполнения отдельных шагов. Надеюсь, это поможет и позволит вам обойти некоторые препятствия, которые замедляют вашу интеграцию Paypal Payflow.

ДА, он соответствует стандарту PCI в том смысле, что никакие защищенные данные клиентов не попадут на ваши собственные серверы. Помните, что соблюдение PCI - это довольно сложно и сложно, но это большая часть. Хорошо, я объясню, что я сделал, чтобы заставить эту работу работать в среде MVC C #. Я объясню шаги здесь, а затем включу код ниже.

  1. КЛИЕНТ: Клиент завершает добавление товаров в корзину и нажимает кнопку КУПИТЬ. Javascript обрабатывает нажатие кнопки, не отправляет и переводит вас к следующему шагу.
  2. КЛИЕНТ -> СЕРВЕР: функция AJAX ОТПРАВЛЯЕТ метод сервера, чтобы связаться с Paypal за одноразовым токеном безопасности. Это сообщение идентифицирует ВАС (торговца) для PayPal с вашей аутентификацией, уникальным идентификатором транзакции (guid) и незащищенными деталями транзакции (общая сумма, информация о выставлении счетов, информация о доставке, детали URL-адреса возврата). Таким образом, вся информация о вашем личном аккаунте продавца будет в безопасности (от веб-сервера до Paypal).
  3. СЕРВЕР -> КЛИЕНТ: из транзакции выше вы получите строку параметра, содержащую защищенный токен (среди прочего, см. Метод с примером). Используя эту информацию, я динамически создаю свой URL-адрес, который мне в конечном итоге понадобится на клиенте для части прозрачного перенаправления, и отправляю строку URL-адреса обратно клиенту.
  4. КЛИЕНТ: Используя URL-адрес, который был возвращен на шаге № 3, я завершаю URL-адрес, добавляя необходимые параметры кредитной карты с помощью jQuery.
  5. КЛИЕНТ -> PAYPAL: Здесь я не понял, что делать. Хотя шаг № 2 был опубликован, этот шаг будет ПЕРЕПРАВИЛЬНЫМ. Конечно, это кажется уместным, учитывая, что это называется «прозрачное перенаправление», но эта часть просто не имела для меня смысла. Итак, как только ваш URL-адрес будет заполнен, вы буквально перенаправите окно на Paypal для обработки вашей транзакции.
  6. PAYPAL -> СЕРВЕР: PayPal отправляет сообщение обратно на один из URL-адресов, которые вы указали на шаге 2 (общедоступный метод на одном из моих контроллеров), и я читаю объект ответа и анализирую параметры.

Легко, правда? Возможно, но для меня шаг 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 
});}

На стороне сервера:

У меня есть отдельные методы, используемые для создания всех значений параметров, необходимых для запроса токена. Первые три сборки: аутентификация, детали транзакции, прозрачное перенаправление. Я храню URL-адреса и информацию о платежном потоке в файле web.config. Последний метод, ProcessTokenTransaction, выполняет всю тяжелую работу, чтобы связаться с Paypal через WebRequest, а затем преобразовать его в URL-адрес, который будет отправлен обратно клиенту. Этот метод следует реорганизовать для более чистой доставки, но я оставлю это вам. ParseResponse - это метод, который заполняет созданную мной простую модель и возвращает эту модель.

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 отправит ответ одним из следующих методов: Отмена, Ошибка или Возврат (назовите методы как хотите в запросе токена). Проанализируйте ответ и посмотрите на переменные, возвращаемые 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);
    }

Надеюсь, что это помогает и удачи! Я отвечу на уточняющие вопросы, если смогу. Спасибо, что посмотрели это, и не забудьте заплатить вперед.

7
RichieMN 10 Мар 2015 в 14:34