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.
- Paypal Name-Value Pair (NVP) API Overview – Basic Connectivity
- Digital Goods for Express Checkout – Main Documentation
- How to Find your API Credentials
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.
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
Is old! Pls update source
Thanks a lot, this solutions works amazing for all those needs to send request through ajax, this php code helps a lot to avoid cross domain issues. I’m very glad bro.
Thanks dude…
Its really helpful. Paypal’s extensive documentation stuck my mind. Nice article.
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!!
very useful! Thanks for your source
Thanks for the article, Mario
Thanks very much. It help me alot!
Thanks!
Tongny
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.
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.
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