PHP Paypal Auth/Capture NVP 集成问题

2021-12-29 00:00:00 curl php paypal payment express-checkout

我们使用 NVP 集成和 php-curl 实现了 Paypal 授权和捕获流程.
完整的流程在PayPal开发者网站上有描述:https://developer.paypal.com/webapps/developer/docs/classic/express-checkout/ht_ec-singleAuthPayment-curl-etc/

We implemented Paypal Authorization and Capture flow using NVP integration and php-curl.
The complete process is described on the PayPal developer website : https://developer.paypal.com/webapps/developer/docs/classic/express-checkout/ht_ec-singleAuthPayment-curl-etc/

在我们的网站上,当前的支付场景是:
- 首先,用户点击按钮发起支付授权,将他重定向到 PayPal 网站 (SetExpressCheckout with paymentaction=Authorization)
- 如果用户在 PayPal 网站上成功确认付款,他将在特定的成功页面上重定向到我们的网站
- 此成功页面"从 PayPal 网站获取 令牌 和 PayerID,然后我们调用 GetExpressCheckoutDetails 来检查此授权的状态和金额
- 如果一切正常,我们会告诉 PayPal 确认此授权(DoExpressCheckoutPayment with paymentaction=Authorization),我们会获得一个授权 ID 以存储到我们的数据库中
- 稍后,其他人可以使用我们存储的授权 ID (DoCapture) 通过单击按钮来结算交易

On our website, the current payment scenario is :
- First, an user click on a button to initiate a payment authorization, redirecting him on the PayPal website (SetExpressCheckout with paymentaction=Authorization)
- If the user succesfully confirmed the payment on the PayPal website, he is redirected to our website on a specific success page
- This "success page" gets a token and a PayerID from the PayPal website, we then call GetExpressCheckoutDetails to check the status and the amount of this authorization
- If everything is ok, we tell PayPal to confirm this authorization (DoExpressCheckoutPayment with paymentaction=Authorization) and we get an authorization ID to store into our database
- Later, someone else can settle the transaction by clicking on a button, using the authorization ID we stored (DoCapture)

根据 PayPal 文档:

According to the PayPal documentation :

PayPal 在三天内兑现 100% 的授权资金
买家和商家的账户不能被关闭,如果有待定(未解决)授权
https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/authcapture/

PayPal honors 100% of authorized funds for three days
The accounts of buyers and merchants cannot be closed if there is a pending (unsettled) authorization
https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/authcapture/

在我们的网站上,如果未在 24 小时内结算,授权将自动作废.(使用 crontab)

On our website, authorizations are automatically voided if they are not settled within 24 hours. (using crontab)

最后一部分出现问题(当我们调用确认"函数时):当用户单击确认"按钮时,似乎有时 curl 请求需要时间从 PayPal 取回交易 ID.
发生这种情况时,用户通常会关闭网页,PayPal 确认授权(从而进行汇款),但我们的网站未收到通知,因为以下代码(来自下面的源代码"部分)尚未执行或到达:

A problem occurs on the last part (when we call the "confirm" function) : When an user click on a "confirm" button, it seems that sometimes the curl request is taking time to get a transaction ID back from PayPal.
When this happens, the user usually close the webpage, PayPal confirm the authorization (and thus money transfert) but our website is not notified about this because the following code (from the "Source Code" section below) has not been executed or reached :

if ($transaction_id) {
    /*
     * [...]
     * Everything is ok, payment has been performed
     * so we do everything to give our user what he asked for
     */
} else {
    // Error : No transaction id
}

因为脚本在得到 curl 响应之前就停止了.
此外,如果我们再次尝试单击该按钮,PayPal 会告诉我们授权 ID 不存在(因为已执行).

Because the script stopped before getting the curl response.
Moreover, if we try to click on the button again, PayPal tells us that the authorization ID doesn't exist (because already performed).

但有时一切正常,没有任何问题或滞后.

But sometimes everything works well without any problem or lag.

/*
 * This is our main function, called when
 * we have to settle our transaction 
 * when an user click on a "confirm" button
**/
public function confirm($cart_id) {
    /*
     * [...]
     * We check lot of stuff to be sure this user 
     * can perform this action
     */

    // We get theses values from the database
    authorization_id = "lorem ipsum";
    $amount = 10; 

    // We tell PayPal to settle the transaction
    $transaction_id = $this->settle_transaction($authorization_id, $amount);
    if ($transaction_id) {
        /*
         * [...]
         * Everything is ok, payment has been performed
         * so we do everything to give our user what he asked for
         */
    } else {
        // Error : No transaction id
    }
}

private function settle_transaction($authorization_id, $amount) {
    // Our credentials
    $params = array(
        "USER" => $this->paypal_user,
        "PWD" => $this->paypal_pwd,
        "SIGNATURE" => $this->paypal_signature,
        "VERSION" => 95
    );
    $params["METHOD"] = "DoCapture";
    $params["AUTHORIZATIONID"] = $authorization_id;
    $params["AMT"] = $amount;
    $params["CURRENCYCODE"] = "EUR";
    $params["COMPLETETYPE"] = "Complete";

    $result = $this->curl($params);
    if ($result) {
        // We check that this PayPal request has been successful
        if ($result["ACK"] == "Success") {
            $transaction_id = $result["TRANSACTIONID"];
            if ($result["PAYMENTSTATUS"] == "Completed") {
                return $transaction_id;
            }
        }
    }
    return NULL;
}


private function curl($params) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $this->paypal_endpoint);
    curl_setopt($ch, CURLOPT_POST, count($params));
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    parse_str(curl_exec($ch), $result);
    curl_close($ch);
    return $result;
}

