PayPal IPN是否有任何样本

时间:2014-10-29 19:23:25

标签: c# asp.net-web-api paypal paypal-ipn

我有一个Asp.Net WEB API 2项目,我想实现一个即时支付通知(IPN)监听器控制器。

我无法找到任何示例和nuget包。我只需要承认用户使用Paypal上的标准html按钮付款。这很简单。

所有nuget包都是创建发票或自定义按钮。这不是我需要的东西

paypal上的示例适用于经典的asp.net,而不适用于MVC或WEB API MVC

我确定有人已经这样做了,当我开始编码时,我感觉我正在重新发明轮子。

是否有任何IPN侦听器控制器示例?

至少一个PaypalIPNBindingModel绑定Paypal查询。

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

        return Ok();
    }

编辑

到目前为止,我有以下代码

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

但是对于第3步,我尝试将我的模型发布为json但paypal返回HTML页面而不是VALIDATED或INVALID。我发现我必须使用application/x-www-form-urlencoded,并且参数的顺序相同。

如何获取请求网址?

我会使用查询网址并将&cmd=_notify-validate添加到其中

5 个答案:

答案 0 :(得分:12)

基于已接受的答案,我提出了以下代码,实现了ASP.NET MVC的IPN监听器。该解决方案已经部署,似乎可以正常工作。

[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");
    }
}

修改

让我分享一下我的经验 - 上面的代码到目前为止工作得很好,但突然之间它处理的IPN失败了,即responseString == "INVALID"

问题原来是我的帐户设置为使用PayPal默认的charset == windows-1252。但是,FormUrlEncodedContent使用UTF-8进行编码,因此验证失败,因为国家字符如“ř”。解决方案是将charset设置为UTF-8,这可以在Profile&gt;中完成。我的销售工具&gt; PayPal按钮语言编码&gt;更多选项,请参阅this SO thread

答案 1 :(得分:6)

这是我的代码

随意回顾是有问题的

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

答案 2 :(得分:2)

扩展Michal Hosala的answer,与PayPal成功握手需要两件事

首先,在向PayPal发出请求之前设置安全协议

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

其次,避免使用字典,因为要进行验证,PayPal要求数据以相同的顺序发回,并以cmd变量开头。我最终做了这个

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"));

答案 3 :(得分:2)

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

以下是卡洛斯所做的概述:

  1. 创建模型。根据您从PayPal获得的ipn响应,在模型中定义属性。

    public class IPNBindingModel
    {
        public string PaymentStatus { get; set; }
        public string RawRequest { get; set; }
        public string CustomField { get; set; }    
    }
    
  2. 创建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. 创建您的控制器。

    [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. 创建一个模型绑定器,用于定义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. 将模型绑定器注册到WebApiConfig.cs。 config.BindParameter(typeof(IPNBindingModel), new IPNModelBinder());

  6. 希望这有助于其他人。感谢Carlos Rodriguez提供的令人惊叹的代码。

答案 4 :(得分:0)

这里有一个官方的c#示例: https://github.com/paypal/ipn-code-samples 在路径\c#\paypal_ipn_mvc.cs

C#示例显示了一个ASP.NET MVC控制器,其操作响应IPN。