简体   繁体   English

PayPal IPN是否有任何样本

[英]Is there any sample for PayPal IPN

I have an Asp.Net WEB API 2 project and I would like to implement an Instant Payment Notification (IPN) listener controller. 我有一个Asp.Net WEB API 2项目,我想实现一个即时支付通知(IPN)监听器控制器。

I can't find any example and nuget package. 我找不到任何示例和nuget包。 All I need is to acknowledge that the user paid with the standard html button on Paypal. 我只需要承认用户使用Paypal上的标准html按钮付款。 It's quite simple. 这很简单。

All the nuget packages are to create invoice or custom button. 所有nuget包都是创建发票或自定义按钮。 It's not what I need 这不是我需要的

The samples on paypal are for classic asp.net and not for MVC or WEB API MVC paypal上的示例适用于经典的asp.net,而不适用于MVC或WEB API MVC

I'm sure somebody did that already and when I started coding I had a feeling that I was reinventing the wheel. 我确定有人已经这样做了,当我开始编码时,我感觉我正在重新发明轮子。

Is there any IPN listener controller example? 有没有IPN监听器控制器示例?

At least a PaypalIPNBindingModel to bind the Paypal query. 至少一个PaypalIPNBindingModel绑定Paypal查询。

    [Route("IPN")]
    [HttpPost]
    public IHttpActionResult IPN(PaypalIPNBindingModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest();
        }

        return Ok();
    }

EDIT 编辑

So far I have the following code 到目前为止,我有以下代码

        [Route("IPN")]
        [HttpPost]
        public void IPN(PaypalIPNBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                // if you want to use the PayPal sandbox change this from false to true
                string response = GetPayPalResponse(model, true);

                if (response == "VERIFIED")
                {

                }
            }
        }

        string GetPayPalResponse(PaypalIPNBindingModel model, 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
                    model.cmd = "_notify-validate";
                    response = client.PostAsync("cgi-bin/webscr", THE RAW PAYPAL REQUEST in THE SAME ORDER ).Result;

                    if(response.IsSuccessStatusCode)
                    {
                        responseState = response.Content.ReadAsStringAsync().Result;
                    }
                }
            }

            return responseState;
        }

but for the step 3 I tried to post my model as json but paypal returns a HTML page instead of VALIDATED or INVALID. 但对于第3步,我试图将我的模型发布为json,但paypal返回HTML页面而不是VALIDATED或INVALID。 I figured out that I have to use application/x-www-form-urlencoded and it the parameters as to be in the same order. 我发现我必须使用application/x-www-form-urlencoded ,并且参数的顺序相同。

How can I get the request URL? 如何获取请求URL?

I would use the query Url and add &cmd=_notify-validate to it 我会使用查询Url并&cmd=_notify-validate添加&cmd=_notify-validate

Based on accepted answer I came up with the following code implementing IPN listener for ASP.NET MVC. 基于已接受的答案,我提出了以下代码实现ASP.NET MVC的IPN侦听器。 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, ie responseString == "INVALID" . 让我分享一下我的经验 - 上面的代码到目前为止工作得很好,但突然之间它正在处理一个IPN失败,即responseString == "INVALID"

The issue turned out to be that my account was set up to use charset == windows-1252 which is PayPal default. 问题原来是我的帐户设置为使用charset == windows-1252 ,这是PayPal的默认值。 However, FormUrlEncodedContent uses UTF-8 for encoding and therefore the validation failed because of national characters like "ř". 但是, FormUrlEncodedContent使用UTF-8进行编码,因此验证失败,因为国家字符如“ř”。 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 . 解决方案是将charset设置为UTF-8,可以在Profile>我的销售工具> PayPal按钮语言编码>更多选项中完成,请参阅此SO线程

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

Extending Michal Hosala's answer , there are two things needed to get a successful handshake with PayPal 扩展Michal Hosala的答案 ,与PayPal成功握手需要两件事

First, setting the security protocol before making request to PayPal 首先,在向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. 其次,避免使用字典,因为要进行验证,PayPal要求数据以相同的顺序发回,并以cmd变量开头。 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"));

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. 我也在寻找类似OP的原始问题的解决方案Is there any IPN listener controller example? At least a PaypalIPNBindingModel to bind the Paypal query. 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. 我尝试了这个线程中提到的其他解决方案,它们都工作但我真的需要PayPal查询到模型解决方案所以我谷歌搜索直到我偶然发现Carlos Rodriguez的创建PayPal IPN Web API Endpoint博客帖子。

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. 根据您从PayPal获得的ipn响应,在模型中定义属性。

     public class IPNBindingModel { public string PaymentStatus { get; set; } public string RawRequest { get; set; } public string CustomField { get; set; } } 
  2. Create a PayPal Validator class. 创建一个PayPal Validator类。

     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. 创建一个模型绑定器,用于定义Web Api如何自动为您创建模型。

     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. 将模型绑定器注册到WebApiConfig.cs。 config.BindParameter(typeof(IPNBindingModel), new IPNModelBinder());

Hope this helps somebody else. 希望这有助于其他人。 Thank you Carlos Rodriguez for your amazing code. 感谢Carlos Rodriguez提供的令人惊叹的代码。

There is an official c# example here: https://github.com/paypal/ipn-code-samples in path \\c#\\paypal_ipn_mvc.cs 这里有一个官方的c#示例: 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. C#示例显示了一个ASP.NET MVC控制器,其中包含响应IPN的操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM