Is there a way to set up recurring payments with the PayPal REST API?

I read this question and this one. They both said (a year ago) that recurring payments via the REST API was in the works. On my client's website, customers need to be able to pay either

  1. in full (all at once — e.g., $1200 at check out)
  2. in installments ($1200 over 6 months at $200 per month)

It is crucial that his website be notified when a customer pays. I have currently set this up for option #1:

app.get("/cart/checkout/paypal", isLoggedIn, isVerified, function (req, res) {
    var user = req.user;
    var paymentDetails = {
        "intent": "sale",
        "payer": { "payment_method": "paypal"},
        "redirect_urls": {
            "return_url": "http://localhost:5000/cart/checkout/success",
            "cancel_url": "http://localhost:5000/cart"
        },
        "transactions": [
            { "amount": { "total": user.cart.finalPrice.toFixed(2), "currency": "USD"},
            "description": "You are being billed for " + user.cart.finalPrice.toFixed(2)}
        ]
    };
    paypal.payment.create(paymentDetails, function (err, payment) {
        if (err) console.log(err);
        else {
            if (payment.payer.payment_method === "paypal") {
                req.session.paymentId = payment.id;
                var redirectURL;
                for (var i = 0; i < payment.links.length; i++) {
                    var link = payment.links[i];
                    if (link.method === "REDIRECT") redirectURL = link.href;
                }
                res.redirect(redirectURL);
            }
        }
    })
})

Then, the "return_url" (/cart/checkout/success) grabs all the correct session info and my database processes it.

app.get("/cart/checkout/success", isLoggedIn, isVerified, function (req, res) {
    var user = req.user,
        paymentId = req.session.paymentId,
        payerId = req.param("PayerID"),
        details = { "payer_id": payerId };
...

Is there a similar setup for option #2 (recurring payments). If not, is there a way for PayPal to notify my server every time a user has paid an installment with the outstanding balance and amount paid/etc.?

Answers:

Answer

Yes, there is now a way to do subscriptions within the new REST API. See the documentation.

OK, first you need to set the Client ID and secret you get from PayPal. I have both a testing and live environment

All {xxx} are my private application variables

public function __construct()
{

    $this->sandbox = {sandbox};
    if($this->sandbox) 
    {
        $this->host = 'https://api.sandbox.paypal.com';
        $this->clientId = {clientIdSandbox};
        $this->clientSecret = {clientSecretSandbox};            
    }
    else 
    {
        $this->host = 'https://api.paypal.com';
        $this->clientId = {clientId};
        $this->clientSecret = {clientSecret};           
    }
    $this->get_access_token();
}

I then go and get the access token

private function get_access_token() 
{
    $curl = curl_init($this->host.'/v1/oauth2/token'); 
    curl_setopt($curl, CURLOPT_POST, true); 
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_USERPWD, $this->clientId . ":" . $this->clientSecret);
    curl_setopt($curl, CURLOPT_HEADER, false); 
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 
    curl_setopt($curl, CURLOPT_POSTFIELDS, 'grant_type=client_credentials'); 
    $response = curl_exec( $curl );
    if (empty($response)) 
    {
        echo "NO RESPONSE for $url for function ".__FUNCTION__;
        print_r(curl_getinfo($curl));
        die(curl_error($curl));
        curl_close($curl); // close cURL handler
    } 
    else 
    {
        $info = curl_getinfo($curl);
        curl_close($curl); // close cURL handler
        if($info['http_code'] != 200 && $info['http_code'] != 201 ) 
        {
            echo "Received error: " . $info['http_code']. "\n";
            echo "Raw response:".$response."\n";
            die();
        }
    }
    $jsonResponse = json_decode( $response );
    $this->token = $jsonResponse->access_token;
    $this->expires = time()+$jsonResponse->expires_in;
}

This then stores the access data in the classes properties

You then need three more sections. Create the subscription template, then retrieve the agreement, then create the agreement for the client.

In this method I send over the data Name, Desc, Period, Interval and Price. However you can just fill in manually. This will create the subscription that you can now sell.

public function create_subscription($name, $desc, $period, $interval, $price)
{
    $data = array(
        'name' => $name,
        'description' => $desc,
        'type' => 'INFINITE',
        'payment_definitions' => array(
            0 => array (
                'name' => 'Payment Definition-1',
                'type' => 'REGULAR',
                'frequency' => $period,
                'frequency_interval' => $interval,
                'amount' => array(
                    'value' => $price,
                    'currency' => 'EUR',
                ),
                'cycles' => '0',
            ),
        ),
        'merchant_preferences' => array(
            'return_url'=>{return_url},
            'cancel_url'=>{cancel_url},
            'auto_bill_amount' => 'YES',
            'initial_fail_amount_action' => 'CONTINUE',
            'max_fail_attempts' => '0',
        ),
    );
    $data=json_encode($data);
    $url = $this->host.'/v1/payments/billing-plans';
    return $this->make_post_call($url, $data);  
}

From the above method you will get in return an id, use that for the method below to collect the data of the subscription and store it

public function retrieve_agreement($id)
{
    $url = $this->host.'/v1/payments/billing-agreements/'.$id;
    return $this->make_get_call($url);      
}

This method will allow you to allocate and agreement to a client. You will need the id of the aggreement with some data for you to be able add to the description.

public function create_agreement($subId, $data, $product)
{
    $paypalId = ($this->sandbox) ? $product->paypal_test_sub_id : $product->paypal_sub_id;
    $startDate = date('c', strtotime('+10 MINUTE'));
    $data = array (
        'name'=>'Subscription for subscription::'.$subId,
        'description'=>{company}.'  Subscription - ' . $data . ' - '.$product->name.' - '.$product->price .'€',
        'start_date'=>$startDate,
        'plan'=>array(
            'id'=>$paypalId,
        ),
        'payer'=>array(
            'payment_method'=>'paypal',
        ),
        'override_merchant_preferences'=>array(
            'return_url'=>{return_url}.$subId.'/',
            'cancel_url'=>{cancel_url}.$subId.'/',
        ),
    );  
    $data=json_encode($data);
    $url = $this->host.'/v1/payments/billing-agreements';
    $response = $this->make_post_call($url, $data);
    header("location:".$response['links'][0]['href']);      
    //return $response;
}

The return_url is the url that the end user will be sent to to complete the aggreement. I than use that to pass to the method below

public function execute_agreement($token)
{

    $data=json_encode('');
    $url = $this->host.'/v1/payments/billing-agreements/'.$token.'/agreement-execute';
    return $response = $this->make_post_call($url, $data);
}

You will then need to create a scheduled task to use the retrieve_agreement method and see if a subscription has been cancelled or not.

This is a brief explanation.

if you require more please let me know.

Get and Post

private function make_post_call($url, $postdata) 
{
    $curl = curl_init($url); 
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_HTTPHEADER, array(
                'Authorization: Bearer '.$this->token,
                'Accept: application/json',
                'Content-Type: application/json'
                ));

    curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); 
    $response = curl_exec( $curl );
    if (empty($response)) 
    {
        echo "NO RESPONSE for $url for function ".__FUNCTION__;
        print_r(curl_getinfo($curl));
        die(curl_error($curl));
        curl_close($curl); // close cURL handler
    } 
    else 
    {
        $info = curl_getinfo($curl);
        curl_close($curl); // close cURL handler
        if($info['http_code'] != 200 && $info['http_code'] != 201 ) 
        {
            echo "Received error: " . $info['http_code']. "\n";
            echo "Raw response:".$response."\n";
            die();
        }
    }
    $jsonResponse = json_decode($response, TRUE);
    return $jsonResponse;
}

