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;
}
}
}
}
@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