1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Development {PHP} Dynamically generated Paypal buttons for digital downloads?

Discussion in 'Software' started by jezmck, 28 Jun 2008.

  1. jezmck

    jezmck Minimodder

    Joined:
    25 Sep 2003
    Posts:
    4,456
    Likes Received:
    36
    Has anyone done this before?
    I have a client waiting for me to do this, but am busy moving house at the moment, so it's difficult to get time to do it.

    I have a database table with the items I wish to create the buttons for, the table includes names, file paths and price.

    I don't want to use pre-built shops/carts as this would be complete overkill for this situation.

    All suggestions very much appreciated.
     
  2. Firehed

    Firehed Why not? I own a domain to match.

    Joined:
    15 Feb 2004
    Posts:
    12,574
    Likes Received:
    16
    Two-step process, really.

    First is easy - generating the buttons. Tweak this accordingly:
    PHP:
    $userid _UID;
    echo <<<PAYPAL
    <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
        <input type="hidden" name="cmd" value="_xclick">
        <input type="hidden" name="business" value="YOURBUSINESSEMAIL@GMAIL.COM">
        <input type="hidden" name="item_name" value="PRODUCT NAME">
        <input type="hidden" name="amount" value="PRICE (EX. 30.00)">
        <input type="hidden" name="shipping" value="0.00">
        <input type="hidden" name="no_shipping" value="0">
        <input type="hidden" name="no_note" value="1">
        <input type="hidden" name="currency_code" value="USD">
        <input type="hidden" name="lc" value="US">
        <input type="hidden" name="bn" value="PP-BuyNowBF">
        <input type="hidden" name="custom" value="
    $userid">
        <input type="image" src="https://www.paypal.com/en_US/i/btn/btn_buynowCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
        <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
    </form>
    PAYPAL;
    The custom field is the really useful one - use it to pass your user ID or whatever to PayPal, specifically so you get it back on the IPN ping.

    Second is a bit tricker - getting paypal to talk to your IPN processor which updates the DB stating that the item (download) has been paid for, and then allowing users to have download links to the items for which they've paid.

    Here's some sample code I'm using in a project, tweaked slightly from PayPal.
    PHP:
    <?php
    include '../defines.php';
    include 
    '../functions/functions.php';
    open_db();


    // read the post from PayPal system and add 'cmd'
    $req 'cmd=_notify-validate';

    foreach (
    $_POST as $key => $value)
    {
        
    $value urlencode(stripslashes($value));
        
    $req .= "&$key=$value";
    }

    // post back to PayPal system to validate
    $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.paypal.com'80$errno$errstr30);

    // assign posted variables to local variables
    $item_name $_POST['item_name'];
    $userid $_POST['custom']; //special field paypal allows for just this kind of thing
    $payment_status $_POST['payment_status'];
    $payment_amount $_POST['mc_gross'];
    $payment_currency $_POST['mc_currency'];
    $txn_id $_POST['txn_id'];
    $receiver_email $_POST['receiver_email'];
    $payer_email $_POST['payer_email'];

    if (!
    $fp) {
        
    // HTTP ERROR
        
    echo 'error!';
    } else {
        
    fputs ($fp$header $req);
        while (!
    feof($fp)) {

            
    $res fgets ($fp1024);
            if (
    strcmp ($res"VERIFIED") == 0)    {
            
                
    // check the payment_status is Completed
                // check that txn_id has not been previously processed
                // check that receiver_email is your Primary PayPal email
                // check that payment_amount/payment_currency are correct
                // process payment

    /*
     *
     * This is the area where you actually do the local processing of the payment - confirming it's updated, etc
     *
     */            

                
    $txn_id clean($txn_id);
                
    $payment_status clean($payment_status);
                
    $userid clean($userid);
                
    $payer_email clean($payer_email);
                
    $payment_amount clean($payment_amount);
                
                
    //add the payment into the transaction log 
                
    $query "INSERT INTO `payments` (`id`,`pp_txn_id`,`pp_status`,`user_id`,`payer_email`,`date`,`amount`) VALUES (NULL,'$txn_id','$payment_status','$userid','$payer_email',NOW(),'$payment_amount');";
                
    mysql_query($query); // db it

                //and update the user table to push out the paid_until value six months
                
    $query 'UPDATE `users` SET `paid_thru` = IF(`paid_thru` < NOW(), ADDDATE(NOW(), INTERVAL 6 MONTH), ADDDATE(`paid_thru`, INTERVAL 6 MONTH)) WHERE users.id = ' $userid ' LIMIT 1';
                
    mysql_query($query);
                
                
            }
            else if (
    strcmp ($res"INVALID") == 0)
            {
                
    // log for manual investigation
                
    echo 'invalid';
            }
        }
    fclose ($fp);
    mysql_close();
    }
    ?>
    (yes, I know my PayPal code is rather messy. It's theirs, and I never went through and made it pretty like I should have)

    The DB updates in step two are going to be the important part, since that will update your users table giving the user permission to get to the download. The exact execution of this will vary depending on how you're doing things, but hopefully that gets you conceptually in the right area. Also know that clean() is a crappy little function I wrote ages back to sanitize DB input. I've since replaced it with a cleanGPC() function that does the same thing to ALL Get/Post/Cookie variables; unfortunately I don't think that code made it home from the office :p
     
    Last edited: 28 Jun 2008
  3. jezmck

    jezmck Minimodder

    Joined:
    25 Sep 2003
    Posts:
    4,456
    Likes Received:
    36
    awesome, but for one thing, I don't have users in this system.

    actually, thinking about it, I could just send an encoded link to the $payer_email or something. (not the most secure, but we're only talking about a quid per track here, so not a major problem.)

    Thanks so much for spending the time giving me this - I owe you.
     
  4. Firehed

    Firehed Why not? I own a domain to match.

    Joined:
    15 Feb 2004
    Posts:
    12,574
    Likes Received:
    16
    Took two minutes to copy across, no problem at all.

    If you don't have users, maybe have the custom field be something like the file id and some sort of verification code (if it's even worth it), ie
    PHP:
    <input type="hidden" name="custom" value="$fileID?code=$verificationCode">
    Yes, you could use the mail() function to fire off a download link to the payer_email. I've got a Mail class you could use if I have it handy, so it would be a pretty darn simple task.
     
  5. Draxin

    Draxin Seeker of Photons

    Joined:
    29 Nov 2001
    Posts:
    965
    Likes Received:
    5
    sorry to jump off topic, People have said not to use the mail() function and to use a mail class instead.

    Im curiousas to why, I myself prefer procedural programing over OOP, so is it mearly a personal preference issue?
     
  6. jezmck

    jezmck Minimodder

    Joined:
    25 Sep 2003
    Posts:
    4,456
    Likes Received:
    36
    It's probably more about security and code-reuse (not reinventing the wheel).
     
  7. tominated

    tominated What's a Dremel?

    Joined:
    28 May 2008
    Posts:
    504
    Likes Received:
    6
    all i have to say is e-junkie.com

    they rock for digital downloads and paypal crap
     
  8. Rum&Coke

    Rum&Coke What's a Dremel?

    Joined:
    23 Apr 2007
    Posts:
    473
    Likes Received:
    14
    Sorry to jump even further off topic but I'm curious about this, its hardly an either/or option (usually)
     
  9. capnPedro

    capnPedro Hacker. Maker. Engineer.

    Joined:
    11 Apr 2007
    Posts:
    4,381
    Likes Received:
    241
    Have you lookec at the PayPal sandbox for developers yet?
     
  10. Firehed

    Firehed Why not? I own a domain to match.

    Joined:
    15 Feb 2004
    Posts:
    12,574
    Likes Received:
    16
    For mail(), it's not really that critical. As compared to procedural programming, classes are an easy way to organize your functions. But when you're using them properly, it provides incredible amount of code re-use without having to duplicate all of your efforts.

    Ex: social networking site. You're viewing someone else's profile, so there's some info from both of your profiles on the page (mutual friends, maybe). Rather than setting up two procedural sets of queries, database processing, etc, you just have, for example:
    PHP:
    <?php
    $viewer 
    = new User($_COOKIE['uid']);
    $viewed = new User($_GET['userid']);
    ?>
    Then you'd automatically have $viewer->friends, $viewer->name, $viewer->age, etc. If you want to dumb it down, it's basically a giant associative array that's automatically populated by the methods defined in your construction method. But that's really oversimplifying things a LOT. Here's a caching class I've developed:
    PHP:
    <?php
    class Cache {
        protected 
    $filePath;
        
        
    // ==================================================================================
        // = $filePath is 'viewName/permalink' resulting in ../Cache/viewName/permalink.txt =
        // ==================================================================================
        
    public function __construct($filePath) {
            
    $this->filePath _CacheFolder "/$filePath.txt";
            if (
    file_exists($this->filePath)) {
                
    $this->send();
                return 
    true//out of habit - useless, since send() ends with the exit function
            
    }
            else {
                return 
    $this->recordOutput();
            }
        } 
    // function __construct
        
        
    private function send() {
            
    $cachedVersion file_get_contents($this->filePath);
            
    //determine any headers if necessary
            /*
             * // Read from the if-modified-since header of the browser and 304 Not Modified accordingly?
             * // Last-modified: Tue, 15 Nov 1994 12:23:34 GMT
             * $lastModifiedDate = gmdate('D, d M Y H:i:s ', filemtime($this->filePath)) . 'GMT';
             * header('Last-modified: ' . $lastModifiedDate);
             * header('Content-length: ' . filesize($this->filePath)); // This could blow up if your filesize is over 2GB... but that had better not be an issue for a cache file!
            */
            
    echo $cachedVersion;
            exit;
        } 
    // function send 

        
    private function recordOutput() {
            return 
    ob_start();
        } 
    // function recordOutput

        
    public static function delete($filePath) {
            
    $path _CacheFolder "/$filePath.txt";
            if (
    unlink($path)) {
                return 
    true;
            }
            else {
                return 
    false;
            }
        } 
    // function delete
        
        
    public function save() {

            
    // Grab the contents of the output buffer (and clean said buffer)
            
    $output ob_get_clean();

            
    // Determine if the appropriate cache folder exists; if not, create it
            
    $pathInfo pathinfo($this->filePath);
            if (!
    file_exists($pathInfo['dirname'])) {
                
    mkdir($pathInfo['dirname'], 0744); // 0744 derived from trial and error, but it works.
            
    }
            
            
    // Write the page output to the cache textfile, and 
            // return same so we don't have to read the file just created
            
    if (file_put_contents($this->filePath$output)) {
                return 
    $output;
            }
            else {
                return 
    false;
            }
        } 
    // function save
        
        
        // ======================================================================
        // = This function could be... really ****ing dangerous if _CacheFolder =
        // = is set wrong!  Take appropriate care when using it.                =
        // ======================================================================
        
    public static function destroyEntireCache() {
            
    //$cacheFolder = opendir(_CacheFolder);
            
            //do stuff
            
        
    // function destroyEntireCache
    // class Cache
    Lots of crap written once. Then on the page, all I have to do is $cache=new Cache("$template/$permalink"); (vars are set elsewhere). If a cached version it exists, it sends it out and exists the program, done deal. If not, it starts recording things, and all that good stuff. Strictly speaking, I should put a call to save() in the currently undefined destructor method, but it's not done yet. So anyways, the net result of which is that I have one extremely portable class file and the entire thing can be called with one line of code.

    Classes also allowed me to write an entire plugin architecture in 30 lines of code. Trimming the variables down to uselessly obscure one-character things and stripping the comments showed me that it could fit in two tweets. Yes, under 280 characters. I could condense it further if I really cared to, though it would be at the expense of a pointless performance loss.

    At the end of the day use what you think is best. For smaller projects, building out things into classes doesn't make any sense at all. But for something hefty, your all_my_functions.php file is going to get out of control incredibly quickly, to the point of being useless.
     

Share This Page