您有解决此问题的想法吗?
我正在考虑在脚本结束时结算交易,因为 PayPal 会在三天内兑现 100% 的授权资金,而我只需要将它们保留 1 天,但无论如何我都不确定......

Do you have any idea to resolve this issue ?
I was thinking about settling transaction at the end of the script because PayPal honors 100% of authorized funds for three days, and I only need them to be hold for 1 day but I'm not sure of this anyway ...

当这个问题发生时,我的 apache2 error.log 报告了这个:

My apache2 error.log reported this when this problem happens :

[Mon Aug 08 20:42:55.959330 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:42:56.960453 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:42:57.961188 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:42:58.962230 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:42:59.963297 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:00.964384 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:01.965476 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:02.966478 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:03.967595 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:04.968713 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:05.969783 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:06.970877 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:07.972002 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:08.972749 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:09.973847 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:10.974926 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:11.976080 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:12.977168 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:13.978244 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:14.979320 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:15.980414 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:16.981493 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:17.982578 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:18.983673 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:19.984762 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:20.985841 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:21.986650 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:22.987725 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:23.988826 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:24.989939 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:25.991061 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:26.992181 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:27.993305 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:28.994422 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:29.995556 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:30.996661 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:31.997774 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:32.998905 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:34.000089 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:35.001202 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:36.002326 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:37.003424 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:38.004551 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:39.005677 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:40.006799 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:41.007902 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:42.009021 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:43.010132 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:44.011245 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:45.012361 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:46.013479 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:47.014577 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:48.015685 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:49.016801 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:50.017906 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:51.018980 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:52.020049 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:53.021158 2016] [mpm_event:error] [pid 141:tid 3779940374400] AH00485: scoreboard is full, not at MaxRequestWorkers
[Mon Aug 08 20:43:53.391316 2016] [:error] [pid 980:tid 3779386513152] (104)Connection reset by peer: [client MY-IP:55236] FastCGI: failed to read from backend server, referer: http://####
[Mon Aug 08 21:18:04.748237 2016] [:error] [pid 1287:tid 3779782977280] (104)Connection reset by peer: [client MY-IP:37196] FastCGI: failed to read from backend server

编辑 2:

我发现这个话题似乎有类似问题:

特别奇怪的是,付款已被正确处理.

What is particularly strange is that the payment has been processed correctly.

现在我似乎无法重现这个错误.
您认为这可能是 PayPal 问题或类似问题吗?
即使是这样,我也不想确保这个问题不会再次发生,但是如果我无法重现这个问题,我该如何测试?

And right now I can't seem to be able to reproduce this bug.
Do you think it could have been a PayPal issue or something like this ?
Even if it was, I wan't to make sure that this problem won't happen again, but how can I test if I can't reproduce this ?

推荐答案

注意:并非所有付款都是即时付款.如果买家只有银行与他们的 PayPal 账户关联的账户,转账不会立即的.因此,如果想要自动通知所有付款和相关活动,最佳做法是使用 IPN.

根据 PayPal 官方文档:

According to PayPal Official Docs:

即时付款通知 (IPN) 是一条消息通知的服务与 PayPal 交易相关的事件.您可以使用 IPN 消息自动化后台和管理功能,例如履行订单、跟踪客户或提供状态和其他交易相关信息.

Instant Payment Notification (IPN) is a message service that notifies you of events related to PayPal transactions. You can use IPN messages to automate back-office and administrative functions, such as fulfilling orders, tracking customers, or providing status and other transaction-related information.

作为最佳实践,在您的 IPN 侦听器 中设置事务脚本.有关集成指南,您可以参考此处:https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNImplementation/

As best practice set transactional script in your IPN Listener. For the integration guide you can refer here : https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNImplementation/

几个月前,我为 PayPal IPN Listener 扩展了一个 PHP 类.希望它可以作为起点有所帮助.随意分叉:https://github.com/datumradix/PayPal-IPN-PHP-类-

I have extended a PHP class for PayPal IPN Listener few months backs. Hope it may help as starting point. Feel free to fork :https://github.com/datumradix/PayPal-IPN-PHP-Class-

(PayPal 文档在很多地方都不清楚,对许多第一次阅读的读者来说似乎很困惑)

IPN 作为辅助机制可以派上用场,以确认 DoCapture 是否成功.txn_typetxn_idauth_idauth_amountpayer_id 等 IPN 变量都会被通知通过 IPN.请在此处参考完整列表:https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNandPDTVariables/

IPN can come handy as secondary mechanism to confirm if the DoCapture was successful. IPN variables like txn_type, txn_id,auth_id, auth_amount and payer_id are all notified thro IPN. Please ref here for full list: https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNandPDTVariables/

注意:我们可以在每次调用中指定 NOTIFYURL 或者我们可以从贝宝后端设置相同.有关设置相同的步骤PayPal 个人资料设置,参考:https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNSetup/

Note: We can either specify the NOTIFYURL in each call or we can setup the same from paypal back-end. For steps to setup the same from PayPal profile settings, ref :https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNSetup/

相关文章