Tips for PHP Developers on the PayPal API

I recently had a large amount of frustration and work trying to get an integration going with the PayPal Express Checkout for Digital Goods API. The steps to just get an account were tricky, but then weeding through all of the API documentation was hard. Mind you, I had just come off of building integrations with both SendGrid and Twilio, which have fantastic RESTful APIs. In all, the entire process was 8 hours. After that much trouble, I wanted to put online a few of the tips, tricks, code, and documentation links to help others out.

Documentation

PayPal does offer documentation, and in many ways it is really robust. However, it doesn’t do a good job of stepping you through the basics of connecting, then the integration, and doesn’t offer clear examples of how the responses will be returned. However, here is where I found all the relevant details.

 

Testing

PayPal does do a great job of offering a SandBox account, so you can test every scenario before turning on the real credentials. They also do a great job of including a wizard to auto-generate the appropriate accounts for testing. Really, this was the best part of the whole experience. You can access the PayPal Sandbox at anytime. You get to use API credentials for the sandbox, and then just switch them out when you are ready with your real account’s API credentials.

Sending Requests to the API

The basic principle is to send an HTTP POST with all the important values over to the API. To do so, below is some helpful code. First and foremost, a function for sending over a cURL request:

function CurlMePost($url,$post){ 
	// $post is a URL encoded string of variable-value pairs separated by &
	$ch = curl_init();
	curl_setopt ($ch, CURLOPT_URL, $url);
	curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt ($ch, CURLOPT_POST, 1);
	curl_setopt ($ch, CURLOPT_POSTFIELDS, $post); 
	curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, 3); // 3 seconds to connect
	curl_setopt ($ch, CURLOPT_TIMEOUT, 10); // 10 seconds to complete
	$output = curl_exec($ch);
	curl_close($ch);
	return $output;
}

Before I pass along the code for your initial communication, it’s important to mention how PayPal’s API responds to requests. It actually outputs directly to the page a url string much like you pass to the function above. Thus, you’ll notice that in the below code, I use the function parse_str() to break it into an array that I can reference and work with it easily. With the initial communication, there are two response variables: ACK and TOKEN. TOKEN is only returned if ACK is equal to ‘Success’ or ‘SuccessWithWarning’ (this is the status message from PayPal).

/************************************************
Note: $cost variable must be provided by you.
In this example, I am sending over only 1 item, 
thus item cost and total cost are the same.
************************************************/
//$baseurl = 'https://api-3t.sandbox.paypal.com/nvp'; //sandbox
$baseurl = 'https://api-3t.paypal.com/nvp'; //live
$username = urlencode('yourApiUsernameFromPaypal');
$password = urlencode('yourApiPasswordFromPaypal');
$signature = urlencode('yourSignatureFromPaypal');
$returnurl = urlencode('http://yourURL'); // where the user is sent upon successful completion
$cancelurl = urlencode('http://yourURL'); // where the user is sent upon canceling the transaction
$post[] = "USER=$username";
$post[] = "PWD=$password";
$post[] = "SIGNATURE=$signature";
$post[] = "VERSION=65.1"; // very important!
$post[] = "PAYMENTREQUEST_0_CURRENCYCODE=USD"; 
$post[] = "PAYMENTREQUEST_0_AMT=$cost";
$post[] = "PAYMENTREQUEST_0_ITEMAMT=$cost";
$post[] = "PAYMENTREQUEST_0_PAYMENTACTION=Sale"; // do not alter
$post[] = "L_PAYMENTREQUEST_0_NAME0=NameOfDigitalGood"; // use %20 for spaces
$post[] = "L_PAYMENTREQUEST_0_ITEMCATEGORY0=Digital"; // do not alter
$post[] = "L_PAYMENTREQUEST_0_QTY0=1";
$post[] = "L_PAYMENTREQUEST_0_AMT0=$cost";
$post['returnurl'] = "RETURNURL=$returnurl"; // do not alter
$post['cancelurl'] = "CANCELURL=$cancelurl"; // do not alter
$post['method'] = "METHOD=SetExpressCheckout"; // do not alter
 
