Skip to content

Instantly share code, notes, and snippets.

@jsdevtom
Last active November 14, 2023 19:17
Show Gist options
  • Save jsdevtom/562b1740164bae8e41f3bc37f28c5328 to your computer and use it in GitHub Desktop.
Save jsdevtom/562b1740164bae8e41f3bc37f28c5328 to your computer and use it in GitHub Desktop.
The solution to getting Paypal's verify endpoint to work was to serialize JSON manually like so:
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace server.Payment
{
public class PaymentController : Controller
{
private static readonly HttpClient client = new HttpClient();
[HttpPost("api/checkout/webhook")]
[AllowAnonymous]
public async Task<IActionResult> HandleWebhook()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
var headers = HttpContext.Request.Headers;
await VerifyEvent(json, headers);
// handle etc...
return Ok();
}
private async Task VerifyEvent(string json, IHeaderDictionary headerDictionary)
{
var bearerToken = await Authenticate();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
// !!IMPORTANT!!
// Without this direct JSON serialization, PayPal WILL ALWAYS return verification_status = "FAILURE".
// This is probably because the order of the fields are different and PayPal does not sort them.
var paypalVerifyRequestJsonString = $@"{{
""transmission_id"": ""{headerDictionary["PAYPAL-TRANSMISSION-ID"][0]}"",
""transmission_time"": ""{headerDictionary["PAYPAL-TRANSMISSION-TIME"][0]}"",
""cert_url"": ""{headerDictionary["PAYPAL-CERT-URL"][0]}"",
""auth_algo"": ""{headerDictionary["PAYPAL-AUTH-ALGO"][0]}"",
""transmission_sig"": ""{headerDictionary["PAYPAL-TRANSMISSION-SIG"][0]}"",
""webhook_id"": ""<get from paypal developer dashboard>"",
""webhook_event"": {json}
}}";
var content = new StringContent(paypalVerifyRequestJsonString, Encoding.UTF8, "application/json");
var resultResponse = await client.PostAsync("https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature", content);
var responseBody = await resultResponse.Content.ReadAsStringAsync();
var verifyWebhookResponse = JsonConvert.DeserializeObject<VerifyWebhookResponse>(responseBody);
if (verifyWebhookResponse.verification_status != "SUCCESS")
{
throw new Exception("failed to verify webhook response");
}
}
private async Task<string> Authenticate()
{
// TODO: make all hard coded values configurable
var request = WebRequest.Create("https://api-m.sandbox.paypal.com/v1/oauth2/token");
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Headers.Add("grant_type", "client_credentials");
var postData = "grant_type=client_credentials";
var byteArray = Encoding.UTF8.GetBytes(postData);
var dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;
var clientId = "<get from paypal developer dashboard>";
var clientSecret = "<get from paypal developer dashboard>";
var encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1")
.GetBytes(clientId + ":" + clientSecret));
request.Headers.Add("Authorization", "Basic " + encoded);
try
{
var response = await request.GetResponseAsync();
using (dataStream = response.GetResponseStream())
{
var reader = new StreamReader(dataStream);
var responseFromServer = reader.ReadToEnd();
Console.WriteLine(responseFromServer);
var token = JsonConvert.DeserializeObject<TokenResponse>(responseFromServer);
return token.access_token;
}
}
catch (WebException e)
{
string content;
using (var reader = new StreamReader(e.Response.GetResponseStream()))
{
content = reader.ReadToEnd();
Console.WriteLine("content", content);
}
throw;
}
}
}
}
@itfm
Copy link

itfm commented Jul 21, 2022

What does VerifyWebhookResponse look like or where can I find a sample response from PayPal?

@HenryBJ
Copy link

HenryBJ commented Aug 4, 2022

@itfm It must look like this:

{
  "verification_status": "SUCCESS"
}

@HenryBJ
Copy link

HenryBJ commented Aug 4, 2022

@jsdevtom Thanks for your help bro !!!

@jsdevtom
Copy link
Author

jsdevtom commented Aug 8, 2022

@HenryBJ You're very welcome

@gsmcdonald
Copy link

@jsdevtom Thank you! I wish PayPal's docs were as helpful as your example! I was seeing intermittent failures, which make it even more confusing. This solved it.

@jsdevtom
Copy link
Author

@gsmcdonald No problem

@Ohmniox
Copy link

Ohmniox commented Nov 14, 2023

Thanks for the detailed explanation. Unfortunately it is not working me! Always giving me FAILURE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment