即使安装费失败,PayPal PHP SDK 也会执行计费协议

Posted

技术标签:

【中文标题】即使安装费失败,PayPal PHP SDK 也会执行计费协议【英文标题】:PayPal PHP SDK executes billing agreement even if setup fee fails 【发布时间】:2020-10-06 02:08:39 【问题描述】:

我的问题是即使没有支付安装费,也可以成功执行计费协议。查看日志,通知设置费失败和协议被取消的 IPN 事件通常需要 5-10 分钟才能到达,这是一个疯狂的延迟。

我正在使用https://github.com/paypal/PayPal-php-SDK 的官方 PayPal PHP SDK。它在一个月前被弃用,但它的替代品被标记为“未准备好投入生产”。

计费方案详细信息,旨在收取 29.99 美元/年的订阅费用。安装费用于保证初始付款。

按照https://paypal.github.io/PayPal-PHP-SDK/sample/ 中记录的两步流程,为了便于阅读,移除了包装的 try/catch 块:

// Step 1: https://paypal.github.io/PayPal-PHP-SDK/sample/doc/billing/CreateBillingAgreementWithPayPal.html

use PayPal\Api\Agreement;
use PayPal\Api\MerchantPreferences;
use PayPal\Api\Payer;
use PayPal\Api\Plan;

/**
 * @var \PayPal\Rest\ApiContext $apiContext
 */
$plan = Plan::get('EXAMPLE-PLAN-ID', $apiContext);

$agreement = new Agreement();

date_default_timezone_set('America/Los_Angeles');
$agreement->setName($plan->getName())
    ->setDescription($plan->getDescription())
    // I'm not sure why +1 hour is used here, but that's how it is in the codebase.
    ->setStartDate(date('c', strtotime("+1 hour", time())));

$agreement->setPlan($plan);

/**
 * ------------------------------------------------------------------------------------------
 * I think overriding should be optional since they currently precisely match the given 
 * plan's data.  So for this particular plan, if I deleted everything between these comment
 * blocks, nothing bad should happen.
 * ------------------------------------------------------------------------------------------
 */
$preferences = new MerchantPreferences();
$preferences->setReturnUrl("https://www.example.com/actually-a-valid-site")
    ->setCancelUrl("https://www.example.com/actually-a-valid-site")
    ->setAutoBillAmount('no')
    ->setInitialFailAmountAction('CANCEL');

$agreement->setOverrideMerchantPreferences($preferences);
/**
 * ------------------------------------------------------------------------------------------
 * ------------------------------------------------------------------------------------------
 */

$payer = new Payer();
$payer->setPaymentMethod('paypal');
$agreement->setPayer($payer);

$agreement = $agreement->create($apiContext);
$approvalUrl = $agreement->getApprovalLink();

// This takes us to PayPal to login and confirm payment.
header("Location: ".$approvalUrl);
// Step 2: https://paypal.github.io/PayPal-PHP-SDK/sample/doc/billing/ExecuteAgreement.html

use PayPal\Api\Agreement;

/**
 * @var \PayPal\Rest\ApiContext $apiContext
 */
try 
    $agreement = new Agreement();
    $agreement->execute($_GET['token'], $apiContext);

    $agreement = Agreement::get($agreement->getId(), $apiContext);

    /**
     * I assume at this point the agreement is executed successfully.  Yet, the setup fee does not
     * have to be paid for us to get here.  This behavior is verified on live.
     */
 catch (\Exception $e) 
    // Do something.

我不知道我做错了什么,即使没有支付安装费也会导致结算协议执行。帮助将不胜感激!

以下是创建使用的计划的方法:

use PayPal\Api\Currency;
use PayPal\Api\MerchantPreferences;
use PayPal\Api\Patch;
use PayPal\Api\PatchRequest;
use PayPal\Api\PaymentDefinition;
use PayPal\Api\Plan;
use PayPal\Common\PayPalModel;

$plan = new Plan();

$plan->setName('Test Name')
    ->setDescription('Test Description')
    ->setType('INFINITE');

$payment_definition = new PaymentDefinition();

$payment_definition->setName('Regular Payments')
    ->setType('REGULAR')
    ->setFrequency('YEAR')
    ->setFrequencyInterval(1)
    ->setCycles('0')
    ->setAmount(new Currency(['value' => '29.99', 'currency' => 'USD']));

$merchant_preferences = new MerchantPreferences();
$merchant_preferences->setReturnUrl'https://insert.actual.url.here')
    ->setCancelUrl('https://insert.actual.url.here')
    ->setAutoBillAmount('NO')
    ->setInitialFailAmountAction('CANCEL')
    ->setMaxFailAttempts('1')
    ->setSetupFee(new Currency(['value' => '29.99', 'currency' => 'USD']));

$plan->setPaymentDefinitions([$payment_definition]);
$plan->setMerchantPreferences($merchant_preferences);

$request = clone $plan;

try 
    /**
     * @var \Paypal\Rest\ApiContext $apiContext
     */
    $plan->create($apiContext);

    $patch = new Patch();
    $value = new PayPalModel(['state' => 'ACTIVE']);

    $patch->setOp('replace')
        ->setPath('/')
        ->setValue($value);

    $patchRequest = new PatchRequest();
    $patchRequest->addPatch($patch);

    if (!$plan->update($patchRequest, $apiContext)) 
        throw new \Exception("Failed to apply patch to plan.");
    
    // Done.
 catch (\Exception $e) 
    // Some error handling.
    exit;

【问题讨论】:

【参考方案1】:

替换 SDK 为 https://github.com/paypal/Checkout-PHP-SDK ,不包含任何计费协议或订阅用例。对于该 SDK 未涵盖的用例,您应该使用直接 HTTPS 集成。这记录在这里:https://developer.paypal.com/docs/api/rest-sdks/

您尝试使用的代码是针对过时 API 的过时 SDK(旧版本的计费协议,与新订阅不兼容)。

这是您应该集成的 API,没有 SDK:https://developer.paypal.com/docs/subscriptions/

【讨论】:

什么鬼,官方的SDK基本都是垃圾?好吧,我想如果有必要我会在没有它们的情况下实现。 这是必要的。当前的官方 SDK 仅涵盖 Checkout 和 Payouts 用例。其他一切都应该直接集成。 没有办法使用已弃用的SDK强制立即支付安装费?我可以自己编写一个集成,但如果可能的话,能够在花费时间之前修复网站上当前存在的内容会很好。【参考方案2】:

事实证明,通常的协议执行不应该引发异常。要查看设置是否已支付,请在执行协议后查看$agreement->getState()的值。

【讨论】:

以上是关于即使安装费失败,PayPal PHP SDK 也会执行计费协议的主要内容,如果未能解决你的问题,请参考以下文章

即使可以从 Internet 访问 URL,Paypal IPN 触发也会失败?

PayPal PHP SDK:由于身份验证凭据无效或缺少授权标头,身份验证失败

从数据库动态创建的 PayPal SDK (PHP) 项目

即使存在标头,CORS 也会失败

即使存在标头,CORS 也会失败

为啥即使安装成功,导入 mysql-connector 也会失败?