PayPal is the most popular platform for receiving online payments today. The ease of opening a PayPal account and receiving payments compared to opening a merchant account with a traditional payment gateway is probably the number one reason for its popularity, with a close second being the comprehensive API that PayPal provides for its payment services.
Disclaimer: PayPal’s API is among the worst I’ve ever had to deal with. Inconsistencies, sometimes poor or conflicting documentation, unpredictable failures and account changes, and major differences between the live and sandbox versions all conspire to make the PayPal API quite a pain in the arse to work with. Over the years, I’ve taken my lumps from working quite a bit with the PayPal API, and I’ve published the results of my hard-learned lessons as a commercial PHP PayPal API component on the source-code marketplace Binpress.
In this post, I will break down some of the techniques and approaches to working with the PayPal API, in order to make integration and troubleshooting simpler and easier.

The Different Payment Options
PayPal offers a variety of payment options, which might be confusing at first:
This list is not comprehensive, but it covers the main payment options (see the API documentation for more).
Making API Requests
PayPal supports two main formats over HTTP: NVP and SOAP. NVP is short for Name-Value Pair, and SOAP stands for Simple Object Access Protocol. I will cover the NVP approach, which I prefer to SOAP’s relatively verbose and complex syntax.
Each of the API methods has different parameters, but they all share some basic parameters, which are used to identify the API account and sign the transaction. These include:
The last required parameter is METHOD, which declares which API method we are calling.
Requests are made over HTTPS. We’ll use cURL to build our basic request, and then encapsulate the process in a class:
class Paypal {
/**
* Last error message(s)
* @var array
*/
protected $_errors = array();
/**
* API Credentials
* Use the correct credentials for the environment in use (Live / Sandbox)
* @var array
*/
protected $_credentials = array(
'USER' => 'seller_1297608781_biz_api1.lionite.com',
'PWD' => '1297608792',
'SIGNATURE' => 'A3g66.FS3NAf4mkHn3BDQdpo6JD.ACcPc4wMrInvUEqO3Uapovity47p',
);
/**
* API endpoint
* Live - https://api-3t.paypal.com/nvp
* Sandbox - https://api-3t.sandbox.paypal.com/nvp
* @var string
*/
protected $_endPoint = 'https://api-3t.sandbox.paypal.com/nvp';
/**
* API Version
* @var string
*/
protected $_version = '74.0';
/**
* Make API request
*
* @param string $method string API method to request
* @param array $params Additional request parameters
* @return array / boolean Response array / boolean false on failure
*/
public function request($method,$params = array()) {
$this -> _errors = array();
if( empty($method) ) { //Check if API method is not empty
$this -> _errors = array('API method is missing');
return false;
}
//Our request parameters
$requestParams = array(
'METHOD' => $method,
'VERSION' => $this -> _version
) + $this -> _credentials;
//Building our NVP string
$request = http_build_query($requestParams + $params);
//cURL settings
$curlOptions = array (
CURLOPT_URL => $this -> _endPoint,
CURLOPT_VERBOSE => 1,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_CAINFO => dirname(__FILE__) . '/cacert.pem', //CA cert file
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $request
);
$ch = curl_init();
curl_setopt_array($ch,$curlOptions);
//Sending our request - $response will hold the API response
$response = curl_exec($ch);
//Checking for cURL errors
if (curl_errno($ch)) {
$this -> _errors = curl_error($ch);
curl_close($ch);
return false;
//Handle errors
} else {
curl_close($ch);
$responseArray = array();
parse_str($response,$responseArray); // Break the NVP string to an array
return $responseArray;
}
}
}
Note that I use a CA certificate file for SSL certificate validation. You can obtain the file from the cURL website or any trusted source. Update the path to the certificate file according to where you’ve placed it.
The response returned will be in NVP format as well, and I reformat it into an array before returning it. A parameter named ACK signifies the status of the request: Success or SuccessWithWarning when the request succeeds, and Error or Warning when the request fails.
A request could fail for many reasons, and there are different reasons for each API method, which are covered in detail in the manual. We’ll go over some further down in this article and look at ways to handle them. Keep in mind that the parameter values are case-sensitive, so code against them accordingly.
Express Checkout
One of the most popular APIs is the Express Checkout API, which enables you to receive payments without opening a Website Payments Pro account (which is available only to verified US accounts) or hosting the actual transaction yourself (which requires additional security).
The Express Checkout process works as follows:

