• Howdy! Welcome to our community of more than 130.000 members devoted to web hosting. This is a great place to get special offers from web hosts and post your own requests or ads. To start posting sign up here. Cheers! /Peo, FreeWebSpace.net
managed wordpress hosting

Automating website payments with Paypal IPN

Status
Not open for further replies.

krakjoe

stop staring
NLC
So you write a website, you want to sell website templates, webhosting, ebooks or whatever it is that you do. You go looking for a way to take payments and automate some of the business your website has to carry out, only you hit a brick wall, because all the IPN examples that are available on the internet do not work, are out of date, or are too complicated to actually use. So I come along and write a walkthrough tutorial with working code to make your headache go away ... here goes ....

IPN: Instant Payment Notification

Paypal provide a way for your website to automate some of your business, in the shape of IPN. When someone clicks on a "Buy Now" button on your website they are taken to paypal, they login and send a payment, asynchronously ( at the same time ) paypal send data concerning the payment to a predefined script. The payment is sent by way of HTTP POST, your predefined script parses the data and sends it back to paypal. Paypal's server will reply with a response such as "VERIFIED" or "INVALID", this allows your script to send an email containing the template, ebook, webhosting details or whatever, all while you were sleeping or drinking beer.

IPN seems quite confusing at first, there's a ton of variables that paypal could or should send and reading the documentation can be quite daunting, what I plan to do here is dumb it down ( not that you're stupid !! ), so you can start using it, with experience your knowledge will grow as it did with PHP.

So, first of all, you login to your paypal account and enable IPN in your profile. So click on "Profile" in the paypal menu. On the right hand side under the "Selling Preferences" heading, you will see "Instant Payment Notification Preferences", you need to enable this and set the url to something like http://youdomain.com/ipn.php ( ipn.php is what we're writing here ).

The most generic code will send emails about payments to a predefined address, that's what we're doing here, of course you can have the script do whatever suits your website best, this is a working but very generic example of what to do and how it works.

Now, coding standards dictate that anything constant should be defined, so at the top of the script, you'll have the following

PHP:
define( "PAYPAL_WEBSCR",            "www.paypal.com" ); # Server to use
define( "PAYPAL_ADDRESS",            "you@yourdomain.com" ); # paypal address expected
define( "PAYPAL_CURRENCY",            "USD" ); # Currency of transaction
define( "PAYPAL_REQUIRED_STATUS",        "completed" ); # All payments should be completed before we take automated action
define( "PAYPAL_ATTEND",            "you@yourdomain.com" ); # Address to send notifications generated by this code to
These are commented already, so read the comments.

As always, when recieving input, even though it's from what you would expect to be a safe source, you should sanitize the input, if you're using mysql in any way then best mysql_real_escape_string, I'll use stripslashes if magic_quotes are on...

PHP:
if( get_magic_quotes_gpc() ) $_REQUEST = array_map( 'stripslashes', $_REQUEST );
You'll notice, I use _REQUEST instead of _POST, this isn't necessary, nor is it unsafe in any way. _REQUEST commonly contains phpsessid, which can sometimes be usefull for tracking bugs.

Next, we open a socket to paypal, on port 80 ( assuming you dont have a way to support SSL here, infact you should use ssl where available, as that's not generic I won't )

