Paypal 的 REST API 可以通过 cURL 发起调用,但是从 C# 代码中却无法正常工作

8

我试图从我的代码中调用Paypal API。我设置了沙盒账户,当我使用curl时它可以正常工作,但我的代码却没有同样的效果,返回401未经授权错误。

以下是curl命令,如Paypal文档所述

curl https://api.sandbox.paypal.com/v1/oauth2/token -H "Accept: application/json" -H "Accept-Language: en_US" -u "A****:E****" -d "grant_type=client_credentials" 

更新:显然.Credentials不能起作用,手动设置Authorization头部可以解决问题(参见代码)

以下是核心代码:

  HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("https://api.sandbox.paypal.com/v1/oauth2/token");
  request.Method = "POST";
  request.Accept = "application/json";
  request.Headers.Add("Accept-Language:en_US")

  // this doesn't work:
  **request.Credentials = new NetworkCredential("A****", "E****");**

  // DO THIS INSTEAD
  **string authInfo = Convert.ToBase64String(System.Text.Encoding.Default.GetBytes("A****:E****"));**
  **request.Headers["Authorization"] = "Basic " + authInfo;**

  using (StreamWriter swt = new StreamWriter(request.GetRequestStream()))
  {
    swt.Write("grant_type=client_credentials");
  }

  request.BeginGetResponse((r) =>
  {
    try
    {
       HttpWebResponse response = request.EndGetResponse(r) as HttpWebResponse; // Exception here
       ....
    } catch (Exception x)  { .... } // log the exception - 401 Unauthorized
  }, null);

这是通过Fiddler捕获的代码请求(原始格式),由于某些原因没有身份验证参数:
POST https://api.sandbox.paypal.com/v1/oauth2/token HTTP/1.1
Accept: application/json
Accept-Language: en_US
Host: api.sandbox.paypal.com
Content-Length: 29
Expect: 100-continue
Connection: Keep-Alive

grant_type=client_credentials

接受头中缺少一个空格,但我看不出其他明显的问题。你尝试过捕获这两个请求以查看有什么不同吗?例如使用Wireshark或代理工具如Fiddler? - Rup
@Rup 我已经尝试使用 Fiddler,但仍然无法捕获 curl 请求,但代码请求不包含 Auth 标头(请参见更新)。 - Sten Petrov
1
是的,一些HTTP库(例如Apache)不会发送凭据,除非远程服务器要求它们,但我不知道.NET也会这样做。或者至少在401状态码时应该回复相应的凭据。也许有一种方法可以强制要求请求对象发送凭据? - Rup
1
在这个旧答案中有一个不太好的解决方法:构建自己的基本身份验证标头。或者我在考虑使用HttpWebRequest.PreAuthenticate。 - Rup
@Rup,是的,我发现了这个问题并解决了它。谢谢你的关注。 - Sten Petrov
可能是强制使用WebRequest中的基本身份验证的重复问题。 - Chris Pitman
4个回答

6
希望以下代码能帮助那些仍在寻找与PayPal连接的好方法的人。
像许多人一样,我花费了很多时间尝试获取我的PayPal令牌访问权限却未能成功,直到我发现了以下内容:
public class PayPalClient
{
    public async Task RequestPayPalToken() 
    {
        // Discussion about SSL secure channel
        // https://dev59.com/DFwY5IYBdhLWcg3wHkuK
        ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

        try
        {
            // ClientId of your Paypal app API
            string APIClientId = "**_[your_API_Client_Id]_**";

            // secret key of you Paypal app API
            string APISecret = "**_[your_API_secret]_**";

            using (var client = new System.Net.Http.HttpClient())
            {
                var byteArray = Encoding.UTF8.GetBytes(APIClientId + ":" + APISecret);
                client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

                var url = new Uri("https://api.sandbox.paypal.com/v1/oauth2/token", UriKind.Absolute);

                client.DefaultRequestHeaders.IfModifiedSince = DateTime.UtcNow;

                var requestParams = new List<KeyValuePair<string, string>>
                            {
                                new KeyValuePair<string, string>("grant_type", "client_credentials")
                            };

                var content = new FormUrlEncodedContent(requestParams);
                var webresponse = await client.PostAsync(url, content);
                var jsonString = await webresponse.Content.ReadAsStringAsync();

                // response will deserialized using Jsonconver
                var payPalTokenModel = JsonConvert.DeserializeObject<PayPalTokenModel>(jsonString);
            }
        }
        catch (System.Exception ex)
        {
            //TODO: Log connection error
        }
    }
}

