8

I'm trying to call Paypal api from my code. I set up the sandbox account and it works when I use curl but my code isn't working the same way, returning 401 Unauthorized instead.

Here's the curl command as documented by 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" 

UPDATE: Apparently the .Credentials doesn't do the trick, instead setting Authorization header manually works (see code)

Here's the code (trimmed to its essence):

  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);

This is the request from code captured by Fiddler (raw), there are no authorization parameters for some reason:

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
Sten Petrov
  • 10,943
  • 1
  • 41
  • 61
  • There's a missing space in the accept header but I can't see anything else obvious. Have you tried capturing the two requests to see what's different, e.g. using wireshark or a proxy such as Fiddler? – Rup Jun 02 '13 at 22:03
  • @Rup I tried with Fiddler, still having trouble capturing the curl request but the code request doesn't contain Auth headers (see update) – Sten Petrov Jun 02 '13 at 22:09
  • 1
    Yes some HTTP libraries e.g. Apache's won't send the credentials unless asked for them by the remote server, but I didn't know .NET's did too. Or at least it ought to then reply to the 401 with them. There may be a way to force it too on the request object? – Rup Jun 02 '13 at 22:10
  • 1
    There's an unpleasant work-around [in this old answer](http://stackoverflow.com/a/3266465/243245): construct your own basic authentication header. Or I was thinking of [HttpWebRequest.PreAuthenticate](http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.preauthenticate.aspx). – Rup Jun 02 '13 at 22:15
  • @Rup yeah I found that and worked around the problem. Thank you for looking into it – Sten Petrov Jun 02 '13 at 22:53
  • possible duplicate of [Forcing Basic Authentication in WebRequest](http://stackoverflow.com/questions/2764577/forcing-basic-authentication-in-webrequest) – Chris Pitman Jun 02 '13 at 22:58

4 Answers4

6

Hoping the following code help to anyone who is still looking for a good piece of cake to get connected to PayPal.

As many people, I've been investing a lot of time trying to get my PayPal token access without success, until I found the following:

public class PayPalClient
{
    public async Task RequestPayPalToken() 
    {
        // Discussion about SSL secure channel
        // http://stackoverflow.com/questions/32994464/could-not-create-ssl-tls-secure-channel-despite-setting-servercertificatevalida
        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; }
}

This code works pretty well for me, hoping for you too. The credits belong to Patel Harshal who posted his solution here.

ryuzaki
  • 86
  • 1
  • 2
3

This Works using HttpClient... 'RequestT' is a generic for the PayPal request arguments, however it is not used. The 'ResponseT' is used and it is the response from PayPal according to their documentation.

'PayPalConfig' class reads the clientid and secret from the web.config file using ConfigurationManager. The thing to remember is to set the Authorization header to "Basic" NOT "Bearer" and if and to properly construct the 'StringContent' object with right media type (x-www-form-urlencoded).

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

        // 'HTTP Basic Auth Post' <http://stackoverflow.com/questions/21066622/how-to-send-a-http-basic-auth-post>
        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;
    }

IMPORTANT: use Task.WhenAll() to ensure you have a result.

    // 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;
    }
Ansar
  • 206
  • 2
  • 5
3

Paypal has deprecated TLS 1.1, and only accepts 1.2 now. Unfortunately .NET (prior to version 4.7) uses 1.1 by default, unless you configure it otherwise.

You can turn on TLS 1.2 with this line. I recomend placing it Application_Start or global.asax.

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
0

I too suffered from a lack of example code and various issues with response errors and codes.

I am a big fan of RestClient as it helps a lot with integrations and the growing number of RESTful API calls.

I hope this small snippet of code using RestSharp helps someone: -

        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);
Cueball 6118
  • 517
  • 4
  • 16