我正在将PayPal Express Checkout集成到现有的web应用程序中,我已经为该应用程序设置了Google Checkout和Amazon Payments(SimplePay和CBA)。所以我对此并不陌生。
所有的东西,包括即时更新回调,在使用沙盒设置的设置中都能正常工作。回调在沙盒中的HTTP和HTTPS上都能很好地工作。但一旦我切换到Live凭据,PayPal UI就会停止点击CallbackURL,转而使用SetExpressCheckout中发送的(回退)运费。显然,税收根本没有计算在内。
我使用的是最新版本的PayPal PHP SDK(版本106.0)。将设置从Sandbox切换到Live的唯一设置是:
- 用户名
- 密码
- 签名
-
模式(从
sandbox
到live
)
我在PayPal的社区帮助论坛上看到了一个类似的问题,但它没有提到解决方案。出于某种原因,我似乎无法在那个帖子中发帖。。。也许是因为它已经存档了。
2013年9月16日更新:似乎是HTTPS相关的问题。沙盒在HTTPS上也不起作用,但从浏览器中点击回调URL时不会出现SSL错误。证书是有效的,我们在网站上使用的其他支付供应商完全可以接受:谷歌钱包和亚马逊支付。
此问题由以下人员解决:1.将CallbackTimeout设置为62.使用PayPal接受的SSL证书。在我们的案例中,证书是由"Go Daddy Root安全证书-G2"颁发的,PayPal的系统不接受该证书。
我的Godaddy UCC证书有这个问题,但非UCC证书没有问题。所以我必须编写一个代理来将请求重定向到正确的位置。
但是最近我的Godaddy证书也失败了,所以他们似乎不那么宽容,而不是更宽容。它甚至在获得电动汽车证书时失败。
这毫无意义。回调URL所做的只是提供运费!这需要多安全的流程。太荒谬了!除了更改证书之外,目前没有其他解决方案,我甚至不能保证证书会起作用。
我设法用startcom.org
的免费证书让它工作(在Security Now播客上听说过)。
他们的网站很糟糕,但PayPal似乎可以接受他们的证书,而他们不可以接受Godaddy证书——都是4096位。此外,它是免费的:-)虽然只有效一年。
我一换上这个证书,效果就很好。
对于PayPal来说,GoDadaddy/Starfield颁发的证书绝对是不可接受的。
还有一个服务ngrok
,我发现它非常有用,可以让我在本地进行测试。它允许您设置一个可从外部访问的隧道代理。即使不接触防火墙设置,您也可以创建一个类似http://83def5f1.ngrok.io
的地址,PayPal可以访问该地址,并将流量重定向到您的本地机器,从而允许您设置断点。
代理页面
(与ngrok完全无关)
我宁愿不将此证书用于我的实时站点(另外,我在UCC证书上有几个不同的站点,我不想更改),所以我制作了一个代理页面,将请求重定向到正确的服务器。然后我只需将以下内容发送到PayPal
"https://example.com/paypalproxy.aspx?callbackUrl=" + HttpUtility.UrlEncode(callbackUrl)
(其中callbackUrl
是您将发送到PayPal的常规回调URL)
代理是一个ASPX页面-它不需要编译,只需放入.NET IIS网站即可。
您可以查看使用?debug=Y
调用它的最后一个请求/响应
<%@ Page language="c#" AutoEventWireup="true" %>
<%@ Import Namespace="System.IO"%>
<Script runat="server" language="C#">
private static string _lastURL;
private static string _lastRequest = "";
private static string _lastResponse = "";
private static DateTime? _lastTime;
private static int _lastDurationMs;
private void Page_Load(object sender, System.EventArgs e)
{
// no cache
Response.Cache.SetCacheability(HttpCacheability.NoCache);
var sw = new System.Diagnostics.Stopwatch();
var wc = new System.Net.WebClient();
sw.Start();
var callbackUrl = Request.Params["callbackUrl"];
var debugMode = Request.Params["debug"] == "Y";
if (debugMode)
{
Response.ContentType = "text/text";
Response.Write(_lastTime + "'n");
Response.Write("LastURL = ["+_lastURL+"]'n'n");
Response.Write("LastDuration = [" + _lastDurationMs +"]'n'n");
Response.Write("REQUEST: 'n['n "+_lastRequest.Replace("&", "&'n ")+"'n]'n'n");
Response.Write("RESPONSE: 'n['n "+_lastResponse.Replace("&", "&'n ")+"'n]");
Response.End();
return;
}
_lastDurationMs = -1;
_lastURL = Request.Params["callbackUrl"];
_lastTime = DateTime.Now;
if (callbackUrl.Contains("dev."))
{
throw new ApplicationException("Callback shouldn't be to a dev machine!");
}
if (callbackUrl.Contains("https") == false)
{
throw new ApplicationException("Callback must be https");
}
var newUri = callbackUrl + "?" + Request.Form.ToString();
var str = wc.DownloadString(newUri);
_lastRequest = Request.Form.ToString();
_lastResponse = str;
_lastDurationMs = (int)sw.ElapsedMilliseconds;
Response.Write(str);
Response.End();
}
</Script>