My problem is that billing agreements are successfully executed even if the setup fee is not paid. Looking at the logs, the IPN event notifiying that the setup fee failed and the agreement is cancelled typically takes 5-10 minutes to arrive, which is an insane amount of delay.
I am using the official PayPal PHP SDK at https://github.com/paypal/PayPal-PHP-SDK. It was deprecated a month ago, but its replacement is marked "not ready for production".
Billing plan details, with the intent to charge a $29.99/yr subscription. Setup fee is used to guarantee initial payment.
Per the 2 step process documented in https://paypal.github.io/PayPal-PHP-SDK/sample/, with the wrapping try/catch blocks removed for legibility:
// 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.
}
I'm at a loss for what I'm doing wrong that would cause the billing agreement to execute even without the setup fee being paid. Help would be appreciated!
Here's how to create the Plan that was used:
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;
}
The replacement SDK is https://github.com/paypal/Checkout-PHP-SDK , which does not include any billing agreement or subscription use cases. For use cases not covered by that SDK , you should use a direct HTTPS integration. This is documented here: https://developer.paypal.com/docs/api/rest-sdks/
The code you are trying to use is for an obsolete SDK for an obsolete API (old version of billing agreements, not compatible with new subscriptions).
Here is the API you should integrate, with no SDK: https://developer.paypal.com/docs/subscriptions/
Turns out an exception is not supposed to be thrown for usual agreement execution. To check whether the setup was paid, check the value of $agreement->getState() after executing the agreement.
Related
I am using PayPal REST API in sandbox mode to test payments.
I create the approval link, then redirect user to said link.
PayPal then asks for credentials which I insert as a sandbox test buyer. But after logging in, PayPal does not redirect me to complete the approval of payment, and instead straight to My Account summary from where I cannot approve the payment. However if the user was already logged in, the payment approval proceeds as expected.
Is it the problem with how was the link created or is it a bug?
This is the code used to create the link
<?php
use PayPal\Api\Amount;
use PayPal\Api\Details;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Rest\ApiContext;
class PaypalFactory
{
private const CLIENT_ID = 'yyy';
private const CLIENT_SECRET = 'xxx';
private function __construct()
{
}
public static final function getContext()
{
return new ApiContext(
new OAuthTokenCredential(
self::CLIENT_ID,
self::CLIENT_SECRET
)
);
}
public static final function createPaymentLink(
int $orderId,
string $currency,
float $totalPrice,
float $deliveryPrice,
string $returnUrl,
string $cancelUrl): string
{
$payer = new Payer();
$payer
->setPaymentMethod("paypal");
$details = new Details();
$details
->setShipping($deliveryPrice)
->setSubtotal($totalPrice);
$item = new Item();
$item
->setName('products and shipping')
->setCurrency($currency)
->setPrice($totalPrice)
->setQuantity(1);
$itemList = new ItemList();
$itemList->setItems(array($item));
$amount = new Amount();
$amount
->setCurrency($currency)
->setTotal($totalPrice + $deliveryPrice)
->setDetails($details);
$transaction = new Transaction();
$transaction
->setItemList($itemList)
->setAmount($amount)
->setDescription("Products from ")
->setInvoiceNumber($orderId);
$redirectUrls = new RedirectUrls();
$redirectUrls
->setReturnUrl($returnUrl)
->setCancelUrl($cancelUrl);
$payment = new Payment();
$payment
->setIntent("sale")
->setPayer($payer)
->setRedirectUrls($redirectUrls)
->setTransactions(array($transaction));
try {
$payment->create(PaypalFactory::getContext());
} catch (Exception $ex) {
print $ex;
}
return $payment->getApprovalLink();
}
}
Purging all browser cookies for paypal domains is good advice, and may help. The problem you're experiencing is probably a general sandbox issue (that won't happen in live mode), and not a problem with your code.
However, seeing your code/solution, I do have suggestions for trying some newer/better things:
Instead of the old PayPal-PHP-SDK for v1/payments, use the new Checkout-PHP-SDK for v2/orders
Instead of redirecting the buyer to the approval URL, give the approval token to the Javascript code of Smart Payment Buttons, which will display an in-context window that keeps your site loaded in the background. Here's a demo pattern
I am using PHP REST APIv1 (I believe) to integrate with paypal.
My request code looks as follows:
include '../../classes/db.connect.php';
use PayPal\Api\Amount;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;
require 'bootstrap.php';
if (empty($_POST['buy_now'])) {
throw new Exception('This script should not be called directly, expected post data');
}
$setitem = strip_tags($_POST['item']);
$stmt=$db->prepare("SELECT * FROM shopping WHERE id = :id");
$stmt->bindParam(':id', $setitem);
$stmt->execute();
$row = $stmt->fetch();
$payer = new Payer();
$payer->setPaymentMethod('paypal');
$currency = 'USD';
$amountPayable = $row['price'];
$item1 = new Item();
$item1->setName($row['name'])
->setCurrency('USD')
->setQuantity('1')
->setPrice($amountPayable);
$invoiceNumber = uniqid();
$amount = new Amount();
$amount->setCurrency($currency)
->setTotal($amountPayable);
$itemList = new ItemList();
if (isset($item1) && isset($item2)) {
$itemList->setItems(array($item1, $item2));
} elseif (isset($item1)) {
$itemList->setItems(array($item1));
} elseif (isset($item2)) {
$itemList->setItems(array($item2));
}
$transaction = new Transaction();
$transaction->setAmount($amount)
->setItemList($itemList)
->setDescription('Website '.$row['name'].' Purchase')
->setInvoiceNumber($invoiceNumber)
->setNotifyUrl("https://REDACTED/notify.php")
->setCustom($username);
$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl($paypalConfig['return_url'])
->setCancelUrl($paypalConfig['cancel_url']);
$payment = new Payment();
$payment->setIntent('sale')
->setPayer($payer)
->setTransactions([$transaction])
->setRedirectUrls($redirectUrls);
try {
$payment->create($apiContext);
} catch (Exception $e) {
throw new Exception('Unable to create link for payment');
}
header('location:' . $payment->getApprovalLink());
A payment goes through just fine, but notify_url is not called by paypal. There are no errors at all. The user can make a payment, the payment goes through, the user is returned to setReturnUrl but setNotifyUrl seems to get ignored.
I have done a var_dump on $transaction and the notify_url is sent to paypal, but it is not called by paypal - thus I get no notifications of transactions/nothing updated in the database.
I have done the most basic test I can think to ensure paypal is not calling notify_url. The php in the file is simply set to:
include '../../classes/db.connect.php';
$stmt = $db->prepare("INSERT INTO test (info) VALUES ('1122')");
$stmt->execute();
I realize REST is intended to work with webhooks - but I am not wanting to use them for several various reasons. My understanding is that setting a notify_url will allow paypal to use that url as an IPN. However I am finding paypal is just ignoring this variable.
Also I should mention I have tried this with IPN enabled and IPN status updates disabled, I have tried with IPN enabled and status updates enabled, and I have tried it with IPN disabled. All three ways has the same result - no errors, everything work, but no notifications sent nor received by paypal.
My IPN istory page on paypal is completely empty.
Am I doing something wrong? Misunderstanding something?
I cannot find any solid information about this.
Paypal IPNs setup through your Paypal account are sent irrespective of the method used to create the transactions.
https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNSetup/
You can use the IPN simulator to test your receiving code, or by signing up for a sandbox account as configuring the IPN setting there.
I have an issue with Paypal REST Api billing (recurring payments).
I did exactly as in paypal documentation but when I click payment button it opens blank page (no errors in the error log).
Here is what I do:
Front End Button
<form action="WEBSITE/subscribe/paypal/paypal_agreement.php" method="POST">
<button type="submit" style="margin-top:10px;border: 0; background: transparent">
<img src="WEBSITE/wp-content/uploads/2017/05/paypal.png" style = "width:170px;" alt="submit" />
</button>
</form>
paypal_agreement.php (copy/paste from paypal docs)
<?php
require_once("../../paypal/vendor/autoload.php");
$createdPlan = require 'paypal_createplan.php';
use PayPal\Api\Agreement;
use PayPal\Api\Payer;
use PayPal\Api\Plan;
use PayPal\Api\ShippingAddress;
$ppstartdate = date('c', time()+210);
$agreement = new Agreement();
$agreement->setName('Title Agreement')
->setDescription('Description Agreement')
->setStartDate($ppstartdate);
// Add Plan ID
// Please note that the plan Id should be only set in this case.
$plan = new Plan();
$plan->setId($createdPlan->getId());
$agreement->setPlan($plan);
// Add Payer
$payer = new Payer();
$payer->setPaymentMethod('paypal');
$agreement->setPayer($payer);
// ### Create Agreement
try {
// Please note that as the agreement has not yet activated, we wont be receiving the ID just yet.
$agreement = $agreement->create($apiContext);
// ### Get redirect url
// The API response provides the url that you must redirect
// the buyer to. Retrieve the url from the $agreement->getApprovalLink()
// method
$approvalUrl = $agreement->getApprovalLink();
header("Location: ".$approvalUrl);
} catch (Exception $ex) {
exit(1);
}
return $agreement;
?>
paypal_createplan.php (copy/paste from paypal docs)
<?php
require_once("../../paypal/vendor/autoload.php");
use PayPal\Api\Currency;
use PayPal\Api\MerchantPreferences;
use PayPal\Api\PaymentDefinition;
use PayPal\Api\Plan;
// Create a new instance of Plan object
$plan = new Plan();
// # Basic Information
// Fill up the basic information that is required for the plan
$plan->setName('Title Plan')
->setDescription('Description Subscription Plan')
->setType('fixed');
// # Payment definitions for this billing plan.
$paymentDefinition = new PaymentDefinition();
// The possible values for such setters are mentioned in the setter method documentation.
// Just open the class file. e.g. lib/PayPal/Api/PaymentDefinition.php and look for setFrequency method.
// You should be able to see the acceptable values in the comments.
$paymentDefinition->setName('Regular Payments')
->setType('REGULAR')
->setFrequency('Month')
->setFrequencyInterval("1")
->setCycles("6")
->setAmount(new Currency(array('value' => 94.99, 'currency' => 'USD')));
$merchantPreferences = new MerchantPreferences();
$baseUrl = "https://websitelink.com";
$merchantPreferences->setReturnUrl("$baseUrl/subscribe/paypal/execute.php?success=true")
->setCancelUrl("$baseUrl/subscribe/paypal/execute.php?success=false")
->setAutoBillAmount("yes")
->setInitialFailAmountAction("CONTINUE")
->setMaxFailAttempts("0")
->setSetupFee(new Currency(array('value' => 0, 'currency' => 'USD')));
// ### Create Plan
try {
$output = $plan->create($apiContext);
} catch (Exception $ex) {
exit(1);
}
return $output;
?>
execute.php (copy/paste from paypal docs)
<?php
// #Execute Agreement
// This is the second part of CreateAgreement Sample.
// Use this call to execute an agreement after the buyer approves it
require_once("../../paypal/vendor/autoload.php");
// ## Approval Status
// Determine if the user accepted or denied the request
if (isset($_GET['success']) && $_GET['success'] == 'true') {
$token = $_GET['token'];
$agreement = new \PayPal\Api\Agreement();
try {
// ## Execute Agreement
// Execute the agreement by passing in the token
$agreement->execute($token, $apiContext);
} catch (Exception $ex) {
exit(1);
}
// ## Get Agreement
// Make a get call to retrieve the executed agreement details
try {
$agreement = \PayPal\Api\Agreement::get($agreement->getId(), $apiContext);
//done
header('Location: https://websitelink.com/subscribe/subscribe.php');
} catch (Exception $ex) {
exit(1);
}
} else {
$_SESSION['pmsg'] = $_SESSION['pmsg'].'<h2>Subscription Failed</h2>';
}
But I couldn't find anything about authentication OAuth2 (as for example with express checkout), nothing in documentation. Maybe it doesn't work because of that?
Variable $apiContext should contain paypal app authentication & setConfig
Is this: "Variable $apiContext should contain paypal app authentication & setConfig"
the solution to this issue?: I have an issue with Paypal REST Api billing (recurring payments). I did exactly as in paypal documentation but when I click payment button it opens blank page (no errors in the error log).
I am having a similiar issue. Pls confirm. And how do we get the paypal app authentication & setConfig?
Reference
Code
public function ShowPaymentWithPaypal()
{
$payer = new Payer();
$payer->setPaymentMethod('paypal');
$item_1 = new Item();
$item_1->setName('Item 1') /** item name **/
->setCurrency('USD')
->setQuantity(1)
->setPrice(2); /** unit price **/
$item_list = new ItemList();
$item_list->setItems(array($item_1));
$amount = new Amount();
$amount->setCurrency('USD')
->setTotal(2);
$transaction = new Transaction();
$transaction->setAmount($amount)
->setItemList($item_list)
->setDescription('Your transaction description');
$redirect_urls = new RedirectUrls();
$redirect_urls->setReturnUrl(\URL::route('ReturnedFromPaypal')) /** Specify return URL **/
->setCancelUrl(\URL::route('CancelledPaymentWithPaypal'));
$payment = new Payment();
$payment->setIntent('Sale')
->setPayer($payer)
->setRedirectUrls($redirect_urls)
->setTransactions(array($transaction));
/** dd($payment->create($this->_api_context));exit; **/
try {
$payment->create($this->_api_context);
} catch (\PayPal\Exception\PPConnectionException $ex) {
dd($ex);
if (\Config::get('app.debug')) {
\Session::put('error','Connection timeout');
return "Error occured";
/** echo "Exception: " . $ex->getMessage() . PHP_EOL; **/
/** $err_data = json_decode($ex->getData(), true); **/
/** exit; **/
} else {
\Session::put('error','Some error occur, sorry for inconvenient');
return "Error occured";
/** die('Some error occur, sorry for inconvenient'); **/
}
}
foreach($payment->getLinks() as $link) {
if($link->getRel() == 'approval_url') {
$redirect_url = $link->getHref();
break;
}
}
/** add payment ID to session **/
\Session::put('paypal_payment_id', $payment->getId());
if(isset($redirect_url)) {
/** redirect to paypal **/
return \Redirect::away($redirect_url);
}
\Session::put('error','Unknown error occurred');
return "Last line error";
}
What's the problem ?
When I try to do the login for doing payment using sandbox credentials, I get the below error.
We aren't able to process your payment using your PayPal account at
this time. Please go back to the merchant and try using a different
payment method.
I am following this to configure Paypal in Laravel 5.5
XHR Error Details
Am I missing something ?
It looks like a Paypal limitation.
In this link, for example, the support say that Russia laws limit paypal account to 100000 rub of transaction.
When you reach the limit, you must specify something on your activities to unlock the limit.
Maybe have you reached the limit of transactions' amount for your country?
Try to use low amount, as 0.01 and to delete old test transactions from the sandbox account (seller).
If you haven't a lot of transactions for the seller, maybe the problem is in the customer's account? Try to create a new account and use it.
I suppose in the sandbox will be a place to unlock the
I'm trying to integrate payment gateway (paypal) with laravel 5.1. I found some solutions for laravel 4, but none for laravel 5.0 and above.
I have been following all the steps mentioned in this link. xroot/laravel-paypalpayment
But I am getting FatalErrorException in PaypalPaymentController.php line 10:
I've attached my Paypal ntegration code, and a screen shot of the error.
Router.php
resource('payment','PaymentController');
app.php
Anouar\Paypalpayment\PaypalpaymentServiceProvider::class,
'Paypalpayment' => Anouar\Paypalpayment\Facades\PaypalPayment::class,
PaypalPaymentController.php
<?php
namespace my_app\Http\Controllers;
use Illuminate\Support\Facades\Cache;
use Illuminate\Http\Request;
use my_app\Http\Requests;
use my_app\Http\Controllers\Controller;
use Paypalpayment;
class PaypalPaymentController extends Facades {
private $_apiContext;
private $_ClientId=''/* ... */;
private $_ClientSecret=''/* ... */;
public function __construct(){
// ### Api Context
// Pass in a `ApiContext` object to authenticate
// the call. You can also send a unique request id
// (that ensures idempotency). The SDK generates
// a request id if you do not pass one explicitly.
$this->_apiContext = Paypalpayment:: ApiContext(
Paypalpayment::OAuthTokenCredential(
$this->_ClientId,
$this->_ClientSecret
)
);
// dynamic configuration instead of using sdk_config.ini
$this->_apiContext->setConfig(array(
'mode' => 'sandbox',
'http.ConnectionTimeOut' => 30,
'log.LogEnabled' => true,
'log.FileName' => __DIR__.'/../PayPal.log',
'log.LogLevel' => 'FINE'
));
}
/*
* Create payment using credit card
* url:payment/create
*/
public function create(){
// ### Address
// Base Address object used as shipping or billing
// address in a payment. [Optional]
$addr= Paypalpayment::Address();
$addr->setLine1(/* ... */);
$addr->setLine2(/* ... */);
$addr->setCity(/* ... */);
$addr->setState(/* ... */);
$addr->setPostal_code(/* ... */);
$addr->setCountry_code(/* ... */);
$addr->setPhone(/* ... */);
// ### CreditCard
// A resource representing a credit card that can be
// used to fund a payment.
$card = Paypalpayment::CreditCard();
$card->setType(/* ... */);
$card->setNumber(/* ... */);
$card->setExpire_month(/* ... */);
$card->setExpire_year(/* ... */);
$card->setCvv2(/* ... */);
$card->setFirst_name(/* ... */);
$card->setLast_name(/* ... */);
$card->setBilling_address($addr);
// ### FundingInstrument
// A resource representing a Payer's funding instrument.
// Use a Payer ID (A unique identifier of the payer generated
// and provided by the facilitator. This is required when
// creating or using a tokenized funding instrument)
// and the `CreditCardDetails`
$fi = Paypalpayment::FundingInstrument();
$fi->setCredit_card($card);
// ### Payer
// A resource representing a Payer that funds a payment
// Use the List of `FundingInstrument` and the Payment Method
// as 'credit_card'
$payer = Paypalpayment::Payer();
$payer->setPayment_method("credit_card");
$payer->setFunding_instruments(array($fi));
// ### Amount
// Let's you specify a payment amount.
$amount = Paypalpayment:: Amount();
$amount->setCurrency("USD");
$amount->setTotal("1.00");
// ### Transaction
// A transaction defines the contract of a
// payment - what is the payment for and who
// is fulfilling it. Transaction is created with
// a `Payee` and `Amount` types
$transaction = Paypalpayment:: Transaction();
$transaction->setAmount($amount);
$transaction->setDescription("This is the payment description.");
// ### Payment
// A Payment Resource; create one using
// the above types and intent as 'sale'
$payment = Paypalpayment:: Payment();
$payment->setIntent("sale");
$payment->setPayer($payer);
$payment->setTransactions(array($transaction));
// ### Create Payment
// Create a payment by posting to the APIService
// using a valid ApiContext
// The return object contains the status;
try {
$payment->create($this->_apiContext);
} catch (\PPConnectionException $ex) {
return "Exception: " . $ex->getMessage() . PHP_EOL;
var_dump($ex->getData());
exit(1);
}
$response=$payment->toArray();
echo"<pre>";
print_r($response);
//var_dump($payment->getId());
//print_r($payment->toArray());//$payment->toJson();
}
/*
Use this call to get a list of payments.
url:payment/
*/
public function index(){
echo "<pre>";
$payments = Paypalpayment::all(array('count' => 1, 'start_index' => 0),$this->_apiContext);
print_r($payments);
}
}
The error I am getting:
I do not know your codebase, but if your class is a controller, why are you extending a Facades class? The issue is that your code is looking for the Facades class in your my_app\Http\Controllers namespace.
Removing that line or correctly importing the class will resolve your issue. However, I think you may need to rethink your design here.