public class PayPalTokenModel 
{
    public string scope { get; set; }
    public string nonce { get; set; }
    public string access_token { get; set; }
    public string token_type { get; set; }
    public string app_id { get; set; }
    public int expires_in { get; set; }
}

这段代码对我来说很有效,希望对您也有帮助。感谢 Patel Harshal 在这里发布了他的解决方案。

2
谢谢!你救了我半天的时间。 - Max Favilli

3

Paypal已经弃用TLS 1.1,现在只接受1.2。不幸的是,除非你进行配置,否则.NET(4.7版本之前)默认使用1.1。

你可以通过添加以下代码启用TLS 1.2。我建议将其放置在Application_Startglobal.asax中。

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

3

使用HttpClient实现如下... 'RequestT' 是PayPal请求参数的泛型,但是它没有被使用。根据PayPal文档,'ResponseT' 是被使用的,它是来自PayPal的响应。

'PayPalConfig' 类从web.config文件中读取客户端id和密钥,使用ConfigurationManager。需要记住的是将Authorization头设置为"Basic"而不是"Bearer",并且正确构建 'StringContent' 对象以适当地使用媒体类型(x-www-form-urlencoded)。

    //gets PayPal accessToken
    public async Task<ResponseT> InvokePostAsync<RequestT, ResponseT>(RequestT request, string actionUrl)
    {
        ResponseT result;

        // 'HTTP Basic Auth Post' <https://dev59.com/vXvaa4cB1Zd3GeqPADHP>
        string clientId = PayPalConfig.clientId;
        string secret = PayPalConfig.clientSecret;
        string oAuthCredentials = Convert.ToBase64String(Encoding.Default.GetBytes(clientId + ":" + secret));

        //base uri to PayPAl 'live' or 'stage' based on 'productionMode'
        string uriString = PayPalConfig.endpoint(PayPalConfig.productionMode) + actionUrl;

        HttpClient client = new HttpClient();

        //construct request message
        var h_request = new HttpRequestMessage(HttpMethod.Post, uriString);
        h_request.Headers.Authorization = new AuthenticationHeaderValue("Basic", oAuthCredentials);
        h_request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        h_request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue("en_US"));

        h_request.Content = new StringContent("grant_type=client_credentials", UTF8Encoding.UTF8, "application/x-www-form-urlencoded");

        try
        {
            HttpResponseMessage response = await client.SendAsync(h_request);

            //if call failed ErrorResponse created...simple class with response properties
            if (!response.IsSuccessStatusCode)
            {
                var error = await response.Content.ReadAsStringAsync();
                ErrorResponse errResp = JsonConvert.DeserializeObject<ErrorResponse>(error);
                throw new PayPalException { error_name = errResp.name, details = errResp.details, message = errResp.message };
            }

            var success = await response.Content.ReadAsStringAsync();
            result = JsonConvert.DeserializeObject<ResponseT>(success);
        }
        catch (Exception)
        {
            throw new HttpRequestException("Request to PayPal Service failed.");
        }

        return result;
    }

重要提示:使用Task.WhenAll()确保您获得结果。

    // gets access token with HttpClient call..and ensures there is a Result before continuing
    // so you don't try to pass an empty or failed token.
    public async Task<TokenResponse> AuthorizeAsync(TokenRequest req)
    {
        TokenResponse response;
        try
        {
            var task = new PayPalHttpClient().InvokePostAsync<TokenRequest, TokenResponse>(req, req.actionUrl);
            await Task.WhenAll(task);

            response = task.Result;
        }
        catch (PayPalException ex)
        {
            response = new TokenResponse { access_token = "error", Error = ex };
        }

        return response;
    }

我遇到了关于SSL/TLS的AuthenticationException-WebException。 - Kiquenet

0

我也曾经遇到过代码示例缺乏和响应错误及状态码等问题。

RestClient 是我非常喜欢的库,它在整合和处理 RESTful API 调用方面非常有帮助。

我希望这段使用 RestSharp 的小代码片段能够帮助到某些人:

        if (ServicePointManager.SecurityProtocol != SecurityProtocolType.Tls12) ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // forced to modern day SSL protocols
        var client = new RestClient(payPalUrl) { Encoding = Encoding.UTF8 };
        var authRequest = new RestRequest("oauth2/token", Method.POST) {RequestFormat = DataFormat.Json};
        client.Authenticator = new HttpBasicAuthenticator(clientId, secret);
        authRequest.AddParameter("grant_type","client_credentials");
        var authResponse = client.Execute(authRequest);
        // You can now deserialise the response to get the token as per the answer from @ryuzaki 
        var payPalTokenModel = JsonConvert.DeserializeObject<PayPalTokenModel>(authResponse.Content);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接