1. Getting the Checkout Token: SetExpressCheckout
We initiate the Express Checkout process by passing the order details to the PayPal API, and we receive a token string that identifies it. This token would be used in the next step to redirect to PayPal.
Here are the required parameters:
We can pass additional parameters to add more information about the order, some of which have default values:
We can also add details about individual items in the order:
The variable index m identifies the item. (Use the same variable for all details of the same item.)
There are many other optional parameters, which can be found in the API documentation.
We’ll use the function that we wrote above to build the SetExpressCheckout request:
//Our request parameters
$requestParams = array(
'RETURNURL' => 'http://www.yourdomain.com/payment/success',
'CANCELURL' => 'http://www.yourdomain.com/payment/cancelled'
);
$orderParams = array(
'PAYMENTREQUEST_0_AMT' => '500',
'PAYMENTREQUEST_0_SHIPPINGAMT' => '4',
'PAYMENTREQUEST_0_CURRENCYCODE' => 'GBP',
'PAYMENTREQUEST_0_ITEMAMT' => '496'
);
$item = array(
'L_PAYMENTREQUEST_0_NAME0' => 'iPhone',
'L_PAYMENTREQUEST_0_DESC0' => 'White iPhone, 16GB',
'L_PAYMENTREQUEST_0_AMT0' => '496',
'L_PAYMENTREQUEST_0_QTY0' => '1'
);
$paypal = new Paypal();
$response = $paypal -> request('SetExpressCheckout',$requestParams + $orderParams + $item);
2. Redirecting to PayPal Using the Checkout Express Token
If the request is successful, we’ll receive a checkout token in the TOKEN parameter of the response.
if(is_array($response) && $response['ACK'] == 'Success') { //Request successful
$token = $response['TOKEN'];
header( 'Location: https://www.paypal.com/webscr?cmd=_express-checkout&token=' . urlencode($token) );
}
The user now goes through the purchase process on PayPal’s website. When they confirm or cancel it, they will return to one of the URLs that we’ve specified in the request.
3. Completing the Transaction
Assuming the user confirms the transaction, they will be redirected to our website by PayPal. At this point, we should use two relevant API methods: DoExpressCheckoutPayment will complete the transaction, but before that we might want to get additional information on the buyer using GetExpressCheckoutDetails.
PayPal will redirect the user back from the purchase with the checkout token, which we will use to call those methods. The token will be available in the URL query parameters via the token parameter. We will check for its existence in the confirmation URL and then send our API requests if we find it.
The GetExpressCheckoutDetails method requires only the checkout token. DoExpressCheckoutPayment requires a couple of additional parameters:
if( isset($_GET['token']) && !empty($_GET['token']) ) { // Token parameter exists
// Get checkout details, including buyer information.
// We can save it for future reference or cross-check with the data we have
$paypal = new Paypal();
$checkoutDetails = $paypal -> request('GetExpressCheckoutDetails', array('TOKEN' => $_GET['token']));
// Complete the checkout transaction
$requestParams = array(
'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale',
'PAYERID' => $_GET['PayerID']
);
$response = $paypal -> request('DoExpressCheckoutPayment',$requestParams);
if( is_array($response) && $response['ACK'] == 'Success') { // Payment successful
// We'll fetch the transaction ID for internal bookkeeping
$transactionId = $response['PAYMENTINFO_0_TRANSACTIONID'];
}
}
Direct Payment
The Direct Payment API allows you to receive payments directly on your website or application, giving you complete control over the checkout process. PayPal tends to push users to register and use a PayPal account, which is understandable, but this conflicts somewhat with our interest to make the payment process as simple and clear as possible for our customers. For this reason, full control over the checkout process is preferred and gives us more options to optimize sales and generate more sales.