PHP:
if( count( $_REQUEST ) )
{
    if( ( $sock = fsockopen( PAYPAL_WEBSCR, 80, $errno, $errstr, 5 ) ) ) # Open a socket to paypal
    {
        $post[ ] = "cmd=_notify-validate"; # Append what we're doing here
        
        foreach( $_REQUEST as $k => $v ) $post[ ] = sprintf( "%s=%s", $k, urlencode( $v ) ); # Build postback
        
        $post = implode( '&', $post ); # Finalize postback
        
        $request[ ] = "POST /cgi-bin/webscr HTTP/1.0"; # POST request
        $request[ ] = sprintf( "Content-length: %d", strlen( $post ) ); # Length of final postback
        $request[ ] = "Content-type: application/x-www-form-urlencoded"; # Duh
        $request[ ] = "" ; # HTTP Compliant post request
        $request[ ] = $post ; # Final post string
        
        fwrite( $sock, sprintf( "%s\r\n", implode("\r\n", $request ) ) ); # Write postback to paypal
        
        while( !feof( $sock ) ) $response .= fgets( $sock ); # Get response from paypal server
        
        fclose( $sock ); # Close socket
    }
Notice, I make sure there is data in _REQUEST before I do anything, dont want to waste anyones time. I'll take some time to explain this line by line ....

As mentioned I check _REQUEST has data, we then attempt to open a socket ( actually a file descriptior to a socket ).

Before we build a request to send to paypal, we need to tell paypal what we're doing, this is IMPORTANT

PHP:
$post[ ] = "cmd=_notify-validate";
You'll have noticed when you go to paypal, or do anything there is a cmd variable in the url, _notify-validate is as it sounds, tells paypal we want to validate a transaction, if this is not added, no dice...

Next, we build the request to send to paypal, we use an array just to make code neater and less prone to errors because you forgot a . before the assignment operator

PHP:
foreach( $_REQUEST as $k => $v ) $post[ ] = sprintf( "%s=%s", $k, urlencode( $v ) );
So, now out $post array conatins whatever _REQUEST did, but urlencoded and with the added cmd variable. to transform the array into a query string simply

PHP:
$post = implode( '&', $post );
now $post contains the formatted and urlencode'd query string to send to paypal.

Next, we build a standard HTTP POST header to write to our socket, as always the first line is

PHP:
$request[ ] = "POST /cgi-bin/webscr HTTP/1.0";
Notice, NO \r\n at the end of the line, again, we're going to implode an array. The next line will be the content length of the request we just formatted in $post

PHP:
$request[ ] = sprintf( "Content-length: %d", strlen( $post ) );
As it's a post request, you should also

PHP:
$request[ ] = "Content-type: application/x-www-form-urlencoded";
When you post data to a server there should be a newline between headers and the post data, so

PHP:
$request[ ] = ""
Then the post string from $post

PHP:
$request[ ] = $post
Next we write the headers and post request to the socket with

PHP:
fwrite( $sock, sprintf( "%s\r\n", implode("\r\n", $request ) ) );
Notice, this part adds \r\n to the end of each header line, but NOT after post, so sprintf'ing the string is necessary to append the final newline feed.

Paypal will now respond with a result, so

PHP:
while( !feof( $sock ) ) $response .= fgets( $sock );
$response now contains both the response headers and body from paypal.

and then we

PHP:
fclose( $sock ); # Close socket
Next we want to build an email to send if something goes wrong to PAYPAL_ATTEND defined at the top of the code.

To make it simple

PHP:
foreach( $_REQUEST as $k => $v ) $email[ ] = sprintf( "%s:\t%s", $k, urldecode( $v ) );
$email[ ] = sprintf( "RAW RESPONSE:\n%s\n", $response )
This gets everything that's happening into our email array, including paypals response.

PHP:
$email = implode("\n", $email );
Then finalize the email as a string.

Now, what we're looking for in the $response is the word "VERIFIED", in capital letters just like that, so we're going to start testing the response and taking action accordingly...

PHP:
if( ereg( "VERIFIED", $response ) ) # Ensure the customer was verified
    {
        if( strtolower( $_REQUEST['payment_status'] ) == PAYPAL_REQUIRED_STATUS ) # Check for completed payment, echeck etc dont' complete instantly
        {
            if( $_REQUEST['receiver_email'] == PAYPAL_ADDRESS ) # Check for correct email address
            {
                if( $_REQUEST['mc_currency'] == PAYPAL_CURRENCY ) # Check for correct currency
                {
                    
                }
                else mail( PAYPAL_ATTEND, "Non standard currency used", $email );
            }
            else mail( PAYPAL_ATTEND, "Payment to wrong address", $email );
        }
        else mail( PAYPAL_ATTEND, "Non completed payment", $email );
    }
    else mail( PAYPAL_ATTEND, "Suspicious payment recieved", $email );
First, we check for VERIFIED, if it's found we then see if the payment was completed ( or whatever status you defined at the top of script ), we then check the payment went to the correct address, and finally, we check that the currency of the payment was as expected ( obviously a different currency could mean less money ) ...

Now, I've written as much as I can, the rest is up to you, and your site, the place to put your successfull handling code is

PHP:
if( $_REQUEST['mc_currency'] == PAYPAL_CURRENCY )
{
                    // your site specific code here
}
else mail( PAYPAL_ATTEND, "Non standard currency used", $email );
and the final script look like...

PHP:
<?php

define( "PAYPAL_WEBSCR",            "www.paypal.com" ); # Server to use, should normally be www.paypal.com
define( "PAYPAL_ADDRESS",            "you@yourdomain.com" ); # paypal address expected
define( "PAYPAL_CURRENCY",            "USD" ); # Currency of transaction
define( "PAYPAL_REQUIRED_STATUS",        "completed" ); # All payments should be completed before we take automated action
define( "PAYPAL_ATTEND",            "warning@yourdomain.com" ); # Address to send notifications generated by this code to

if( get_magic_quotes_gpc() ) $_REQUEST = array_map( 'stripslashes', $_REQUEST );

if( count( $_REQUEST ) )
{
    if( ( $sock = fsockopen( PAYPAL_WEBSCR, 80, $errno, $errstr, 5 ) ) ) # Open a socket to paypal
    {
        $post[ ] = "cmd=_notify-validate"; # Append what we're doing here
        
        foreach( $_REQUEST as $k => $v ) $post[ ] = sprintf( "%s=%s", $k, urlencode( $v ) ); # Build postback
        
        $post = implode( '&', $post ); # Finalize postback
        
        $request[ ] = "POST /cgi-bin/webscr HTTP/1.0"; # POST request
        $request[ ] = sprintf( "Content-length: %d", strlen( $post ) ); # Length of final postback
        $request[ ] = "Content-type: application/x-www-form-urlencoded"; # Duh
        $request[ ] = "" ; # HTTP Compliant post request
        $request[ ] = $post ; # Final post string
        
        fwrite( $sock, sprintf( "%s\r\n", implode("\r\n", $request ) ) ); # Write postback to paypal
        
        while( !feof( $sock ) ) $response .= fgets( $sock ); # Get response from paypal server
        
        fclose( $sock ); # Close socket
    }
    
    foreach( $_REQUEST as $k => $v ) $email[ ] = sprintf( "%s:\t%s", $k, urldecode( $v ) ); # Build an email for later
    
    $email[ ] = sprintf( "RAW RESPONSE:\n%s\n", $response ); # Append raw response to email
    
    $email = implode("\n", $email ); # Finalize email
    
    if( ereg( "VERIFIED", $response ) ) # Ensure the customer was verified
    {
        if( strtolower( $_REQUEST['payment_status'] ) == PAYPAL_REQUIRED_STATUS ) # Check for completed payment, echeck etc dont' complete instantly
        {
            if( $_REQUEST['receiver_email'] == PAYPAL_ADDRESS ) # Check for correct email address
            {
                if( $_REQUEST['mc_currency'] == PAYPAL_CURRENCY ) # Check for correct currency
                {
                    // your site specific code here
                }
                else mail( PAYPAL_ATTEND, "Non standard currency used", $email );
            }
            else mail( PAYPAL_ATTEND, "Payment to wrong address", $email );
        }
        else mail( PAYPAL_ATTEND, "Non completed payment", $email );
    }
    else mail( PAYPAL_ATTEND, "Suspicious payment recieved", $email );

    mail( PAYPAL_ATTEND, "TEST", $email );
}
?>
 
IPN is such an important tool in any e-commerce, used it a few times but this looks like one of the best I've seen (unsurprisingly).
 
ah, yes, the wonderful IPN. i would have posted something like this sooner if i would have thought of it first. :p i am currently using IPN for a clients site, and it is great. nice script Joe.
 
Joe, this is fantastic, could I use it for a Paypal module in cP Creator?
Ofcourse credits will be given.
 
You certainly could, in it's current form, you'd need to invoice users for payment using your own code and this to handle the actual payment, although you could just as easily, read a little bit about paypal, make it do subscriptions and handle cancellations and refunds etc automatically...

Anything I write in a forum is public domain so you can do whatever you want with it, although if I helped in some way credit is always nice :)
 
What can I say, credit given when credit deserved.

I've already got a sandbox account on paypal which I'll read up upon and play about with, I'll take a look at the invoices aswell and see what I can do with those.
 
The paypal sandbox is the worst place in the world, up until recently I would use eliteweaver's ipntest script, only that seems to be offline which is crap because that only leaves the sandbox, and like I said there's no worse place to be, with all the logging in and out and timing in and out and oh ... my ... god ...
 
Question, if a subscription is completed, would the same code with handles the invoice completion work?
 
The same sort of idea, only subscriptions use different variables and there are more checks to do, there's a pdf on the paypal website documenting the variables and their usage, I'd read that, I don't really wanna confuse this post with that information, plus I'd just be copying and pasting.
 
The same sort of idea, only subscriptions use different variables and there are more checks to do, there's a pdf on the paypal website documenting the variables and their usage, I'd read that, I don't really wanna confuse this post with that information, plus I'd just be copying and pasting.
Thanks, that's fair enough. I used that PDF earlier for the buttons used and some return variables, custom values for security checks.

I'll check through it again Joe. Thanks.
 
Status
Not open for further replies.
Back
Top