PHP PayPal IPN Script

To use this script, you need to create the database table it'll write to. You can cut & paste this definition into your MySQL front-end. It's designed to work with the PayPal structure, but could be used as a starting point for other systems too:

/*------------------------------------*/
/* Sales - Order received via Paypal  */
/*------------------------------------*/
CREATE TABLE sales (
  invoice          INT UNSIGNED AUTO_INCREMENT,
  receiver_email   VARCHAR(60),
  item_name        VARCHAR(100),
  item_number      VARCHAR(10),
  quantity         VARCHAR(6),
  payment_status   VARCHAR(10),
  pending_reason   VARCHAR(10),
  payment_date     VARCHAR(20),
  mc_gross         VARCHAR(20),
  mc_fee           VARCHAR(20),
  tax              VARCHAR(20),
  mc_currency      VARCHAR(3),
  txn_id           VARCHAR(20),
  txn_type         VARCHAR(10),
  first_name       VARCHAR(30),
  last_name        VARCHAR(40),
  address_street   VARCHAR(50),
  address_city     VARCHAR(30),
  address_state    VARCHAR(30),
  address_zip      VARCHAR(20),
  address_country  VARCHAR(30),
  address_status   VARCHAR(10),
  payer_email      VARCHAR(60),
  payer_status     VARCHAR(10),
  payment_type     VARCHAR(10),
  notify_version   VARCHAR(10),
  verify_sign      VARCHAR(10),
  referrer_id      VARCHAR(10),
  PRIMARY KEY (invoice)
  );

Line By Line: ipn.php

This program works as described in the chapter on Automation. It is important to get it working perfectly - you don't want to lose orders. Having said that, it's not rocket science, as you'll see most of it's quite straightforward...

