Last active
November 14, 2023 19:17
-
-
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 It must look like this:
{
"verification_status": "SUCCESS"
}
@jsdevtom Thanks for your help bro !!!
@HenryBJ You're very welcome
@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.
@gsmcdonald No problem
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
What does VerifyWebhookResponse look like or where can I find a sample response from PayPal?