如何使用 SDK 对 PayPal 定期付款配置文件进行计费
How to Bill a PayPal Recurring Payment Profile with the SDK
我在 .net 中使用 PayPal SDK(我认为它是 'old' 版本,classic?) I have a bunch of recurring payment agreements under my merchant profile (the ones that can be invoiced manually from https://www.paypal.com/ca/cgi-bin/webscr?cmd=_merchant-hub,并列在 Activity -> 所有报告 -> 客户协议 ->在 PayPal 网站上定期付款)。手动为它们开具发票工作正常,但我想自动化。我能够获得要开具发票的定期付款配置文件列表,所以我只是错过了最后一步 - 实际为经常性付款配置文件开具发票。
我试过了
PayPalAPIInterfaceServiceService.BillUser()
和
PayPalAPIInterfaceServiceService.BillOutstandingAmount()
而且它们都不起作用。 PayPalAPIInterfaceServiceService.BillUser() returns一个
Agreement Id is not valid
错误(我猜他们正在寻找一种不同类型的计费协议)。 PayPalAPIInterfaceServiceService.BillOutstandingAmount() returns一个
Outstanding balance must be > 0
错误。我想我也许可以使用
设置定期付款的未结余额
PayPalAPIInterfaceServiceService.UpdateRecurringPaymentsProfile()
但是,当在构造函数中将经常性付款配置文件 ID 传递给 UpdateRecurringPaymentsProfileRequestDetailsType 或通过 UpdateRecurringPaymentsProfileRequestDetailsType.ProfileID 设置时,会导致
Profile ID is not valid for this account. Please resubmit request
with the correct profile ID.
将UpdateRecurringPaymentsProfileRequestDetailsType.ProfileReference设置为定期付款配置文件 ID 时,错误消息为
The profile ID is invalid
最后,我也试过使用参考交易来计费:
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) });
这导致
Billing Agreement Id or transaction Id is not valid
我运行没主意了!
根据配置文件 ID,invoicing/billing 定期付款配置文件的正确 PayPal SDK 调用是什么?
根据 PayPal 商家技术支持,使用 link 类似
创建的重复性 PayPal 支付协议
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=###
ID 以“I-”开头不能使用 PayPal API。
相反,必须使用参考交易,它只能使用 PayPal API 创建(而不是如上所示 link 到 'hosted button')。
现在,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
return strtok("https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]", '?'); // see whosebug.com/a/6975045, whosebug.com/a/6768831
}
?>
我在 .net 中使用 PayPal SDK(我认为它是 'old' 版本,classic?) I have a bunch of recurring payment agreements under my merchant profile (the ones that can be invoiced manually from https://www.paypal.com/ca/cgi-bin/webscr?cmd=_merchant-hub,并列在 Activity -> 所有报告 -> 客户协议 ->在 PayPal 网站上定期付款)。手动为它们开具发票工作正常,但我想自动化。我能够获得要开具发票的定期付款配置文件列表,所以我只是错过了最后一步 - 实际为经常性付款配置文件开具发票。
我试过了
PayPalAPIInterfaceServiceService.BillUser()
和
PayPalAPIInterfaceServiceService.BillOutstandingAmount()
而且它们都不起作用。 PayPalAPIInterfaceServiceService.BillUser() returns一个
Agreement Id is not valid
错误(我猜他们正在寻找一种不同类型的计费协议)。 PayPalAPIInterfaceServiceService.BillOutstandingAmount() returns一个
Outstanding balance must be > 0
错误。我想我也许可以使用
设置定期付款的未结余额PayPalAPIInterfaceServiceService.UpdateRecurringPaymentsProfile()
但是,当在构造函数中将经常性付款配置文件 ID 传递给 UpdateRecurringPaymentsProfileRequestDetailsType 或通过 UpdateRecurringPaymentsProfileRequestDetailsType.ProfileID 设置时,会导致
Profile ID is not valid for this account. Please resubmit request with the correct profile ID.
将UpdateRecurringPaymentsProfileRequestDetailsType.ProfileReference设置为定期付款配置文件 ID 时,错误消息为
The profile ID is invalid
最后,我也试过使用参考交易来计费:
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) });
这导致
Billing Agreement Id or transaction Id is not valid
我运行没主意了!
根据配置文件 ID,invoicing/billing 定期付款配置文件的正确 PayPal SDK 调用是什么?
根据 PayPal 商家技术支持,使用 link 类似
创建的重复性 PayPal 支付协议https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=###
ID 以“I-”开头不能使用 PayPal API。
相反,必须使用参考交易,它只能使用 PayPal API 创建(而不是如上所示 link 到 'hosted button')。
现在,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
return strtok("https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]", '?'); // see whosebug.com/a/6975045, whosebug.com/a/6768831
}
?>