Adventures in PayPal: Executing the sale


In order to execute the sale you'll have a return page setup where PayPal redirects to after the customer has paid. Since the user is going away from the page and returning to either the same one or a new one you need to find a way of storing the access token so that you can use this again for execution.

While it's true that you can simply create a new access token, and this will be equally valid for performing the execution, what you are meant to do is to use access tokens until their expiry (which occurs 8–9 hours after their creation). So in all likelihood the access token will be active when the user returns from PayPal.

To address this it is possible to store the token as a cookie or in session storage. Session storage keeps the data on the server and cookies save to the user's machine. So session storage seems like the obvious choice.

To perform this we need to add a two lines of code to the original call to retrieve the PayPal access token. First we need to start a session, which can be done at the head of the page:
session_start();
and second a line needs to be added to the token creation code
$_SESSION["paypal_access_token"] = $token;
so that it looks like this
echo "\n";
echo "###########################################\n";
echo "Obtaining OAuth2 Access Token.... \n";
$url = $host.'/v1/oauth2/token'; 
$postArgs = 'grant_type=client_credentials';
$token = get_access_token($url,$postArgs);
echo "Got OAuth Token: ".$token;
$_SESSION["paypal_access_token"] = $token;
echo "\n \n";
For safety, we also need to retrieve the expiry time of the token. To do this we can place a line of code inside the get_access_token: method:
$_SESSION["paypal_expires"] = time() + $jsonResponse->expires_in;
immediately before its return statement.

Executing the sale

Now everything will be prepared for the execution of the sale and our code looks like this:
<?php
session_start();
// http://stackoverflow.com/questions/15729167/paypal-api-with-php-and-curl
// https://github.com/paypal/adaptivepayments-sdk-php/issues/24
// https://github.com/paypal/rest-api-curlsamples/blob/master/execute_all_calls.php

//http://stackoverflow.com/questions/15885742/pay-with-paypal-through-paypal-rest-api-does-not-show-up-payment-description-on
// Full payment capabilities
// https://developer.paypal.com/docs/integration/direct/explore-payment-capabilities/
$host = 'https://api.sandbox.paypal.com';
$clientId = "your client id goes here";
$secret = "your secret goes here";
// check expiry and if expired request new token $_SESSION["paypal_expiry"]
$token = '';

function get_access_token($url, $postdata) {
 global $clientId, $clientSecret;
 $curl = curl_init($url); 
 curl_setopt($curl, CURLOPT_POST, true); 
 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
 curl_setopt($curl, CURLOPT_USERPWD, $clientId . ":" . $clientSecret);
// curl_setopt($ch, CURLOPT_SSLVERSION, 1);
// curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1');
 curl_setopt($curl, CURLOPT_HEADER, false); 
 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 
 curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); 
 // curl_setopt($curl, CURLOPT_VERBOSE, TRUE);
 $response = curl_exec( $curl );
 if (empty($response)) {
     // some kind of an error happened
     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();
     }
 }
 // Convert the result from JSON format to a PHP array 
 $jsonResponse = json_decode( $response );
 return $jsonResponse->access_token;
}


function make_post_call($url, $postdata) {
 global $token;
 $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 '.$token,
    'Accept: application/json',
    'Content-Type: application/json'
    ));
 
 curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); 
 #curl_setopt($curl, CURLOPT_VERBOSE, TRUE);
 $response = curl_exec( $curl );
 if (empty($response)) {
     // some kind of an error happened
     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();
     }
 }

 // Convert the result from JSON format to a PHP array 
 $jsonResponse = json_decode($response, TRUE);
 return $jsonResponse;
}

// check if token has expired, create new one if it has
if (isset($_SESSION["expiry_time"]) && $_SESSION["expiry_time"] > time()) { 
    $url = $host.'/v1/oauth2/token'; 
    $postArgs = 'grant_type=client_credentials';
    $token = get_access_token($url,$postArgs);
}
else if (isset($_SESSION["paypal_access_token"])) {
 $token = $_SESSION["paypal_access_token"];
}

$payment_execute_url = $host."/v1/payments/payment/".$_GET["paymentId"]."/execute/";

$payerId = $_GET["PayerID"];
// payerId not being received.
echo "\n \n";
echo $token;
echo "###########################################\n";
echo "Executing the PayPal Payment for PayerId (".$_GET["PayerID"].")... \n".$payment_execute_url;
$payment_execute = array(
  "payer_id" => $_GET["PayerID"]
        );
$json = json_encode($payment_execute);
echo $json;
$json_resp = make_post_call($payment_execute_url, $json);
echo "Payment Execute processed " . $json_resp['id'] ." with state '". $json_resp['state']."'";
echo "\n \n";

?>
In real use you probably don't want to go repeating your storing your secret and id across multiple files, it's just done here for convenience in order to show a self-contained return file but in real-world use we can be smarter in our code arrangement. Not only this but of course functions themselves have been repeated across the transaction and execution files, which again doesn't actually need to be happening. The point is, however, that within two fairly simple files we're able to send a request for payment and receive back the information necessary to execute that payment and to then execute.

Next steps

From here we can progress to make calls for inspecting transactions that have taken place among other things and I hope to look at these in a future post as well as making this code more object-oriented in style.

Comments