我有一个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
添加到其中
答案 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博客帖子。
以下是卡洛斯所做的概述:
创建模型。根据您从PayPal获得的ipn响应,在模型中定义属性。
public class IPNBindingModel
{
public string PaymentStatus { get; set; }
public string RawRequest { get; set; }
public string CustomField { get; set; }
}
创建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;
}
}
创建您的控制器。
[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;
}
}
}
创建一个模型绑定器,用于定义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;
}
}
}
将模型绑定器注册到WebApiConfig.cs。
config.BindParameter(typeof(IPNBindingModel), new IPNModelBinder());
希望这有助于其他人。感谢Carlos Rodriguez提供的令人惊叹的代码。
答案 4 :(得分:0)
这里有一个官方的c#示例:
https://github.com/paypal/ipn-code-samples
在路径\c#\paypal_ipn_mvc.cs
C#示例显示了一个ASP.NET MVC控制器,其操作响应IPN。