Note, the program comments are included as a major part of the explanation!

  • Program Start.
    The construct: 'gmstrftime(time())' formats the system time into something readable.

    <?php
    //------------------------------------------------------------------
    // Open log file (in append mode) and write the current time into it.
    // Open the DB Connection. Open the actual database.
    //-------------------------------------------------------------------
    $log = fopen("ipn.log", "a");
    fwrite($log, "\n\nipn - " . gmstrftime ("%b %d %Y %H:%M:%S", time()) . "\n");
    $db = mysql_connect("localhost", "yourname", "yourpassword");
    mysql_select_db("yourdb",$db);


  • Create a reply to validate the PayPal post. (Standard PayPal Code)
    This bit of code first creates a new array ($postvars) containing all of the values posted from PayPal.
    Then it begins a reply message ($req), with 'cmd=_notify-validate'
    Using a 'for' loop, it adds a list of posted vars in the format 'variable=value .

    //------------------------------------------------
    // Read post from PayPal system and create reply
    // starting with: 'cmd=_notify-validate'...
    // then repeating all values sent - VALIDATION.
    //------------------------------------------------
    $postvars = array();
    while (list ($key, $value) = each ($HTTP_POST_VARS)) {
    $postvars[] = $key;
    }
    $req = 'cmd=_notify-validate';
    for ($var = 0; $var < count ($postvars); $var++) {
    $postvar_key = $postvars[$var];
    $postvar_value = $$postvars[$var];
    $req .= "&" . $postvar_key . "=" . urlencode ($postvar_value);
    }

    INFO: Note the values are urlencoded - this is the format in which data is transmitted in a post from a web page.

    The function 'urlencode':
       "Returns a string in which all non-alphanumeric characters except -_. have been replaced with a percent (%) sign followed by two hex digits and spaces encoded as plus (+) signs" (from the PHP manual).

    This is basically a method of transmitting an 8-bit character set in just 7-bits.


  • Create an HTTP header for the reply message, open a connection...

    //--------------------------------------------
    // Create message to post back to PayPal...
    // Open a socket to the PayPal server...
    //--------------------------------------------
    $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, $errstr, 30);


  • Write the transaction details to the log file.
    The log file is useful for debugging problems. You'll ideally never lose any details, and if the database is down for any reason, you need a backup..

    //---------------------------------------------
    fwrite($log, "Vals: ". $invoice." ". $receiver_email." ". $item_name." ". $item_ number." ". $quantity." ". $payment_status." ". $pending_reason." ".$payment_dat e." ". $payment_gross." ". $payment_fee." ". $txn_id." ". $txn_type." ". $first_ name." ". $last_name." ". $address_street." ". $address_city." ". $address_state . " ".$address_zip." ". $address_country." ". $address_status." ". $payer_email. " ". $payer_status." ". $payment_type." ". $notify_version." ". $verify_sign. "\ n");


  • Check Connection...
    The variables $errstr and $errno are system variables which report details on the last error.

    //----------------------------------------------------------------------
    // Check HTTP connection made to PayPal OK, If not, print an error msg
    //----------------------------------------------------------------------
    if (!$fp) {
    echo "$errstr ($errno)";
    fwrite($log, "Failed to open HTTP connection!");
    $res = "FAILED";
    }


  • Final Verification
    At last, the string of posted variables ($req) is sent to PayPal's server. When it receives it - the server gives a 'VERIFIED' response if the transaction was real and successful.

    //--------------------------------------------------------
    // If connected OK, write the posted values back, then...
    //--------------------------------------------------------
    else {
    fputs ($fp, $header . $req);
    //-------------------------------------------
    // ...read the results of the verification...
    // If VERIFIED = continue to process the TX...
    //-------------------------------------------
    while (!feof($fp)) {
    $res = fgets ($fp, 1024);
    if (strcmp ($res, "VERIFIED") == 0) {


  • If payment is complete, get the password from the database.
    If the payment status is 'Completed'... The payment wasn't by e-check. The transaction's complete.

    Using the 'while' construct to get the data into a local variable is just a convenience... We know that only 1 row will come back from the query.

    //----------------------------------------------------------------------
    // If the payment_status=Completed... Get the password for the product
    // from the DB and email it to the customer.
    //----------------------------------------------------------------------
    if (strcmp ($payment_status, "Completed") == 0) {
    $qry = "SELECT password FROM products WHERE pid = \"$item_number\" ";
    $result = mysql_query($qry,$db);
    while ($myrow = mysql_fetch_row($result)) { $passwd = $myrow[0]; }


  • Create message. Send the email
    The message is created in the variable '$message' then sent using the PHP 'mail' function.
    Note the sender and reply-to info goes at the end.

    $message .= "Dear Customer,\n Thankyou for your order.\n\nThe password f or the item you ordered is: $row[0]\n\nIf you have any problems, please contact us: \n\nsales\@yourdomain.com";

    mail($payer_email, "Your Book Password...", $message, "From: ipn@yourdomain.com\nReply-To: sales@yourdomain.com");
    }


  • Handle Incomplete Transactions
    Inform the customer, and also inform yourself that something needs doing.

    //----------------------------------------------------------------------
    // If the payment_status is NOT Completed... You'll have to send the
    // password later, by hand, when the funds clear...
    //----------------------------------------------------------------------
    else {
    $message .= "Dear Customer,\n Thankyou for your order.\n\nThe password for the item you ordered will be sent to you when the funds have cleared.\n\nThankyou \n\nsales\@yourdomain.com";

    mail($payer_email, "Your Book Password...", $message, "From: ipn@yourdomain.com\nReply-To: sales@yourdomain.com");

    mail($receiver_email, "Incomplete PayPal TX...", "An incomplete transaction requires your attention.");
    }


  • Deal with 'Unverified' transactions
    If, for example, the transaction you just processed didn't originate with PayPal, but was an attempted hack - it would cause this error to occur. An invalid transaction needs human intervention.

    //----------------------------------------------------------------
    // ..If UNVerified - It's 'Suspicious' and needs investigating!
    // Send an email to yourself so you investigate it.
    //----------------------------------------------------------------
    else {
    mail($payer_email, "An Error Occurred...", "Dear Customer,\n an error occurred while PayPal was processing your order. It will be investigated by a human at the earliest opportunity.\n\nWe apologise for any inconvenience.", "From: ipn@yourdomain.com\nReply-To: sales@yourdomain.com");

    mail($receiver_email, "Invalid PayPal TX...", "An invalid transaction requires your attention.");

    }
    }
    }


  • Insert Details Into DB
    The '\"' is placed around each of the variable to ensure that they appear in double quotes in the query string. This ensures that whatever they contain, they'll get inserted.

    //--------------------------------------
    // Insert Transaction details into DB.
    //--------------------------------------
    $qry = "INSERT into psales (
    invoice, receiver_email, item_name, item_number, quantity, payment_status, pendi ng_reason, payment_date, payment_gross, payment_fee, txn_id, txn_type, first_nam e, last_name, address_street, address_city, address_state, address_zip, address_ country, address_status, payer_email, payer_status, payment_type, notify_version , verify_sign )
    VALUES
    ( \"$invoice\", \"$receiver_email\", \"$item_name\", \"$item_number\", \"$quanti ty\", \"$payment_status\", \"$pending_reason\", \"$payment_date\", \"$payment_gr oss\", \"$payment_fee\", \"$txn_id\", \"$txn_type\", \"$first_name\", \"$last_na me\", \"$address_street\", \"$address_city\", \"$address_state\", \"$address_zip \", \"$address_country\", \"$address_status\", \"$payer_email\", \"$payer_status \", \"$payment_type\", \"$notify_version\", \"$verify_sign\" ) ";

    $result = mysql_query($qry,$db);


  • Close up.
    //-------------------------------------------
    // Close PayPal Connection, Log File and DB.
    //-------------------------------------------
    fclose ($fp);
    fclose ($log);
    mysql_close($db);
    ?>




Warning: Cannot modify header information - headers already sent by (output started at /home/sites/web-bureau.com/public_html/modules/free-php-paypal-ipn-script.php:7) in /home/sites/web-bureau.com/public_html/footer.php on line 12