Is there any sample for PayPal IPN

后端 未结 5 1132
半阙折子戏
半阙折子戏 2021-02-05 20:47

I have an Asp.Net WEB API 2 project and I would like to implement an Instant Payment Notification (IPN) listener controller.

I can\'t find any example and nuget package

相关标签:
5条回答
  • 2021-02-05 21:26

    Extending Michal Hosala's answer, there are two things needed to get a successful handshake with PayPal

    First, setting the security protocol before making request to PayPal

    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
    

    Second, avoiding the dictionary because for verification, PayPal requires the data to be posted back in the same order and preceded by the cmd variable. I ended up doing this

    Request.InputStream.Seek(0, SeekOrigin.Begin);
    string rawRequestBody = new StreamReader(Request.InputStream).ReadToEnd();
    var ipnVarsWithCmd = rawRequestBody.Split('&').Select(x => new KeyValuePair<string, string>(x.Split('=')[0], x.Split('=')[1])).ToList();
    ipnVarsWithCmd.Insert(0, new KeyValuePair<string, string>("cmd", "_notify-validate"));
    
    0 讨论(0)
  • 2021-02-05 21:36

    This is my code

    Feel free to review is something is wrong

            [Route("IPN")]
            [HttpPost]
            public IHttpActionResult IPN()
            {
                // if you want to use the PayPal sandbox change this from false to true
                string response = GetPayPalResponse(true);
    
                if (response == "VERIFIED")
                {
                    //Database stuff
                }
                else
                {
                    return BadRequest();
                }
    
                return Ok();
            }
    
            string GetPayPalResponse(bool useSandbox)
            {
                string responseState = "INVALID";
                // Parse the variables
                // Choose whether to use sandbox or live environment
                string paypalUrl = useSandbox ? "https://www.sandbox.paypal.com/"
                : "https://www.paypal.com/";
    
                using (var client = new HttpClient())
                {
                    client.BaseAddress = new Uri(paypalUrl);
                    client.DefaultRequestHeaders.Accept.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
    
                    //STEP 2 in the paypal protocol
                    //Send HTTP CODE 200
                    HttpResponseMessage response = client.PostAsJsonAsync("cgi-bin/webscr", "").Result;
    
                    if (response.IsSuccessStatusCode)
                    {
                        //STEP 3
                        //Send the paypal request back with _notify-validate
                        string rawRequest = response.Content.ReadAsStringAsync().Result;
                        rawRequest += "&cmd=_notify-validate";
    
                        HttpContent content = new StringContent(rawRequest);
    
                        response = client.PostAsync("cgi-bin/webscr", content).Result;
    
                        if(response.IsSuccessStatusCode)
                        {
                            responseState = response.Content.ReadAsStringAsync().Result;
                        }
                    }
                }
    
                return responseState;
            }
    
    0 讨论(0)
  • 2021-02-05 21:39

    There is an official c# example here: https://github.com/paypal/ipn-code-samples in path \c#\paypal_ipn_mvc.cs

    The C# example shows an ASP.NET MVC controller with an action that responds to the IPN.

    0 讨论(0)
  • 2021-02-05 21:41

    Based on accepted answer I came up with the following code implementing IPN listener for ASP.NET MVC. The solution has already been deployed and appears to work correctly.

    [HttpPost]
    public async Task<ActionResult> Ipn()
    {
        var ipn = Request.Form.AllKeys.ToDictionary(k => k, k => Request[k]);
        ipn.Add("cmd", "_notify-validate");
    
        var isIpnValid = await ValidateIpnAsync(ipn);
        if (isIpnValid)
        {
            // process the IPN
        }
    
        return new EmptyResult();
    }
    
    private static async Task<bool> ValidateIpnAsync(IEnumerable<KeyValuePair<string, string>> ipn)
    {
        using (var client = new HttpClient())
        {
            const string PayPalUrl = "https://www.paypal.com/cgi-bin/webscr";
    
            // This is necessary in order for PayPal to not resend the IPN.
            await client.PostAsync(PayPalUrl, new StringContent(string.Empty));
    
            var response = await client.PostAsync(PayPalUrl, new FormUrlEncodedContent(ipn));
    
            var responseString = await response.Content.ReadAsStringAsync();
            return (responseString == "VERIFIED");
        }
    }
    

    EDIT:

    Let me share my experience - the above code was working just fine up until now, but suddenly it failed for one IPN it was processing, i.e. responseString == "INVALID".

    The issue turned out to be that my account was set up to use charset == windows-1252 which is PayPal default. However, FormUrlEncodedContent uses UTF-8 for encoding and therefore the validation failed because of national characters like "ř". The solution was to set charset to UTF-8, which can be done in Profile > My selling tools > PayPal button language encoding > More Options, see this SO thread.

    0 讨论(0)
  • I was also looking for a solution similar to the OP's original question Is there any IPN listener controller example? At least a PaypalIPNBindingModel to bind the Paypal query. and I got to this page. I tried the other solutions mentioned in this thread, they all worked but I really need the PayPal query-to-model solution so I googling until I stumbled on Carlos Rodriguez's Creating a PayPal IPN Web API Endpoint blogpost.

    Here's an overview on what Carlos did:

    1. Create a model. Base the properties you'll define in the model from the ipn response you'll get from PayPal.

      public class IPNBindingModel
      {
          public string PaymentStatus { get; set; }
          public string RawRequest { get; set; }
          public string CustomField { get; set; }    
      }
      
    2. Create a PayPal Validator class.

      public class PayPalValidator
      {
          public bool ValidateIPN(string body)
          {
              var paypalResponse = GetPayPalResponse(true, body);
              return paypalResponse.Equals("VERIFIED");
          }
      
          private string GetPayPalResponse(bool useSandbox, string rawRequest)
          {
              string responseState = "INVALID";
              string paypalUrl = useSandbox ? "https://www.sandbox.paypal.com/"
              : "https://www.paypal.com/";
      
              ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
              using (var client = new HttpClient())
              {
                  client.BaseAddress = new Uri(paypalUrl);
                  client.DefaultRequestHeaders.Accept.Clear();
                  client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
                  HttpResponseMessage response = client.PostAsJsonAsync("cgi-bin/webscr", "").Result;
                  if (response.IsSuccessStatusCode)
                  {
                      rawRequest += "&cmd=_notify-validate";
                      HttpContent content = new StringContent(rawRequest);
                      response = client.PostAsync("cgi-bin/webscr", content).Result;
                      if (response.IsSuccessStatusCode)
                      {
                          responseState = response.Content.ReadAsStringAsync().Result;
                      }
                  }
              }
              return responseState;
          }
      }
      
    3. Create your controller.

      [RoutePrefix("paypal")]
      public class PayPalController : ApiController
      {
          private PayPalValidator _validator;
      
          public PayPalController()
          {
             this._validator = new PayPalValidator();
          }
      
          [HttpPost]
          [Route("ipn")]
          public void ReceiveIPN(IPNBindingModel model)
          {
              if (!_validator.ValidateIPN(model.RawRequest)) 
                  throw new Exception("Error validating payment");
      
              switch (model.PaymentStatus)
              {
      
                  case "Completed":
                      //Business Logic
                      break;
              }
         }
      }
      
    4. Create a model binder that will define how Web Api will automatically create the model for you.

      public class IPNModelBinder : IModelBinder
      {
          public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
          {
              if (bindingContext.ModelType != typeof(IPNBindingModel))
              {
                 return false;
              }
          var postedRaw = actionContext.Request.Content.ReadAsStringAsync().Result;
      
          Dictionary postedData = ParsePaypalIPN(postedRaw);
          IPNBindingModel ipn = new IPNBindingModel
          {
              PaymentStatus = postedData["payment_status"],
              RawRequest = postedRaw,
              CustomField = postedData["custom"]
          };
      
          bindingContext.Model = ipn;
          return true;
      }
      
      private Dictionary ParsePaypalIPN(string postedRaw)
      {
          var result = new Dictionary();
          var keyValuePairs = postedRaw.Split('&');
          foreach (var kvp in keyValuePairs)
          {
              var keyvalue = kvp.Split('=');
              var key = keyvalue[0];
              var value = keyvalue[1];
              result.Add(key, value);
          }
      
          return result;
      }
      }
       }
      
    5. Register your model binder to WebApiConfig.cs. config.BindParameter(typeof(IPNBindingModel), new IPNModelBinder());

    Hope this helps somebody else. Thank you Carlos Rodriguez for your amazing code.

    0 讨论(0)
提交回复
热议问题