$post_str = implode('&',$post);
$output_str = CurlMePost($baseurl,$post_str);
parse_str($output_str,$output_array);
$ack = $output_array['ACK'];
$token = (!empty($output_array['TOKEN'])) ? $output_array['TOKEN'] : '';

If you get back a successful response, and thus a TOKEN, then you will be reusing that TOKEN to redirect the user to PayPal per the documentation (make sure you urlencode it). While I didn’t make use of the javascript overlay they provide (it just made it more and more complicated), the URLs you can use are noted below:

//$redirecturl = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=$token"; //sandbox
$redirecturl = "https://www.paypal.com/incontext?token=$token"; //live

After the Transaction

This is where the PayPal documentation and behaviour gets weird. See, your return and cancel urls (which I used the same URL and smart code) will be where the respondent is sent, and PayPal will always send over as a query string ($_GET) the token. Of course, in this situation, it is $_GET[‘token’] (notice it is lowercase now). So if they cancel, you will get just a token back. If it is successful, you will also get PayerID: $_GET[‘PayerID’]. If you get that back, you have to do a final confirmation with PayPal, passing back almost everything all over again, and also including the PayerID and token.

$payerid = urlencode($_GET['PayerID']);
$token = urlencode($_GET['token']);
 
//$baseurl = 'https://api-3t.sandbox.paypal.com/nvp'; //sandbox
$baseurl = 'https://api-3t.paypal.com/nvp'; //live
$username = urlencode('yourApiUsernameFromPaypal');
$password = urlencode('yourApiPasswordFromPaypal');
$signature = urlencode('yourSignatureFromPaypal');
$post[] = "USER=$username";
$post[] = "PWD=$password";
$post[] = "SIGNATURE=$signature";
$post[] = "VERSION=65.1";
$post[] = "PAYMENTREQUEST_0_CURRENCYCODE=USD";
$post[] = "PAYMENTREQUEST_0_AMT=$cost";
$post[] = "PAYMENTREQUEST_0_ITEMAMT=$cost";
$post[] = "PAYMENTREQUEST_0_PAYMENTACTION=Sale";
$post[] = "L_PAYMENTREQUEST_0_NAME0=NameOfDigitalGood"; // use %20 for spaces
$post[] = "L_PAYMENTREQUEST_0_ITEMCATEGORY0=Digital";
$post[] = "L_PAYMENTREQUEST_0_QTY0=1";
$post[] = "L_PAYMENTREQUEST_0_AMT0=$cost";
$post['method'] = "METHOD=DoExpressCheckoutPayment";
$post['token'] = "TOKEN=$token";
$post['payerid'] = "PayerID=$payerid";
$post_str = implode('&',$post);
$output_str = CurlMePost($baseurl,$post_str);
parse_str($output_str,$output_array);

This last call returns just like the first one (where you got the token), and includes a lot more data. The most important of course is still ACK, which should return ‘Success’. It returns some really useful information, including fees charged, and unfortunately I lost my notes on the fields returned for digital goods. However, the one I chose to store, the most important one in my opinion, is the Transaction ID:

$transactionid = $output_array['PAYMENTINFO_0_TRANSACTIONID'];

There you have it, a quick guide on working with the PayPal API for Digital Goods. Do make sure if the payments are less than $12, that you set your PayPal account to micropayments for the $0.05 + 5% fee rate, which makes a huge difference on small amounts, compared to the standard rate of $0.30 + 2.9%.

Update: June 14, 2011

A friend pointed out that for security, I really should use a cURL with SSL verification enabled. For those interested in taking this route, please see the following well-written guide on downloading the certificate and adjusting the cURL call: UnitStep.net. Here is also a new function using your downloaded certificate (renamed it by adding .crt to the end of the default name) (changed to represent the new file name specified below):

