PayPal has a huge API reference to integrate it’s services into any website. But from personal experience I found that the theory is far more simple than the practice. There are various methods to implement PayPal’s services including Soap webservices with ‘Express Checkout’ and ‘Website Payment Pro’. This tutorial will focus on ‘Website Payment Pro’ and the ‘IPN’ event listener.

IPN Services use an HTTP _POST or _GET conversation method. Your application sends a form request to PayPal’s web services which then send an HTTP request to an event handler. The event handler must then re-send the same post request back to authenticate the transaction as valid. Once returned and verified we can then process the transactions.

My tutorial assumes that a skeleton subscription row was created to be updated by the IPN event handler, then a Cron Job must be used to automatically clean up incomplete skeleton entries every 7 days or so.

Let’s look at the initial post form to paypal. In this example I will be doing a simple recurring monthly membership subscription of $30.

<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
 <input type="hidden" name="invoice" id="invoice" value="<?php echo $member['id']; ?>" />
 <input type="hidden" name="cmd" value="_xclick-subscriptions" />
 <input type="hidden" name="business" id="business" value="[email protected]" />
 <input type="hidden" name="item_name" id="item_name" value="Subscription to XYZ website" />
 <input type="hidden" name="currency_code" id="currency_code" value="USD" />
 <input type="hidden" name="notify_url" id="notify_url" value="<?php echo $site_url;?>/ipn/"/>
 <input type="hidden" name="cancel_return" value="<?php echo $site_url; ?>/cancelled/" />
 <input type="hidden" name="return" value="<?php echo $site_url; ?>/confirmation/" />
 <input type="hidden" name="no_shipping" id="no_shipping" value="1" />
 <input type="hidden" name="a3" id="a3" value="50.00" />
 <input type="hidden" name="p3" id="p3" value="1" />
 <input type="hidden" name="t3" id="t3" value="M" />
 <input type="hidden" name="src" id="src" value="1" />
 <input type="hidden" name="rm" id="rm" value="2" />
 <input type="image" name="submit" border="0" src="https://www.paypal.com/en_US/i/btn/btn_subscribe_LG.gif" alt="PayPal - The safer, easier way to pay online" />
 <img alt="" border="0" width="1" height="1" src="https://www.paypal.com/en_US/i/scr/pixel.gif" />
 </form>

Let’s go through some of the fields in the form.

The invoice field is usually used to contain a unique identifier for the transaction, such as a basket ID or order ID referencing the DB. In this example I am passing the member’s Primary Key ID as the reference.

The CMD identifier references the type of button the user has clicked to arrive at PayPal, this information is purely for PayPal’s benefit.

Business, Item_name and Currency_code are pretty self-explanatory.

The notify_url tells PayPal which resource to send the _POST responses to in the event that a user makes a payment or a recurring payment goes through. This is the location of the IPN event handler.

The return and cancel_return attributes tell PayPal where to redirect when a payment succeeds or is cancelled.

The no_shipping tells paypal to remove the user’s ability to select a shipping address as it is not required when handling subscriptions.

The a3,p3 and t3 variables are the most important. The ’3′ tells Paypal that the payments will occur in stated increments. The a value is the amount due, the p value is the increment and the t value is the time period. For example a3 = 50.00, p3 = 1, t3 = ‘M’ will translate to $50.00 every month. The t value can use ‘D’, ‘M’ or ‘Y’.

Src and RM tell PayPal to try again when transactions fail, the default is to try 3 times before cancelling the subscription.

When this request is sent PayPal will send a _POST request to our INP handler. So lets take a look at the PHP side.

$header = '';
 $req = 'cmd=_notify-validate';

 foreach($_POST as $key => $value) {
    //All PayPal reqs must be URL encoded
    $value = urlencode(stripslashes($value));
    //Append key => value pair to CMD string
    $req .= "&$key=$value";
 }

 //Post info back to paypal for verification
 $header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
 $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
 $header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
 $fp = fsockopen ('www.sandbox.paypal.com', 80, $errno, $errstr, 30);

 if(!$fp) {
     //Process HTTP Error
     $message .= "\n HTTP ERROR. \n";

 } else {
     fputs ($fp, $header . $req);
     while (!feof($fp)) {
         $res = fgets ($fp, 1024);
         if (!strcmp ($res, "VERIFIED")) {
         //VERIFIED TRANSACTION
         //PAYMENT
         if($_POST['payment_status'] == 'completed') {

         //THE PAYMENT IS COMPLETE, UPDATE INFORMATION IN SUBSCRIPTION & ADD PAYMENT
         $subscription = //Do SQL to retrieve the subscription based on $_POST['invoice']

         //UPDATE SUBSCRIPTION
         //We know that the payment has succeeded, so we can modify the last_paid fields and set the paypal id
         //Using $_POST['txn_id']

         //ADD PAYMENT
         //We can now add the payment information using $_POST['payer_id'], ['auth_id'] ['mc_gross'] ['payment_status']

     } elseif($_POST['payment_status'] == 'failed' || $_POST['payment_status'] == 'expired' ||     $_POST['payment_status'] == 'voided') {
         //THEY HAVE FAILED THE PAYMENT, REMOVE THE SUBSCRIPTION 

 } elseif(!strcmp($res, "INVALID")) {
 //INVALID - EMAIL FOR INVESTIGATION
 }
 }
 fclose ($fp);
 }

You may be wondering why I have only done some if elseif else statements, the whole answer is that I won’t do everything for you! All I have done is outlined the framework needed to do paypal transactions. The main SQL work I will leave to you!