如何使用 SDK 对 PayPal 定期付款配置文件进行计费

Posted

技术标签:

【中文标题】如何使用 SDK 对 PayPal 定期付款配置文件进行计费【英文标题】:How to Bill a PayPal Recurring Payment Profile with the SDK 【发布时间】:2021-07-19 02:09:07 【问题描述】:

我在 .net 中使用 PayPal SDK(我认为它是“旧”版本,classic?)我的商家资料下有一堆定期付款协议(可以从 https://www.paypal.com/ca/cgi-bin/webscr?cmd=_merchant-hub 手动开具发票的协议) ,并在 PayPal 网站上的活动 -> 所有报告 -> 客户协议 -> 定期付款下列出)。手动为它们开具发票可以正常工作,但我想自动化。我能够获得要开具发票的定期付款资料列表,因此我只是错过了最后一步 - 实际为定期付款资料开具发票。

我试过了

PayPalAPIInterfaceServiceService.BillUser() 

PayPalAPIInterfaceServiceService.BillOutstandingAmount()

它们都不起作用。 PayPalAPIInterfaceServiceService.BillUser() 返回一个

协议 ID 无效

错误(我猜他们正在寻找另一种计费协议)。 PayPalAPIInterfaceServiceService.BillOutstandingAmount() 返回一个

未结余额必须> 0

错误。我想我或许可以使用

设置定期付款的未结余额
PayPalAPIInterfaceServiceService.UpdateRecurringPaymentsProfile()

但是,当在构造函数中将定期付款配置文件 ID 传递给 UpdateRecurringPaymentsProfileRequestDetailsType 或通过 UpdateRecurringPaymentsProfileRequestDetailsType.ProfileID 设置时,会导致

个人资料 ID 对此帐户无效。请重新提交请求 使用正确的个人资料 ID。

将 UpdateRecurringPaymentsProfileRequestDetailsType.ProfileReference 设置为定期付款配置文件 ID 时,错误消息是

个人资料 ID 无效

最后,我也尝试过使用参考交易计费:

PaymentDetailsType payment = new PaymentDetailsType()  OrderTotal = new BasicAmountType(CurrencyCodeType.USD, amount.ToString()) ;
DoReferenceTransactionRequestDetailsType request = new DoReferenceTransactionRequestDetailsType(recurringPaymentId, PaymentActionCodeType.SALE, payment);
var response = service.DoReferenceTransaction(new DoReferenceTransactionReq()  DoReferenceTransactionRequest = new DoReferenceTransactionRequestType(request) );

这会导致

结算协议 ID 或交易 ID 无效

我的想法不多了!

在给定其配置文件 ID 的情况下,对定期付款配置文件开票/计费的正确 PayPal SDK 调用是什么?

【问题讨论】:

也许您正在使用计费协议创建参考交易 @PrestonPHX 谢谢;我尝试了 DoReferenceTransaction(),但没有运气 - 除非你能发现我在上面做错了什么。 您引用的 ID 或 BA 的示例是什么? 定期付款资料 ID:I-FA4XHGTEA1A1 【参考方案1】:

根据 PayPal 商家技术支持,使用类似链接创建的定期 PayPal 支付协议

https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=###

并且 ID 以“I-”开头不能使用 PayPal API 开具发票。

相反,必须使用只能使用 PayPal API 创建的参考交易(而不是如上所示的“托管按钮”链接)。

现在,PayPal 商家账户默认不启用参考交易;必须致电或发送电子邮件至 PayPal 才能启用该功能 (some guidance available)。

一旦启用参考交易(我的 PayPal 前后来回花了大约两周时间才启用,但也许我不如大多数人幸运 :^),可以使用Express Checkout API(以及其他)来创建参考交易。

参考交易没有列在可以注册的自动 SFTP RPP 报告中。他们确实出现在商户中心(并且他们的 ID 以 B- 而不是 I- 开头),并且可以从悬停时显示的工具提示中从商户中心获取与参考交易相关联的电子邮件地址将鼠标悬停在列表上(查看参考交易详细信息时不显示付款人电子邮件地址)。还可以在 returnUrl 调用的代码中使用 GetExpressCheckoutDetails 获取付款人信息,将作为参数传递给 returnUrl 的令牌传递给它(与调用 CreateBillingAgreement 相同的令牌)。

这是处理创建 PayPal 参考交易的 php 代码。一个 php 文件来处理这一切。我在其中留下了一些(已注释掉的)调试代码,以帮助您在需要时完成此操作。

<?php
    require_once( dirname(__FILE__) . '/ppconfig.php' ); 

    /* ppconfig.php should contain something like:
    <?php
    global $ppApiUser, $ppApiPwd, $ppApiSig;
    $ppApiUser = '...';
    $ppApiPwd = '...';
    $ppApiSig = '...';
    ?>    
    */

    //print_r($_GET);
   
    if (!array_key_exists("a", $_GET))
        Intro();
    else switch ($_GET["a"])
     
        case "go":
            Setup();   
            break;

        case "cf":
            Confirmed();   
            break;

        case "cx":
            Cancelled();   
            break;

        default:
            Intro();
            break;
    

    function Intro()
    
        echo("<p>Please <a href='" . BaseUrl() . "?a=go'>click here</a> to create a PayPal billing agreement.</p>");
        

    function Setup()
    
        $post = [
            'PAYMENTREQUEST_0_PAYMENTACTION' => 'AUTHORIZATION',
            'PAYMENTREQUEST_0_AMT' => '0',
            'PAYMENTREQUEST_0_CURRENCYCODE' => 'USD',
            'L_BILLINGTYPE0' => 'MerchantInitiatedBilling',
            'L_BILLINGAGREEMENTDESCRIPTION0' => 'Monthly Fee',
            'cancelUrl' => BaseUrl() . '?a=cx',
            'returnUrl' => BaseUrl() . '?a=cf'
        ];        

        $post = SetupPostArray($post, 'SetExpressCheckout');
        //echo "<p>query: " . http_build_query($post) . "</p>";

        $parsedResponse = DoCurl($post);
        // example response: Array ( [TOKEN] => EC-9WG24287H6582094R [TIMESTAMP] => 2021-05-17T18:14:09Z [CORRELATIONID] => 37010d4454fac [ACK] => Success [VERSION] => 86 [BUILD] => 55627781 )
           
        if ($parsedResponse['ACK'] === "Success")        
            Redirect("https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=" . $parsedResponse['TOKEN']);
        else
            echo("<p>There was an issue creating your PayPal billing agreement; please forward this to us: SetExpressCheckout response = " . print_r($parsedResponse, true) . "</p>");             
        

    function Confirmed()
    
        //$callerIp = $_SERVER['REMOTE_ADDR'];
        //$callerName = gethostbyaddr ( $callerIp );
        //echo("<p>Caller ip: $callerIp  Caller name: $callerName</p>"); returns my own address

        // sample request url: https://xxx.php?a=cf&token=EC-6F958498XP432134J
        // (paypal parameter same as cancel)

        $details = GetExpressCheckoutDetails($_GET["token"]);
        //echo("<p>Payer email: " . $details['EMAIL'] . "  Payer ID: " . $details['PAYERID'] . "</p>");
        
        $post = SetupPostArray([ 'TOKEN' => $_GET["token"] ], 'CreateBillingAgreement');  
        $parsedResponse = DoCurl($post);
        // sample response to create billing agreement: Array ( [BILLINGAGREEMENTID] => B-4EM76674LS64xxxxx [TIMESTAMP] => 2021-05-17T17:51:59Z [CORRELATIONID] => 3be427e93xxxx [ACK] => Success [VERSION] => 86 [BUILD] => 55627781 )

        if ($parsedResponse['ACK'] === "Success")
        
            echo("<p>You successfully setup a pre-authorized payment (" . $parsedResponse['BILLINGAGREEMENTID'] . "). Thank you.</p>");                               
        
        else
            echo("<p>There was an issue creating your PayPal billing agreement; please forward this to us: CreateBillingAgreement = " . print_r($parsedResponse, true) . "</p>");                
        

    function Cancelled()
    
        // sample request url: https://xxx.php?a=cx&token=EC-6F958498XP432134J
        // (paypal parameter same as success)

        echo("<p>It looks like you cancelled the creation of your PayPal pre-authorized payment (how could you!) Please <a href='" . BaseUrl() . "?a=go'>click here</a> to try again.</p>");
        

    function GetExpressCheckoutDetails($token)
    
        $post = SetupPostArray([ 'TOKEN' => $token ], 'GetExpressCheckoutDetails');  
        //echo "<p>GetExpressCheckoutDetails query: " . http_build_query($post) . "</p>";
        $parsedResponse = DoCurl($post);
        // sample response to GetExpressCheckoutDetails:  Array ( [TOKEN] => EC-87S04858V0280572C [BILLINGAGREEMENTACCEPTEDSTATUS] => 1 [CHECKOUTSTATUS] => PaymentActionNotInitiated [TIMESTAMP] => 2021-05-19T15:16:06Z [CORRELATIONID] => f5d17498exxxx [ACK] => Success [VERSION] => 86 [BUILD] => 55627781 [EMAIL] => xxxx@gmail.com [PAYERID] => xxxx [PAYERSTATUS] => verified [FIRSTNAME] => xxx [LASTNAME] => xxx [COUNTRYCODE] => xx [SHIPTONAME] => xxx xxx [SHIPTOSTREET] => xxx Dr [SHIPTOCITY] => xxx [SHIPTOSTATE] => xx [SHIPTOZIP] => xxxx [SHIPTOCOUNTRYCODE] => xx [SHIPTOCOUNTRYNAME] => xxx [ADDRESSSTATUS] => Confirmed [CURRENCYCODE] => USD [AMT] => 0.00 [ITEMAMT] => 0.00 [SHIPPINGAMT] => 0.00 [HANDLINGAMT] => 0.00 [TAXAMT] => 0.00 [INSURANCEAMT] => 0.00 [SHIPDISCAMT] => 0.00 [INSURANCEOPTIONOFFERED] => false [PAYMENTREQUEST_0_CURRENCYCODE] => USD [PAYMENTREQUEST_0_AMT] => 0.00 [PAYMENTREQUEST_0_ITEMAMT] => 0.00 [PAYMENTREQUEST_0_SHIPPINGAMT] => 0.00 [PAYMENTREQUEST_0_HANDLINGAMT] => 0.00 [PAYMENTREQUEST_0_TAXAMT] => 0.00 [PAYMENTREQUEST_0_INSURANCEAMT] => 0.00 [PAYMENTREQUEST_0_SHIPDISCAMT] => 0.00 [PAYMENTREQUEST_0_SELLERPAYPALACCOUNTID] => xxx@xxx.com [PAYMENTREQUEST_0_INSURANCEOPTIONOFFERED] => false [PAYMENTREQUEST_0_SHIPTONAME] => xxx xxx [PAYMENTREQUEST_0_SHIPTOSTREET] => xxx Dr [PAYMENTREQUEST_0_SHIPTOCITY] => xxx [PAYMENTREQUEST_0_SHIPTOSTATE] => xx [PAYMENTREQUEST_0_SHIPTOZIP] => xxxxx [PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE] => xx [PAYMENTREQUEST_0_SHIPTOCOUNTRYNAME] => xxx [PAYMENTREQUEST_0_ADDRESSSTATUS] => Confirmed [PAYMENTREQUESTINFO_0_ERRORCODE] => 0 )

        //echo("<p>GetExpressCheckoutDetails response: " . print_r($parsedResponse, true) . "</p>");                

        return $parsedResponse;        
    
    
    function SetupPostArray($post, $method)
    
        global $ppApiUser, $ppApiPwd, $ppApiSig; 
        $post['USER'] = $ppApiUser;
        $post['PWD'] = $ppApiPwd;
        $post['SIGNATURE'] = $ppApiSig;
        $post['METHOD'] = $method; 
        $post['VERSION'] = '86';
        return $post;
    

    function DoCurl($post)
    
        $ch = curl_init("https://api-3t.paypal.com/nvp");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));    
        $response = curl_exec($ch);
        curl_close($ch);    

        //echo("<p>Raw response: '$response'</p>");
        // example response: Array ( [TOKEN] => EC-9WG24287H6582094R [TIMESTAMP] => 2021-05-17T18:14:09Z [CORRELATIONID] => 37010d445xxxx [ACK] => Success [VERSION] => 86 [BUILD] => 55627781 )

        parse_str($response, $parsedResponse);

        return $parsedResponse;
    

    function Redirect($url)
    
        echo "<script type='text/javascript'> 
        window.location = '$url'; 
        </script>";        
    

    function BaseUrl()
    
        //return strtok($_SERVER["REQUEST_URI"], '?'); // see https://***.com/a/6975045/68936
        return strtok("https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]", '?'); // see ***.com/a/6975045, ***.com/a/6768831
    
?>

【讨论】:

以上是关于如何使用 SDK 对 PayPal 定期付款配置文件进行计费的主要内容,如果未能解决你的问题,请参考以下文章

如何在 node.js 中的 paypal 中创建可变的定期付款

如何在 PHP 中使用 IPN(即时付款通知)在 Paypal 中配置定期付款

什么是 Paypal 的定期付款配置文件 RPSTATE 参数?

带有快速结账功能的 PayPal 定期付款 - 10 个付款配置文件限制

如何在 PayPal 高级中创建定期配置文件

Paypal 定期付款配置文件 API