function CurlMePaypal($url,$post){
	$ch = curl_init();
	curl_setopt ($ch, CURLOPT_URL, $url);
	curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, true);
	curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 2);
	curl_setopt ($ch, CURLOPT_CAINFO, getcwd() . "/api-3t.paypal.com.pem");
	curl_setopt ($ch, CURLOPT_POST, 1);
	curl_setopt ($ch, CURLOPT_POSTFIELDS, $post); 
	curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, 3);
	curl_setopt ($ch, CURLOPT_TIMEOUT, 10);
	$output = curl_exec($ch);
	curl_close($ch);
	return $output;
}

Update: March 23, 2015

Paypal is updating their SSL certificates and you must download the new api-3t.paypal.com.pem file.

16 thoughts on “Tips for PHP Developers on the PayPal API

  1. Arthur

    Very good blog! Do you have any suggestions for aspiring writers?
    I’m planning to start my own site soon but I’m a little lost on everything.
    Would you propose starting with a free platform like WordPress or go for a
    paid option? There are so many options out there that I’m completely confused ..

    Any ideas? Cheers!

    Reply
  2. thiết kế website nghệ an

    My programmer is trying to convince me to move to .net from PHP.
    I have always disliked the idea because of the costs. But he’s
    tryiong none the less. I’ve been using WordPress on a number of websites for
    about a year and am worried about switching to another platform.
    I have heard great things about blogengine.net. Is there a
    way I can transfer all my wordpress content into it?
    Any help would be really appreciated!

    Reply
  3. Reward Points for Magento 2

    It lacks theme and plugin store as compared to other ecommerce platforms but with $5999 plan, Yo.

    You may create your drawing without any additional layers, but it is always
    better to create layers whenever required. This audio configuration also
    features real-time traffic, weather, fuel prices, sports,
    and stocks.

    Reply
  4. Bruno

    Hi,
    I developped a programm with the paypal APIs. All is running, but please, could you advise me how to protect my Paypal merchant credentials ( USER, PWD, SIGNATURE). Now, they’re stored as variables in a php file . Thanks

    Reply
  5. Muhammad Azeem

    Thanks dude…

    Its really helpful. Paypal’s extensive documentation stuck my mind. Nice article.

    Reply
  6. Deepak Vishwakarma

    Thank you so much, I was using code provided by Paypal Integration Wizard but it kept failing due to some reason or the other, I used your code and it worked like charm the very first time. What a relief!! Thank you!!

    Reply
  7. Janine

    all the time i used to read smaller content that
    as well clear their motive, and that is also happening with
    this article which I am reading here.

    Reply
  8. Erik

    Thank you very much for sharing this. Exactly what I was looking for (basically in order to avoid spending too much time in PayPal documentation hell, crappy PHP4 libraries, examples and infos you never can be sure if it applies).

    Kudos! Much appreciated.

    Reply
  9. Brent

    Thanks for publishing Mario. The more community documentation we can get out there for Digital Goods the better.

    After I deciphered the PayPal documentation, I abstracted the API with an easy-to-use PHP class. I’ve released the class free & open-source on GitHub. You can find it here: https://github.com/thenbrent/paypal-digital-goods

    Despite already having battled (and beaten) the PayPal Doc Demon, you may still find the class useful for interacting with the API.

    Reply
  10. Sasha

    Thanks for the article, Mario. I struggled with many of the same issues, and in my quest to find answers I ran across a nice digital goods script from Firesage Solutions. I was literally up and running in 10 minutes, without having to worry about all the Paypal communication, tokens, and parameter parsing. It even simplified the product details, as well as the variables for both post and get. I only wish I had found it before I wasted two days of my life working from the Paypal docs. Hopefully this post saves someone the same frustration.

    http://firesage.com/digitalgoods.php

    Reply

Leave a Reply to Muhammad Azeem Cancel reply

Your email address will not be published. Required fields are marked *