The process is a bit simpler than that of Express Checkout, because the entire interaction occurs on our website, and we need to perform just one API call to process a normal payment: DoDirectPayment.
A couple of more API requests are required if you want to perform a transaction that is billed at a later date (for example, when you ship the product or confirm availability). These would be the Authorization & Capture API methods, which I will not cover in this post, but be aware that this option exists.
DirectPayment Parameters
DirectPayment requires different parameters than Express Checkout, as to be expected. While the transaction details parameters are similar (with different key names, to make it more interesting), the method also requires credit-card and address information.
DirectPayment’s basic parameters:
Credit-card details:
Payer information and address parameters:
This address will be used in the address verification system (AVS). You’ll receive a specific error code if a transaction has failed due to an address verification failure.
The payment details parameters are the same as the ones for Express Checkout, but with slightly different names (AMT, ITEMAMT, CURRENCYCODE, SHIPPINGAMT, TAXAMT and DESC) and without the PAYMENTREQUEST_0_ prefix. Refer to the previous section or the API documentation for specific details on those.
Similarly, the item details parameters are similar to those of Express Checkout. These include L_NAMEm, L_DESCm, L_AMTm and L_QTYm, giving you granular control of item details in the order summary. The m integer variable is used to account for multiple items (replace with 0, 1 and so on for numbered items in the order). See the API documentation for a comprehensive list of item details.
Performing the Transaction
Sending the request using our function is very similar to GetExpressCheckoutToken. We pass all of the parameters into the request function as before, with the method set to DoDirectPayment.
$requestParams = array(
'IPADDRESS' => $_SERVER['REMOTE_ADDR'],
'PAYMENTACTION' => 'Sale'
);
$creditCardDetails = array(
'CREDITCARDTYPE' => 'Visa',
'ACCT' => '4929802607281663',
'EXPDATE' => '062012',
'CVV2' => '984'
);
$payerDetails = array(
'FIRSTNAME' => 'John',
'LASTNAME' => 'Doe',
'COUNTRYCODE' => 'US',
'STATE' => 'NY',
'CITY' => 'New York',
'STREET' => '14 Argyle Rd.',
'ZIP' => '10010'
);
$orderParams = array(
'AMT' => '500',
'ITEMAMT' => '496',
'SHIPPINGAMT' => '4',
'CURRENCYCODE' => 'GBP'
);
$item = array(
'L_NAME0' => 'iPhone',
'L_DESC0' => 'White iPhone, 16GB',
'L_AMT0' => '496',
'L_QTY0' => '1'
);
$paypal = new Paypal();
$response = $paypal -> request('DoDirectPayment',
$requestParams + $creditCardDetails + $payerDetails + $orderParams + $item
);
if( is_array($response) && $response['ACK'] == 'Success') { // Payment successful
// We'll fetch the transaction ID for internal bookkeeping
$transactionId = $response['TRANSACTIONID'];
}
There are plenty of parameters, but all relatively simple.
Error Handling
In a perfect world, this section would not exist. In reality, you will be referring to it quite a lot. PayPal can fail a transaction for a multitude of reasons, not all of which you can control.
The $response variable we returned from our paypalApiRequest() function could contain a different value than Success for the ACK parameter. That value could be:
This gives us two success statuses and two failure statuses. The mock code above tests for the Success value only, but we could change it to check for SuccessWithWarning as well; and keep in mind that we need to find out what the warning is. A common scenario is that a Direct Payment charge will have been performed successfully, but the credit-card company responds that the transaction has failed, for whatever reason.
Errors from PayPal are returned in four parameters in the response:
The 0 part of these parameters is an incrementing integer for multiple error message (1, 2, etc.).
Here are some common errors you’ll run into:
How Do We Handle These Errors in Practice?
PayPal error messages vary and could contain private information that you do not want your users to see (such as an invalid merchant configuration). That being the case, showing PayPal error messages directly to users is not advisable, even though some of them might be useful.
In most cases, I would do the following:
If an error falls outside of the white-listed array, I will also log it to a file on the server and send an email to the administrator, with the full details so that someone is up to speed on payment failures. In fact, logging PayPal requests and responses is good practice regardless of errors, so that you can monitor and troubleshoot payment failures (I provide this option in the commercial component that I mentioned at the beginning of this article).
Ready To Get Started With The PayPal API?
In this post, I’ve covered two of the most widely used API methods, as well as error handling with the PayPal API. This should be enough for you to get started using the most popular payment platform online.
The PayPal API has many more methods and processes, more than can be covered in any one post. Once you get up to speed on the basic methods, learning the others should be relatively straightforward (even if somewhat exhausting). I hope this guide has given you a good head start on using the API. If you have questions or comments, I would love to hear from you in the comments!
Front cover: Image source
(al)
© Eran Galperin for Smashing Magazine, 2011.
