Validating PayPal Webhook calls with PayPal .NET SDK in Sandbox - c#

I've been trying to set up PayPal Webhooks (in Sandbox mode) to receive notifications about declined and successful payments. My problem is that I can't get validation working. Some details about my attempts:
The app is an OWIN self-hosted Web API 2.
Hosted as an Azure Web App, tested on Azure as well.
I set the Paypal Webhook receiver URL in the Paypal dashboard to the URL of my endpoint on Azure.
I used the Paypal Webhooks simulator from the Paypal dashboard to send a message to the Azure endpoint.
I tried listening for Webhook calls two ways:
ASP.NET Webhook Receivers (http://blogs.msdn.com/b/webdev/archive/2015/09/04/introducing-microsoft-asp-net-webhooks-preview.aspx), which didn't work. I get the error message "WebHook validation failed: "
Tried creating a Web API endpoint to receive and validate the request, didn't work either. Code here:
[HttpPost]
public async Task<IHttpActionResult> PaymentCaptureCompleted()
{
// Get the received request's headers
NameValueCollection nvc = new NameValueCollection();
foreach (var item in Request.Headers)
{
nvc.Add(item.Key, string.Join(",", item.Value));
}
// Get the received request's body
var requestBody = await Request.Content.ReadAsStringAsync();
var isValid = WebhookEvent.ValidateReceivedEvent(Api, nvc, requestBody, ConfigurationManager.AppSettings["paypal.webhook.id"]);
if (isValid)
{
return Ok();
}
else
{
return BadRequest("Could not validate request");
}
}
There are a lot more details to this of course, but I'm not sure how much information is required to answer my question. Just let me know what you need and I'll edit this question.

Please refer PayPal Dot Net SDK for code samples. https://github.com/paypal/PayPal-NET-SDK
Also if your simulator is not working, to rule out if there is something wrong with the configuration of webhook or the azure, you may want to use Runscope. Ypu can configure a Runscope bucket as a webhook endpoint and If you are getting a webhook notification there, you may need to make changes in the Azure config.

Really don`t go to the documentation. It is old but objects is still good.
You can use WebhookEvent class for it.
Just use this action in your controller.
public JsonResult Index(WebhookEvent event)
{
// event has all the data
return Json(new { success = true });
}

Validation does not work for either IPN sandbox nor Webhook events from the mock. It's stated in the PayPal documentation. Validation only works on the production environment of PayPal.

Wrap WebhookEvent.ValidateReceivedEvent in a try catch. If the call fails, it can just hang without that. The exception will show you the error.
try
{
var isValid = WebhookEvent.ValidateReceivedEvent(apiContext, nv, requestBody, hookId);
}
catch(Exception e)
{
}
In my case the exception said "Unable to load trusted certificate"

Related

Unable to sign in to login.microsoftonline.com oauth 2 authorize endpoint

I've tried different ways to connect the Microsoft sign in function which open a webpage so you can use things like sign in with MFA. I manage to get this to work in Postman and now im trying it in C# particularly in .NET MVC 5.
HomeController:
public ActionResult TestAuth()
{
HttpClient client = new HttpClient();
var bodyParams = new Dictionary<string, string>();
bodyParams.Add("client_id", "{my_client_id}");
bodyParams.Add("client_secret", "{my_client_secret}");
bodyParams.Add("scope", "openid");
bodyParams.Add("redirect_uri", "https://localhost");
bodyParams.Add("grant_type", "authorization_code");
var response = client.PostAsync("https://login.microsoftonline.com/{my_tenant_id}/oauth2/v2.0/authorize", new FormUrlEncodedContent(bodyParams)).Result;
return View("TestAuth", new { response.Content.ReadAsStringAsync().Result });
}
View TestAuth.cshtml:
#model dynamic
#Html.Raw(Model)
If i sign in with my email on that domain, or any text at all really, i get this message. I cannot see why this issue occurs it gives me zero information what to do next more than just trying until you make it basically :). I've looked at tons of different Microsoft documentations, Stack posts, forums etc but with no success.
The postman call example:
Is it possible I'm doing something wrong in the request in the c# code or am i missing something important like configurations in Azure AD etc?
I'm up for anything that will work that i can sign into a Microsoft account that use MFA, then i can use their login to fetch data from Microsoft Graph based on their permissions basically.
P.S. I also can fetch data with the access token generated from postman so it's working as expected. I only need to "convert the postman call to c#" to make it work esentially. Any help is appreciated :)
You’re trying to do an oauth2 request from the controller. The request you’re sending is incorrect.
Microsoft made a great sample on how to use the Microsoft identity platform in a dotnet application https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/1-WebApp-OIDC
In a nutshell you redirect the user to the endpoint (so not a get/post from the controller, but actually a redirect 302 response to the token url).
The user then has to login and is redirected to the webapplication.
Your webapplication will get an authorization code that is has to exchange for an access token by a post request.
Postman does this for you, but in order to do it in dotnet core, just follow the sample.
I didn't find a soultion to this specific problem what i did find was another guide which led me to this github project https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect
Which had similar code in the Startup.cs file but actually had some examples like SendMail and ReadMail etc which was fetched from ms graph api. This gave me some idea of how this project was structured compared to mine. So one thing that was missing was this part I couldnt figure out:
IConfidentialClientApplication app = await MsalAppBuilder.BuildConfidentialClientApplication();
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId());
So the Msal app builder which is a custom made thingy i needed to get the current user etc which i needed. This works fine and after that i can start doing requests to the graph api like adding scopes etc and make http request.
Example see groups:
[Authorize]
[HttpGet]
public async Task<ActionResult> GetGroups()
{
IConfidentialClientApplication app = await MsalAppBuilder.BuildConfidentialClientApplication();
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId());
string[] scopes = { "GroupMember.Read.All", "Group.Read.All", "Group.ReadWrite.All", "Directory.Read.All", "Directory.AccessAsUser.All", "Directory.ReadWrite.All" };
AuthenticationResult result = null;
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/groups");
try
{
//Get acccess token before sending request
result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
if (result != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
//Request to get groups
HttpResponseMessage response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
ViewBag.Groups= response.Content.ReadAsStringAsync().Result;
return View("MyView");
}
}
}
catch (Exception ex)
{
Response.Write($"Error occured:{System.Environment.NewLine}{ex}");
}
return View();
}

How to get response from IPN cryptocurrencies

We're trying to receive payment with cryptocurrencies using coinpayment IPN. We are able to create a request and able to do a payment. However, not able to get success or failure response while user come back to the seller side.
Here is how payment request created:
public ActionResult IPN()
{
var uri = new UriBuilder("https://www.coinpayments.net/index.php");
uri.SetQueryParam("cmd", "_pay_auto");
uri.SetQueryParam("merchant", "merchant_key");
uri.SetQueryParam("allow_extra", "0");
uri.SetQueryParam("currency", "USD");
uri.SetQueryParam("reset", "1");
uri.SetQueryParam("success_url", "http://localhost:49725/home/SuccessResponse"); //todo: redirect to confirm success page
uri.SetQueryParam("key", "wc_order_5b7b84b91a882");
uri.SetQueryParam("cancel_url", "http://localhost:49725/home/FailiureResponse");
uri.SetQueryParam("order_id", "36");
uri.SetQueryParam("invoice", "PREFIX-36");
uri.SetQueryParam("ipn_url", "http://localhost:49725/?wc-api=WC_Gateway_Coinpayments");
uri.SetQueryParam("first_name", "John");
uri.SetQueryParam("last_name", "Smith");
uri.SetQueryParam("email", "a#a.com");
uri.SetQueryParam("want_shipping", "1");
uri.SetQueryParam("address1", "228 Park Ave S&address2");
uri.SetQueryParam("city", "New York");
uri.SetQueryParam("state", "NY");
uri.SetQueryParam("zip", "10003-1502");
uri.SetQueryParam("country", "US");
uri.SetQueryParam("item_name", "Order 33");
uri.SetQueryParam("quantity", "1");
uri.SetQueryParam("amountf", "100.00000000");
uri.SetQueryParam("shippingf", "0.00000000");
return Redirect(uri.ToString());
}
This will be redirected to the coinpayment site, once payment done, it is showing the following screen.
And trying to get data when user click on back to seller's site, I have tried to get data using Request.Form, but not getting any value in form.
The same thing, working with this woocommerce code, but I have no idea of PHP and how they are dealing with it.
Any thought to get IPN response?
Note: there is no development documentation or sample code available for IPN in .NET
Edit
I'm trying to get value from IPN success
Public ActionResult SuccessResponse()
{
var ipn_version = Request.Form["ipn_version"];
var ipn_id = Request.Form["ipn_id"];
var ipn_mode = Request.Form["ipn_mode"];
var merchant = Request.Form["merchant"];
var txn_id = Request.Form["txn_id"];
var status = Request.Form["status"];
return Content(status);
}
You cannot use localhost for a IPN callback. You must use a public domain name.
As an example I would change the following parameters:
var uri = new UriBuilder("https://www.coinpayments.net/api.php");
uri.SetQueryParam("success_url", "http://kugugshivom-001-site1.atempurl.com/Home/SuccessResponse");
uri.SetQueryParam("cancel_url", "http://kugugshivom-001-site1.atempurl.com/Home/FailiureResponse");
uri.SetQueryParam("ipn_url", "http://kugugshivom-001-site1.atempurl.com/Home/CoinPaymentsIPN"); // Public ActionResult CoinPaymentsIPN()
Since you are creating your own gateway you also need to implement it properly as described in the documentation at CoinPayments API and Instant Payment Notifications (IPN).
I have tested your success_url endpoint, and got status code: 100 (when entering status:100). I see you use form-data, but I don't know if that's on purpose / required.
Postman POST http://kugugshivom-001-site1.atempurl.com/Home/SuccessResponse
In Body tab form-data is selected with Bulk Edit values:
ipn_version:1.0
ipn_type:api
ipn_mode:hmac
ipn_id:your_ipn_id
merchant:your_merchant_id
txn_id:your_transaction_id
status:100
As updated answer stated by #Gillsoft AB, you should need to use valid IPN URL from the code end. Also webhook would not work with localhost. thus, you should listen the request with live server.
Simplest way to check webhook response is to use online tool such as Webhook Tester, it will provide an URL which you have to set as your IPN URL, whenever server will sends the data, you can simply see it to the web. To check that, create one URL and set as your IPN URL as below:
uri.SetQueryParam("ipn_url", "https://webhook.site/#/457f5c55-c9ce-4db4-8f57-20194c17d0ae");
After that run the payment cycle from local machine, payment server will sends notification to that IPN URL.
Make sure you understood it right! success_url and cancel_url are for user redirection, you will not get any response code over there, inspection of seller's store URL give your exact same URL that you have been passing though, so it is recommended to use unique URLs for each order(i.e add order id at last to the URL) which will give you an idea which order payment has been done or canceled.
http://localhost:49725/home/SuccessResponse?orderid=123
In order to test your local code, add following changes and deployed it to server.
1) Add one new method which will listen IPN response
[ValidateInput(false)]
public ActionResult IPNHandler()
{
byte[] param = Request.BinaryRead(Request.ContentLength);
string strRequest = Encoding.ASCII.GetString(param);
//TODO: print string request
//nothing should be rendered to visitor
return Content("");
}
2) Pass IPN URL while creating a request:
public ActionResult IPN()
{
var uri = new UriBuilder("https://www.coinpayments.net/index.php");
...
..
uri.SetQueryParam("success_url", "http://localhost:49725/home/SuccessResponse");
uri.SetQueryParam("cancel_url", "http://localhost:49725/home/FailiureResponse");
uri.SetQueryParam("ipn_url", "http://localhost:49725/home/IPNHandler");
....
..
return Redirect(uri.ToString());
}
You will get all status code responses in IPNHandler method.
Hope this helps!

Verify if email exists in database with remote validation by consuming with the client (MVC) the action in the web API?

I have a web API (ASP.net) and I want to have 2 clients (Xamarin and ASP.net MVC).
My web API works, I tested it with Postman. I tried to add a new user with the web client by consuming the WebAPI and it works. But now I want to check if email already exists in the database when the user enters his email in the form.
I saw that it's possible to use a RemoteAttribute to do this but this solution does not work for me.
I can check in the API if the email exists.
[AllowAnonymous]
[HttpGet]
public async Task<JsonResult<bool>> VerifyEmailAsync(string email)
{
var user = await UserManager.FindByEmailAsync(email);
if (user == null)
{
return Json(false);
}
else
{
return Json(true);
}
}
I can retrieve true or false but I don't know how I can consume this in my MVC client.
I hope you understand my problem.
It's not possible if you return boolean (true/false) from your VerifyEmailAsync method. Remote attribute will work if you change the result as Task<JsonResult>
So what I recommend; you can return http response code like this;
HTTP response code for POST when resource already exists
So you will be closer to web api standards. But if you have a lot of business messages like this, you could think json-envelope approach.

Stripe .net "The signature for the webhook is not present in the Stripe-Signature header."

I am using Stripe.net SDK from NuGet. I always get the
The signature for the webhook is not present in the Stripe-Signature header.
exception from the StripeEventUtility.ConstructEvent method.
[HttpPost]
public void Test([FromBody] JObject incoming)
{
var stripeEvent = StripeEventUtility.ConstructEvent(incoming.ToString(), Request.Headers["Stripe-Signature"], Constants.STRIPE_LISTENER_KEY);
}
The WebHook key is correct, the Request Header contains "Stripe-Signature" keys.
I correctly receive incoming data from the Webhook tester utility (using nGrok with Visual Studio).
the secureCompare method seems to be the culprit => StripeEventUtility.cs
I tried to manipulate the incoming data from Stripe (Jobject, string, serializing...). The payload signature may cause some problem.
Has anybody had the same problem?
As per #Josh's comment, I received this same error
The signature for the webhook is not present in the Stripe-Signature header.
This was because I had incorrectly used the API secret (starting with sk_) to verify the HMAC on EventUtility.ConstructEvent.
Instead, Stripe WebHook payloads are signs with the Web Hook Signing Secret (starting with whsec_) as per the docs
The Web Hook Signing Secret can be obtained from the Developers -> WebHooks page:
The error can also occur because you are using the secret from the Stripe dashboard. You need to use the temporary one generated by the stripe cli if you are using the CLI for testing.
To obtain it run this:
stripe listen --print-secret
Im not sure about reason of this, but Json readed from Request.Body has a little bit different structure than parsed with [FromBody] and Serialized to string.
Also, you need to remove [FromBody] JObject incoming because then Request.Body will be empty.
The solution you need is:
[HttpPost]
public void Test()
{
string bodyStr = "";
using (var rd = new System.IO.StreamReader(Request.Body))
{
bodyStr = await rd.ReadToEndAsync();
}
var stripeEvent = StripeEventUtility.ConstructEvent(bodyStr, Request.Headers["Stripe-Signature"], Constants.STRIPE_LISTENER_KEY);
}
I was also receiving the same exception message when I looked at it in the debugger but when I Console.WriteLine(e.Message); I received a different exception message.
Received event with API version 2020-08-27, but Stripe.net 40.5.0 expects API version 2022-08-01. We recommend that you create a WebhookEndpoint with this API version. Otherwise, you can disable this exception by passing throwOnApiVersionMismatch: false to Stripe.EventUtility.ParseEvent or Stripe.EventUtility.ConstructEvent, but be wary that objects may be incorrectly deserialized.
I guess your best bet is to set throwOnApiVersionMismatch to false;
EventUtility.ParseEvent(json, header, secret, throwOnApiVersionMismatch: false)

SetExpressCheckout token received but Sandbox claims transaction has expired

I've been attempting to process a very simple express checkout Sandbox transaction using the C# Paypal API, but keep getting a session timeout error on the Paypal website after the redirect.
I should emphasize that I get a successful ACK response from SetExpressCheckout along with a Token string.
Here is an example of the checkout URL I've been trying to redirect to:
https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-9RY2628262462061J
My return address is localhost, but I couldn't see anywhere that this would be a problem in Sandbox.
On trying to redirect to the Sandbox I arrive not at the checkout URL but at this address:
https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_flow&SESSION=CzvBHQErPEHw5gOt51FV88G_4L9HUCLypeGkwVZLW6mkWsZOofIpFR2K6Aa&dispatch=50a222a57771920b6a3d7b606239e4d529b525e0b7e69bf0224adecfb0124e9b61f737ba21b081984719ecfa9a8ffe80733a1a700ced90ae
And see the following error message:
"This transaction has expired. Please return to the recipient's website to complete your transaction using their regular checkout flow."
How can the transaction be timing out when [1] I have a successful API response along with a token and [2] I literally redirect there immediately after getting the token.
Does anyone have any idea what's going on here?
If it helps here is the C# I wrote to access the API. As noted, I get a success ACK response plus a token.
try
{
var details = ToPaymentDetails(data);
var request = new SetExpressCheckoutReq();
request.SetExpressCheckoutRequest = new SetExpressCheckoutRequestType
{
SetExpressCheckoutRequestDetails = details,
Version = Version
};
var result = await client.SetExpressCheckoutAsync(credentials, request);
var response = result.SetExpressCheckoutResponse1;
FailOnError(response);
return Result.Success(response.Token);
}
catch (Exception ex)
{
return Result.Error<string>("Received an error from Paypal.SetExpressCheckout.", exception: ex);
}
The EC token only last for 3 hours. You will have to recall the SetEC API to get a new EC token and proceed with the checkout flow.
I am not closer to understanding why the SOAP API call generates a token that is rejected by Paypal, but I found that I was able to reach the Sandbox payment page if I used the SDK to generate a payment.
This hasn't exactly been an enlightening or educational experience ...

Categories

Resources