Link to home
Start Free TrialLog in
Avatar of OldHatt45
OldHatt45

asked on

PayPal question about email buttons & IPN

I have been looking at the EE messages about PayPal processing via Web Payments Standard & IPN.  I found one message thread (ID23908729 answered by Ray_Paseur and bportlock) from 2008 but I'm not sure if it will work for me.  

Here is what I need.

I'm reworking a web site where students register for classes via a web form written in php.  When they submit their registration, they get an email confirming their registration.  Another email goes to the Class Registration Secretary letting them know that someone has registered for a class.  This part is already done.  With all the information recorded in a mySQL database.

When the Registration Secretary logs into the web site and goes to their admin page, they see the class roster.  They see the students that have registered for each class.  Each student initially has a "submitted" status.

What I need is the following:
1. a.  If the Registration Secretary approves the registration, they change the Student's status to "approved".  The Registration Secretary then hits the "Update Student" button.  At this point, I will send an email to the student.  (I know how to do the above.)  What I don't know is that in the email I want to include a PayPal button, so the student can click on it and pay for the class.  I think I know how to create the email button (looked at the PayPal Web Standard Integration Guide page 62.  I'm not sure of the syntax to use for some of the variables, like amount, Class Name, etc.
1.b. When the Student pays for the class via the button in the email, PayPal should send a notification to my web site (I think using IPN but not sure).  If PayPal uses IPN, then I think I need some kind of listener, but I don't know how to implement this, unless I provide a php script/page via one of the URL's that I think IPN will respond to or maybe provided by the button from the email.
1.c. When I get the information from PayPal, I assume I can kick off a php script that will update the database and change the student status to "confirmed" and send an email to the registrar.  
1.d. I also make the assumption that I need to keep track of the PayPal transactions as well.

What I need is to figure out the actual transaction flow, how to setup the button, and if I am right in 1.b. some help with the listener.  Last, I would guess I need to figure out what I need for the table containing the PayPal transactions.
Avatar of darren-w-
darren-w-
Flag of United Kingdom of Great Britain and Northern Ireland image

here somthing for 1b:

https://github.com/Quixotix/PHP-PayPal-IPN

This does all the IPN listening work for you, when the notification is verified you do a alter on the db to say it is paid, and send a email
Here is a teaching example of a PayPal IPN.  It's rather old, and not very well-written code, but it may give you some ideas about how this interface works.

The IPN is started by a POST-method request from PayPal to the IPN script on your server.  As such, it has no session information nor any browser output.  You will see in this script that it uses output buffering and email to send the error messages, if any.

I think instead of sending a PayPal button in the email message, I might send a link to the web page that contained the "Pay Now" button.  My sense is that this would be easier to debug if you did not have the email component involved in the payment processing.

If the IPN script is part of your web site, item 1c can be completed in the IPN itself.

For further detailed help on this topic, you might look to the PayPal Developers' Network.
https://www.x.com/developers/paypal

Best of luck with it, ~Ray

<?php // RAY_paypal_ipn.php - CUSTOM PayPal IPN PROCESSING


// LOCAL FUNCTIONS AND VARIABLES, DB STUFF, ETC. - NO SESSION HERE - FUNCTION warning_RAY() SENDS AN EMAIL MESSAGE
require_once('common.php');


// READ THE POST FROM PayPal AND ADD 'cmd'
$req      = 'cmd=_notify-validate';
$postdata = '';
foreach ($_POST as $key => $value)
{
    $postdata .= PHP_EOL . " $key = $value ";      // SAVE THE COLLECTION
    $$key     = trim(stripslashes($value));        // ASSIGN LOCAL VARIABLES
    $value    = urlencode(stripslashes($value));   // ENCODE FOR BOUNCE-BACK
    $req      .= "&$key=$value";                   // APPEND TO VERIFICATION STRING
}

// SET THE HEADERS FOR THE CONFIRMATION POST BACK TO PayPal
$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";

// OPEN THE HTTP PIPE FOR THE POST BACK TO PayPal
$fp = fsockopen ('www.paypal.com', 80, $errno, $errstr, 30);

// TEST FOR SUCCESSFUL OPENNING OF THE HTTP PIPE
if (!$fp) // HTTP ERROR
{
    warning_RAY("IPN HTTP ERROR", "FSOCKOPEN FAILED \n\n ERRNO=$errno \n\n ERRSTR=$errstr \n\n");
    die();
}

// WITH HTTP OPEN - WRITE HEADER AND REQUEST
fputs ($fp, $header . $req);

// WITH HTTP OPEN - READ PayPal RESPONSE
$paypal_reply   = '';
$paypal_headers = '';
while (!feof($fp))
{
    $paypal_reply    = fgets($fp, 1024);
    $paypal_headers .= $paypal_reply;
}
fclose ($fp);

// IF THIS IS TRULY A POST FROM PAYPAL, PROCESS ORDER NOTIFICATION
if ($paypal_reply == "VERIFIED")
{
    $errormsg = "";

    // IF PAYMENT IS NOT COMPLETED (MAY BE E-CHECK?)
    if ($payment_status != "Completed") { $errormsg .= "\nE: payment_status"; }

    // IF PAYMENT WAS NOT SENT TO ME?
    $receiver_email = strtolower($receiver_email);
    if ($receiver_email == "me@my.org") { } else { $errormsg .= "\nE: receiver_email"; } // ??? SET EMAIL ADDRESS

    // I AM NOT CHECKING SOME THINGS BECAUSE WE ARE USING ENCRYPTED OR STORED BUY-NOW BUTTONS
    if ($mc_currency != 'USD') { $errormsg .= "\nE: mc_currency"; }

    // CHECK FOR TXN_ID ALREADY PROCESSED - NORMAL FOR E-CHECK
    $sql = "SELECT txn_id FROM PAYPAL_ORDER_LOG WHERE txn_id = \"$txn_id\" ";
    if (!$result = mysql_query($sql, $db_connection)) { fatal_query_error($sql); }
    $num_rows = mysql_num_rows($result);
    if ($num_rows  > 0) { $errormsg .= "\nE: Transaction id $txn_id already processed $num_rows time(s)"; }

    // LOG THE TRANSACTION
    $order_date  = date('Y-m-d\TH:i:s');
    $item_number = mysql_real_escape_string($item_number, $db_connection);
    $mc_gross    = mysql_real_escape_string($mc_gross,    $db_connection);
    $address_zip = mysql_real_escape_string($address_zip, $db_connection);
    $txn_id      = mysql_real_escape_string($txn_id,      $db_connection);
    $receipt_id  = mysql_real_escape_string($receipt_id,  $db_connection);
    $last_name   = mysql_real_escape_string($last_name,   $db_connection);
    $payer_email = mysql_real_escape_string($payer_email, $db_connection);
    $postdata    = mysql_real_escape_string($postdata,    $db_connection);
    $sql = "INSERT INTO PAYPAL_ORDER_LOG (    order_date,      item_number,      mc_gross,      address_zip,      txn_id,      receipt_id,      last_name,      payer_email,      postdata  )
            VALUES                       ( \"$order_date\", \"$item_number\", \"$mc_gross\", \"$address_zip\", \"$txn_id\", \"$receipt_id\", \"$last_name\", \"$payer_email\", \"$postdata\")";
    if (!$result = mysql_query($sql)) { fatal_query_error($sql); }

    // ISSUE A MESSAGE TO THE HOME OFFICE ?
    warning_RAY("IPN VERIFIED", "IPN REPLY $paypal_headers \n\n$errormsg \n\nPOST DATA FOLLOWS: $postdata \n\n");

    // GENERATE ARRAYS OF PURCHASE DATA FROM THE WEIRD PAYPAL VARIABLE NAMES
    // NOTE: NO POSITION ZERO IN THESE ARRAYS
    $w_item_number = array();
    $w_quantity    = array();
    $w_mc_gross    = array();
    $w_item_name   = array();

    // IF TRANSACTION TYPE IS CART, MAY BE MULTIPLE ITEMS IN DIFFERENT QUANTITIES - LOAD ARRAYS
    // NOTE NOTE NOTE NO POSITION ZERO IN THESE ARRAYS
    if ($txn_type == "cart")
    {
        while ($num_cart_items > 0)
        {
            $proxy = "item_number" . "$num_cart_items";
            $w_item_number[$num_cart_items] = $$proxy;

            $proxy = "quantity" . "$num_cart_items";
            $w_quantity[$num_cart_items] = $$proxy;

            $proxy = "mc_gross_" . "$num_cart_items";
            $w_mc_gross[$num_cart_items] = $$proxy;

            $proxy = "item_name" . "$num_cart_items";
            $w_item_name[$num_cart_items] = $$proxy;

            $num_cart_items--;
        }
    }
    else
    {
        // NOT A CART - SINGLETON ITEM ONLY - NORMALIZE INTO ARRAY FOR USE WITH ITERATOR
        // NOTE NOTE NOTE NO POSITION ZERO IN THESE ARRAYS
        $w_item_number[1] = $item_number;
        $w_quantity[1]    = $quantity;
        $w_mc_gross[1]    = $mc_gross;
        $w_item_name[1]   = $item_name;
    }

// *****************************************************
// ACTIVATE THIS CODE BLOCK TO SEE WHAT IS IN THE ORDER ARRAYS
//
//    ob_start();
//    echo "<pre>\n";
//    echo "\nW_ITEM_NUMBER"; var_dump($w_item_number);
//    echo "\nW_QUANTITY";    var_dump($w_quantity);
//    echo "\nW_MC_GROSS";    var_dump($w_mc_gross);
//    echo "\nW_ITEM_NAME";   var_dump($w_item_name);
//    $foo = ob_get_contents();
//    ob_end_clean();
//    warning_RAY("IPN VARDUMPS", "$foo \n\n");
// *****************************************************


    // ITERATE OVER THE ARRAYS
    $kount = 0; // NO POSITION ZERO IN THESE ARRAYS
    while ($kount < count($w_item_number))
    {
        $kount++; // BUMP BEFORE WORKING
        $my_item_number = $w_item_number[$kount];
        $my_quantity    = $w_quantity[$kount];
        $my_mc_gross    = $w_mc_gross[$kount];
        $my_item_name   = $w_item_name[$kount];

        //
        // ??? PROCESS THE ORDERS USING YOUR BUSINESS LOGIC
        //

    } // END ITERATION OVER THE ORDERS

    // END OF NORMAL PAYPAL IPN PROCESSING
    die();
}

// NOT NORMAL PROCESSING
// LOG INVALID POSTS FOR MANUAL INVESTIGATION AND INTERVENTION
if ($paypal_reply == "INVALID")
{
    warning_RAY("IPN INVALID", "IPN REPLY $paypal_headers \n\n$errormsg \n\nPOST DATA FOLLOWS: $postdata \n\n");
    die();
}

// OTHERWISE, PayPal RETURNED BAD DATA (OR INTERNET HTTP ERRORS OR TIMEOUT)
warning_RAY("IPN REPLY UNKNOWN", "IPN REPLY $paypal_headers \n\n$errormsg \n\nPOST DATA FOLLOWS: $postdata \n\n");
die();

Open in new window

Avatar of OldHatt45
OldHatt45

ASKER

Ray,
I was hoping you would respond to this question.  THANK YOU!
If at all possible, I need to have the button in the email.  (My users are not terribly bright whn it comes to computer stuff.  LOL)
Could you tell me if I have the process flow correct??
1. Button in email (with variables)
2. Student clicks button and goes to PayPal.
3. Student pays for class.
4. PayPal sends IPN message.
5. My web site listener picks up request and verifies transaction.
6. My web site verifies transaction with and to PayPal via IPN.
7. My web site sends email to student and Registrar and updates student status.

Thanks
OldHatt45
darren-w

I have looked over your IPN Listener.
It looks Good.  After I go through it in more detail, I may have a question or 2 about it.
I appreciate your time and effort.

Thanks,
OldHatt45
Np, its a great class, gives you all the information you need if debugging.
darren-w
I really appreciate it.
Also, I did not mean to slight you by asking Ray about the process flow.
If you have any thoughts on it, please feel free to comment.

Thanks again,
OldHatt45
Let's rewrite this part a little bit...

4. PayPal sends IPN message.
5. My web site listener picks up request and verifies transaction.
6. My web site verifies transaction with and to PayPal via IPN.
7. My web site sends email to student and Registrar and updates student status.

Here is how I would characterize it.

4. Upon receipt of a payment, PayPal starts the IPN script with a POST-method request.  The IPN script is the same as your web site "listener."
5. The IPN script does whatever verification and data base logging that makes sense for your business needs.
6. The IPN script sends email to student and registrar.

To the question of email or web page, I don't discourage putting the button into an email message.  But I think while you are setting things up, it would be easier to debug the button in a web page.  You can do things like "view source" in a web page, and you don't have to keep sending yourself email messages.  Once you have the button working in the web page, moving that code to create the button in the email message will be very simple.
Ray,

Thanks for the clarification!
What you suggest makes sense.
In your recharacterization, I am making the assumption that when PayPal receives the payment, it automatically kicks off the IPN process.  Is that a correct assumption?
Also, in the PayPal docs, you are supposed to provide URL's for the different transaction outcomes (Success, Failure, and Cancellation).  I would assume that with the listener, that all 3 of these outcomes should have the same URL.  Or am I missing something?
Regarding the web page button vs the email button.  The format of the 2 according to the PayPal documentation is different.  So (again making a possibly bad assumption.  LOL) I assume that the variables I need/want to use in the button are the only things that are really transportable between email and web page.  Is that correct?

Thanks Again,
OldHatt45
when PayPal receives the payment, it automatically kicks off the IPN process.
Yes.  You have to tell PayPal the URL of your IPN script, of course.
provide URL's for the different transaction outcomes (Success, Failure, and Cancellation)
No, these different URLs would be the web page (script) that your web site will show to the client after an attempt to pay.  You might have additional logging or statistical work in these scripts, or you might just show a happy / sad face.  When the client clicks "pay now" (whether on the web page or in the email) PayPal gets control via a POST-method request.  When PayPal is finished, it will direct the browser to one of these pages.

Not sure about the format of the different buttons.  If you want to post a link to the PayPal docs that show this, I'll be glad to take a look.
ASKER CERTIFIED SOLUTION
Avatar of darren-w-
darren-w-
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
OK, stupid question.  On line 25 of Ray's code above, it appears to use HTTP.  Would it still work if I used HTTPS via SSL????

Here is the code example from the Website Payments Standard Integration Guide (page 64) for the email button:

Sample URL for a Buy Now Email Payment Link
https://www.paypal.com/cgibin/
webscr?cmd=_xclick&business=herschelgomez@xyzzyu.com&item_name=Hot
Sauce-12+oz.+Bottle&item_number=12345&amount=5%2e95&currency_code=USD

According to the guide, on page 63;
"Use the same variables and values that you include in HTML code for Buy Now buttons in
Buy Now email payment links. Use ampersands (&) to separate the variables and their values
from each other. Do not enclose values in quotation marks. Use plus signs (+) in place of
spaces within values."

I assume that this means that any variable I can use on a web page, I can use in an email button?

Last, I think that the code from both of you guys is EXCELLENT and I can tailor it to my specific needs.

Thanks,
OldHatt45
Would it still work if I used HTTPS via SSL????
Probably -- it may even be required now for new accounts.
I assume that this means that any variable I can use on a web page, I can use in an email button?
Yes, it sure sounds that way!
OK, I'm going to start putting the Code together.
It will probably take me a day to do.  (Sorry, I'm Old and Slow).
Will post this evening on progress, but I intend to split the points between Ray & Darren and Open a new question if I run into trouble.
If I open one or more new questions, I will post a link here to any new questions, if that is alright with you guys.

I want to be FAIR to both of you guys because you have Truly Been Absolutely Excellent in helping me, so, that said, I propose an even split, unless you guys want to decide on the split.

THANKS AGAIN!!!!
OldHatt45
Split it any way that makes sense - I have enough points to orbit Saturn, so I am not too worried about the split ;-)

Best of luck with your project, and thanks for using EE, ~Ray
Ray
In going through the example you provided, there is a require statement for a "common.php".  Can you tell me what is in that file?????

Thanks,
Oldhatt45
Sure: Stuff like the definitions of local classes and functions (may not be any used here), data base connectivity, etc.
Gentlemen,  Thank You for the Help.
I think I am on my way with this, but will likely have a couple more questions.
Thanks Again,
OldHatt45