private function make_get_call($url) 
{
    $curl = curl_init($url); 
    curl_setopt($curl, CURLOPT_POST, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_HTTPHEADER, array(
                'Authorization: Bearer '.$this->token,
                'Accept: application/json',
                'Content-Type: application/json'
                ));
    $response = curl_exec( $curl );
    if (empty($response))
    {
        echo "NO RESPONSE for $url for function ".__FUNCTION__;
        print_r(curl_getinfo($curl));
        die(curl_error($curl));
        curl_close($curl); // close cURL handler
    } 
    else 
    {
        $info = curl_getinfo($curl);
        //echo "Time took: " . $info['total_time']*1000 . "ms\n";
        curl_close($curl); // close cURL handler
        if($info['http_code'] != 200 && $info['http_code'] != 201 ) 
        {
            echo "Received error: " . $info['http_code']. "\n";
            echo "Raw response:".$response."\n";
            die();
        }
    }
    $jsonResponse = json_decode($response, TRUE);
    return $jsonResponse;
}
Answer

I would recommend staying away from the REST API for now. It's just not complete yet, and the Classic API gives you so much more flexibility.

I'd go with Express Checkout with Recurring Payments, and then you'll want to use Instant Payment Notification (IPN) to handle processing payments, canceled profiles, etc.

IPN notifications will actually be triggered for any transaction that ever hits your account, so you can automate the processing of payments, refunds, disputes, subscriptions, etc. You can update your database, send email notifications, or anything else you need to automate based on these transaction types.

IPN is one of the most valuable tools PayPal provides, yet it's also one of the most underutilized.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.