One of the most popular methods for a website to take payments for goods and services is through PayPal, the online payment service. In particular, it's possible to authenticate credit and debit cards directly through PayPal without the need for the customer to have a PayPal account; this service is referred to as Website Payments Pro.
In the UK, Website Payments Pro is implemented as a REST or SOAP API, to which a website can connect and request transactions. There are, however, some pitfalls to implementing these requests:
- Inadequate documentation:
- PayPal provide at least three conflicting versions of the documentation for the API, each covering a different revision of the API itself; none are marked as current relative to the others, and the API as used in production incorporates elements of each. As such, it can be difficult to produce a working API request.
- The sandbox:
- A "sandbox" environment is provided for testing of API requests before they are used to process live transactions. The principle is well thought-out, but the sandbox has issues: it provides a different revision of the service to the live process, and the server hosting the sandbox is often unresponsive. This makes it, at best, time-consuming to use the sandbox.
- Two transaction methods:
- In order to provide the ability to process credit or debit cards directly (referred to as Direct Payment), PayPal stipulate that a three-step process called Express Checkout also be available for PayPal users without a card to hand. The matter of maintaining state within an Express Checkout transaction can be troublesome, and the process must be kept distinct and separate from Direct Payments.
I fell into all of the above traps when implementing WPP, so I produced the following article for reference purposes, and to serve as a coherent source of documentation for implementatin of Website Payments Pro. Please note that throughout, the API revision used is 56.0, and REST calls will be used.
The Basics: API calls
The PayPal REST API is called by POSTing a request to a secure (HTTPS) URI, with credentials generated by the holder of a "business" PayPal account. The credentials consist of an API "user" based on the account holder's email address, a credential password, and an encoded "signature" used as an additional checksum by the API. An example set of credentials would look as follows:
API credentials (example):
USER: tf_api1.imrannazar.com PWD: QF63NP99NPER3V7A SIGNATURE: AZM6n0EcNmR0AQYsCf0s1VrwkV10AlKArJ7a8X4YHG-R2oFkOwGqVrJZ VERSION: 56.0
These variables are passed, along with the transaction request parameters, in a standard POST-formatted query to the previously mentioned HTTPS URI. The API will return a POST-formatted string with the result of the transaction request, which can be split manually or with a scripting language's built-in functions. The examples in this article are written in PHP, which provides functions to build and to break up POST-format strings. The following code will produce and send a request to the PayPal API, parsing and returning the result.
PHP cURL code to call the PayPal API
class PaymentPaypal { const API_URI = 'https://api-3t.paypal.com/nvp';// PayPal API credential configurationprivate $config;// Data for this transaction (cart ID/contents/amount)private $data; function send($params) { $params['USER'] = $this->config['USER']; $params['PWD'] = $this->config['PWD']; $params['SIGNATURE'] = $this->config['SIGNATURE']; $params['VERSION'] = '56.0';// Fire up a POST request to PayPal$c = curl_init(); curl_setopt_array($c, array( CURLOPT_URL => self::API_URI, CURLOPT_FAILONERROR => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($params) )); $result = curl_exec($c); if(!$result) {// Request failed at HTTP time; return the cURL errorreturn array('ERROR' => curl_error($c)); } else {// Request returned; break out the response into an arraycurl_close($c): $r = array(); parse_str($result, $r); return $r; } } }
The config
member variable for this class can be filled at construction time, and its handling is not shown here.
Error handling
As shown above, a rudimentary catch can be made if PayPal refuses to respond properly, since cURL will produce an error in such a circumstance. However, if the request succeeds but PayPal returns an error, these errors must also be accounted for. PayPal allows for this by sending error messages back with the response as appropriate.
If any error messages are provided in the response, they will be provided in three formats:
L_ERRORCODEx
: An error code for referencing against PayPal's list of error codes and messages;L_SHORTMESSAGEx
: A short technical description of the error;L_LONGMESSAGEx
: A long description which can be printed direct to the user.
PayPal recommend that if a message is to be printed to the user when you trap an error, it should be the long message, since the short message may be difficult to understand. An example of such a situation is error code 10537
, "Risk Control Country Filter Failure". The long message for this is currently documented as: "The transaction was refused because the country was prohibited as a result of your Country Monitor Risk Control Settings", which is eminently more understandable than the short message or the error code itself.
Since a PayPal transaction may result in more than one error, each set of error code and messages is suffixed with a number, denoted with "x" above. The numbers start at 0, which means that any failure in an API call is usually documented in the L_LONGMESSAGE0
part of the response. If L_LONGMESSAGE0
doesn't sufficiently explain the cause of the error, your code can check for any other errors that may have arisen.
Before the request: User input
As previously mentioned, PayPal allows users of Website Payments Pro to use either Express Checkout (EC) or Direct Payments (DP) to authenticate a transaction. In order to provide the choice, and to take credit card details if DP is to be used, a form must be presented to the user stating how much is to be paid, and showing the two methods.
PayPal stipulate the following requirements for this input form:
- The form must state the cost to the user for this transaction (the amount to be paid).
- An "Express Checkout" button must be provided, which will start the EC process.
- A form to take credit card details must be provided if DP is to be used by the website, which must provide to PayPal the billing name and address as well as card details.
The form may be rendered however you wish, and the billing name and address can be taken beforehand from a previously registered account or provided directly with the card details in the DP form. An example would look as follows.
Express Checkout
The Express Checkout process runs in three stages, allowing a user to log in to PayPal and use their account balance to pay for the transaction. Once they've logged in, they can confirm their information such as a shipping address if the website requires it, and then the transaction is sent through the API to be charged against the PayPal account.
The first step, referred to as SetExpressCheckout
in the documentation, is to generate a transaction "token" that will allow both your site and PayPal to track through the three steps: this is done by calling PayPal through the API and requesting an EC token. Once the token has been generated, the user is forwarded to PayPal so that they can log in, and PayPal will then return the user to your site.
Req/Ret | Name | Value |
---|---|---|
Request | TRXTYPE | S |
Request | ACTION | S |
Request | AMT | Amount of the transaction |
Request | CURRENCYCODE | Currency of the transaction (GBP) |
Request | RETURNURL | URL for PayPal to direct to for stage 2 |
Request | CANCELURL | URL for PayPal to direct to if cancelled |
Return | ACK | Success or Failure |
Return | TOKEN | Generated token |
If the API return comes back with an ACK
value of "Failure", the return will not contain a TOKEN
; this can be used to check whether the request for a token succeeded.
The URL parameters to the stage-1 request allow a website to know whether a user has proceeded to step 2, or has cancelled the transaction at the PayPal side. By passing transaction information like the ID through these URLs, the website can be informed and take the appropriate action, such as marking a transaction as "Cancelled" if the CANCELURL
is triggered. The URLs shown for this purpose below link into a ficticious routing framework, but they can be modified to match your configuration.
Request code for Express Checkout stage 1
class PaymentPaypal { function express_stg1() { $params = array( 'TRXTYPE' => 'S', 'ACTION' => 'S', 'AMT' => $this->data['transaction_amount'], 'CURRENCYCODE' => 'GBP', 'RETURNURL' =>' ($this->config['SITEBASE'].'/checkout/ec2/'.$this->data['transaction_id']), 'CANCELURL' => ($this->config['SITEBASE'].'/checkout/cancel/'.$this->data['transaction_id']) ); $response = $this->send($params); if($response['ACK'] == 'Failure' || !isset($response['TOKEN'])) {// Request failed; return errorreturn array( 'status' => 'FAIL', 'msg' => $response['L_LONGMESSAGE0'] ); } else {// Request successful; forward user to PayPal and end scriptheader('Location: https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='.$response['TOKEN']); die('FORWARD'); } } }
Once the user has logged into PayPal and returned to your site, PayPal allow for the retrieval of the token and further redirection for entering a shipping address and related details, through the step named GetExpressCheckout
. This can be advantageous if your site doesn't have the ability to tie a billing and shipping address against a given user's profile, but PayPal state that this second step can be skipped if these details are already on file against a profile on your site. I've found it simpler to deal with these parts of the profile directly, than forward through PayPal again, so stage 2 of Express Checkout is simply a button to take the user to stage 3. This can be implemented, of course, as a direct link to stage 3 using the RETURNURL
parameter to the stage-1 call.
Either way that this is implemented, the URL returned to will be provided with two parameters on the GET line: the original EC token generated in stage 1, and a PayerID
which corresponds to the login given by the user to PayPal. The third stage of Express Checkout, DoExpressCheckout
, does the actual work of firing a transaction through the API using the authenticated token provided by the PayPal user, and uses both of these as parameters to the API call.
Req/Ret | Name | Value |
---|---|---|
Request | TRXTYPE | S |
Request | ACTION | D |
Request | AMT | Amount of the transaction |
Request | CURRENCYCODE | Currency of the transaction (GBP) |
Request | TOKEN | EC token generated in stage 1 |
Request | PAYERID | PayerID returned to stage 2 |
Request | PAYMENTACTION | Sale or Authorization |
Return | RESULT | 0 if successful, positive for comms error, negative for declined |
Return | RESPMSG | Short description of EC result |
The only new parameter here is PAYMENTACTION
; this allows for a PayPal account to be checked for authorisation without proceeding with a full sale, and can be useful for testing purposes as well as advanced purposes such as recurring billing and invoicing. Such features of the Express Checkout are beyond the scope of this article, but PayPal provide a PDF describing EC integration which goes into some detail about these. (Note, however, that this documentation is outdated in its description of the basic sending of API requests; the method described in this article is more current.) For the moment, it's sufficient to set this to Sale
and request a full transaction every time.
In addition to checking the standard error codes for a PayPal response, it's prudent to check the RESULT
of the stage-3 call, to ensure that it comes back as a successful transaction (zero). If another value is set, the RESPMSG
will describe what happened to the transaction, such as "Declined".
Request code for Express Checkout stage 3
class PaymentPaypal { function express_stg3($token, $payerid) { $params = array( 'TRXTYPE' => 'S', 'ACTION' => 'D', 'AMT' => $this->data['transaction_amount'], 'CURRENCYCODE' => 'GBP', 'TOKEN' => $token, 'PAYERID' => $payerid, 'PAYMENTACTION' => 'Sale' ); $response = $this->send($params); if(isset($response['L_ERRORCODE0']) || $response['RESULT'] != 0 || !isset($response['TOKEN'])) { return array( 'status' => 'FAIL', 'msg' => $response['RESPMSG'] ); } else { return array( 'status' => 'PASS', 'msg' => 'Transaction complete' ); } } }
Direct Payment
The alternative to Express Checkout is Direct Payment, the immediate charging of a credit or debit card without the user needing to sign up to PayPal. DP takes a set of card details, along with a billing name and address, and sends them through PayPal; the response will either be a successful charge against the card, or a failure with one of a number of reasons: mismatched billing name, invalid card number, and so forth. Because a good deal of information is asked for by DP, all the request fields below are required unless marked otherwise.
Req/Ret | Name | Value |
---|---|---|
Transaction details | ||
Request | TRXTYPE | S |
Request | TENDER | C |
Request | AMT | Amount of the transaction |
Request | CURRENCYCODE | Currency of the transaction (GBP) |
Request | METHOD | DoDirectPayment |
Request | PAYMENTACTION | Sale |
Request | IPADDRESS | User's remote IP |
Card details | ||
Request | CREDITCARDTYPE | Type of card (Visa, MasterCard, Amex, Maestro, Solo) |
Request | ACCT | Card number (12-20 digits) |
Request | EXPDATE | Expiry date (MMYYYY, month is 01-12) |
Request | STARTDATE | Start date (MMYYYY, month is 01-12) Required for Maestro and Solo cards only |
Request | CVV2 | Card security code (3-6 digits) |
Request | FIRSTNAME | Cardholder's forename |
Request | LASTNAME | Cardholder's surname |
Request | STREET | Billing house number/name and street |
Request | STREET2 | [Optional] Second billing address line |
Request | CITY | Billing address town/city |
Request | ZIP | Billing address postcode |
Request | COUNTRYCODE | Card issuing country (GB) |
Return | ||
Return | ACK | Success or Failure |
Return | TRANSACTIONID | Alphanumeric ID given by PayPal |
Return | TIMESTAMP | ISO-formatted time and date of transaction |
Return | AMT | Amount of the transaction |
Return | CURRENCYCODE | Currency of the transaction (GBP) |
PayPal will request address and CVV matching against the card when you send it through for processing; the results of these matches will be provided in the response along with the other parameters above, as the AVSADDR
, AVSZIP
and CVV2MATCH
response values. Each of these will have one of the following characters as its value:
- Y: Value was matched against that held at the bank;
- N: Value was checked and found to be incorrect;
- X: Value was not checked.
PayPal won't decline a transaction if these fail, but you can record these values against the transaction in case anything comes of them. The important flag, as with Express Checkout, is ACK
; this will indicate whether the charge succeeded.
Request code for Direct Payment
class PaymentPayPal { function direct($cc) { $params = array( 'TRXTYPE' => 'S', 'TENDER' => 'C', 'AMT' => $this->data['transaction_amount'], 'CURRENCYCODE' => 'GBP', 'METHOD' => 'DoDirectPayment', 'PAYMENTACTION' => 'Sale', 'IPADDRESS' => $_SERVER['REMOTE_ADDR'], 'CREDITCARDTYPE' => $cc['type'], 'ACCT' => $cc['number'], 'EXPDATE' => sprintf('%02d%04d', $cc['expmonth'], $cc['expyear'], 'CVV2' => $cc['cvv'] 'FIRSTNAME' => $this->data['user_fname'], 'LASTNAME' => $this->data['user_sname'], 'STREET' => $this->data['user_adstreet'], 'CITY' => $this->data['user_adtown'], 'ZIP' => $this->data['user_adpostcode'], 'COUNTRYCODE' => 'GB', );// Fill in the start date if requiredif($cc['type'] == 'Maestro' || $cc['type'] == 'Solo') { $params['STARTDATE'] = sprintf('%02d%04d', $cc['startmonth'], $cc['startyear']); } $response = $this->send($params); if(isset($response['L_ERRORCODE0']) || $response['ACK'] == 'Failure') { return array( 'status' => 'FAIL', 'msg' => $response['L_LONGMESSAGE0'] ); } else { return array( 'status' => 'PASS', 'msg' => 'Transaction complete' ); } } }
Troubleshooting
A few issues may come up while using the above routines; a couple of the most common ones I came across are documented below.
- Direct Payment transactions fail on small amounts of money:
- PayPal will only accept a credit or debit card for charging if the transaction is more than £1.00; it's for this reason that the cost of the test transaction in Figure 1 is £1.20, to get over this hurdle and allow PayPal to process the transaction.
- PayPal returns an "invalid merchant configuration" on Direct Payment:
- This most often comes up if the PayPal account holder has upgraded their account to Website Payments Pro, and then not paid the monthly £20.00 fee; if this fee isn't paid, the Pro functionality and API access will be suspended, and the routines above won't have any valid credentials to connect with.
In Closing
That's pretty much all you need to know in order to send a transaction through PayPal Website Payments Pro. I haven't covered the more advanced aspects, such as recurring billing and refunds, but these are little more than different TRXTYPE
's and are adequately documented as such in PayPal's own documentation. Just be aware that the authentication methods have changed between various revisions of the API: if the documentation asks that you send a VENDOR
value, you can safely ignore it.
Imran Nazar <tf@imrannazar.com>, Sep 2009