Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PHP

Some Simple PHP Classes for Managing PayPal Instant Payment Notifications

4.88/5 (5 votes)
29 Oct 2014CPOL5 min read 40.4K  
A few classes for handling PayPal Instant Payment Notifications, to take care of the necessary processing on your end.

Updates

Introduction

This is a quick writing to share one method of managing PayPal Instant Payment Notifications (IPN) through PHP. Looking at several examples found on the web, I was struck by the spaghetti-ness of all the ones I saw - they seemed to be based on PayPal's example code, which was far from comprehensive. Also, they were mostly monolithic, and it was hard to understand all the necessary steps. But thanks to them I was able to figure it out, and the code in this article is the result. Hopefully, by sharing, others will be spared some of the difficulty I went through.

The classes below are also not comprehensive, as they only handle my requirement of single item purchases. A couple other features might also be lacking, but they work for me, and should provide a good beginning for those who wish to go further.

I approached handling IPNs by breaking the process into objects responsible for logical sub-tasks. That makes everything much easier to grok for those who are familiar with object-orientation. (And if you aren't, I hope this helps you see the benefits, and become more versed in the methodology. It will save you lots of work in your future.)

There are a couple other IPN payment approaches on CodeProject you may also want to check out. The ones I found are in C# and ASP.NET. As of July, 2013, AllanPolich, becker666, and Mat Stine's articles seem to be most relevant to this task. Feel free to point out any others I'm missed. (DaSpors linked an impressive PHP/JavaScript/SQL shopping cart solution in the comments that can handle much more than PayPal IPNs if you need additional payment options.)

The Solution

In order to not give away important details of my own setup, I am going to copy/paste my code below and change the appropriate information. This will make for a long article which seems to be a code dump, but I will also place some comments here to make the approach a little easier to understand. I believe the code is self-documenting, which may be some consolation, but I still apologize for the inconvenience, as there are seven files this will require you to copy and paste if you use them for your own project. If there is enough call for it in the future, I will take the time and create a zip of the files from the modified code, but right now I want to concentrate on getting back to other projects as quickly as possible.

My solution breaks the process into five objects:

  • payPalController - This class handles the logic of the non-IPN portion of the PayPal process. It checks for duplicate transactions sent by PayPal, and verifies that the product in the IPN transaction is actually one on my site. If so, it initiates adding the purchase to a database and sending an email to the customer.
  • payPalIpn - This is where the IPN processing occurs. Very important!
  • rmwStoreDB - This unit handles adding transactions to a database on my end.
  • myMailClass - A basic mailer which could be vastly extended to handle HTML emails better, but it provides basic functionality. It relies upon Swift Mailer under the hood, to make things much easier.
  • myLoggerClass - Logs errors and can send you an email when something goes wrong if such an action is specified in its setup.

In addition, there are two more non-class files which hold:

  1. the database setup information, and
  2. the email account information

To get this to work, you must perform three steps. First, you must set up a database, which I won't cover here. There is enough online to figure that process out if you haven't yet. Then you must copy the five class files and the two password files to a subdirectory underneath the lowest one visible to the outside world and modify them to your specifications (where 'above' is taken to mean those folders viewable by browsers). And finally, the webpage you specify for PayPal to send notifications to (on a button-by-button basis if you go that route) must look something like this:

HTML
<!doctype html public '-//W3C//DTD HTML 4.01//EN'  'http://www.w3.org/TR/html4/strict.dtd'>
<html>
<head>
<title>RandomMonkeyWorks</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta name="Generator" content="Notepad++">
<meta name="Description" content="PayPal IPN Processor">
<meta name="Keywords" content="Nothing">

</head>


<body>

<!-- The code to deal with the PalPal logic: -->
<?php

   //The following is for the directory below publicly visible,
   //where the processing stuff is:
   $base = dirname(dirname(dirname(__FILE__)));
   define('BASE_PATH', $base == DIRECTORY_SEPARATOR ? $base : $base . DIRECTORY_SEPARATOR);
   require_once (BASE_PATH . "phpPayPalController.php");
   
   $ppc = new payPalController();
   $ppc->setTesting(false);
   $ppc->setLogging(false);
   $ppc->processPayPalIpnPayment();
   ?>


   <!-- THINGS TO DO WHEN GOING FROM TESTING TO PRODUCTION: -->
   <!-- Change 'testing' to false via the '$ppc->setTesting' line above -->
   <!-- Make certain buttons are coded correctly on site to access this page -->
   <!-- Make certain the phpPayPalIpnClass opens socket to proper secured server -->
   
   </body>
   </html>

As you can see, the payPalController is the only unit the page needs to know about. The controller sets up the other classes itself:

PHP
<?php
//This should be placed in the subdirectory beneath the user-accessible
//one.  On localhost this can't be done, so remember to do it manually
//when copying to the site.

require_once(BASE_PATH . "phpPayPalIpnClass.php");
require_once(BASE_PATH . "phpLoggerClass.php");
require_once(BASE_PATH . "phpRmwStoreDbActions.php");
require_once(BASE_PATH . "phpMyMailClass.php");


class payPalController {
   private $db;
   private $logger;
   private $testing;


   function __construct() {
      $this->logger = new myLogger();
      $this->logger->setLogFile("log.txt");
      $this->logger->setLogging(false);
      $this->db = new rmwStoreDB();
      $this->db->setLogger($this->logger);
      $this->testing = false;
      }


   function setTesting($state) {
      $this->testing = $state;
      $this->logger->setLogging($state);
      }


   function setLogging($state) { $this->logger->setLogging($state); }


   function processPayPalIpnPayment() {
      $processor = new payPalIpn();
      $processor->setTesting($this->testing);
      $processor->setLogger($this->logger);
      $this->logger->log("Processing a payment." . PHP_EOL);

      if (!$this->validatePostData()) return;
      if (!$processor->processPost()) return;
      if ($this->duplicateTransaction()) return;
      if (!$this->verify()) return;
      if (!$this->addOrderToDatabase()) return;
      $this->sendProduct();
      }
      
      
   function validatePostData() {
      $step = 0;
      $ret = true;
      if(!isset($_POST['txn_id']))            $step = 1;
      if(!isset($_POST['shipping']))          $step = 2;
      if(!isset($_POST['quantity']))          $step = 3;
      if(!isset($_POST['mc_gross']))          $step = 4;
      if(!isset($_POST['mc_gross']))          $step = 5;
      if(!isset($_POST['last_name']))         $step = 6;
      if(!isset($_POST['first_name']))        $step = 7;
      if(!isset($_POST['address_zip']))       $step = 8;
      if(!isset($_POST['mc_currency']))       $step = 9;
      if(!isset($_POST['item_number']))       $step = 10;
      if(!isset($_POST['payer_email']))       $step = 11;
      if(!isset($_POST['address_city']))      $step = 12;
      if(!isset($_POST['address_name']))      $step = 13;
      if(!isset($_POST['address_state']))     $step = 14;
      if(!isset($_POST['address_street']))    $step = 15;
      if(!isset($_POST['receiver_email']))    $step = 16;
      if(!isset($_POST['payment_status']))    $step = 17;
      if(!isset($_POST['address_country']))   $step = 18;
      //if(!isset($_POST['option_selection1'])) $step = 19;
      
      if ($step != 0) $ret = false;

      if ($ret == false) {
         $this->logger->log("POST DATA not set: $step" . PHP_EOL);
         $response = "";
         
         foreach ($_POST as $key => $value) {
            $numPosts += 1;
            if($magicQuotesFuncExists == true && get_magic_quotes_gpc() == 1) {
               $value = urlencode(stripslashes($value));
               }
            else {
               $value = urlencode($value);
               }
            $response .= "&$key=$value" . PHP_EOL;
            }
         $this->logger->log($response);
         
         }
      return $ret;
      }


   private function duplicateTransaction() {
      $ret = false;
      if ($this->db->itemExists("orders", "payPalTransId",  $_POST['txn_id'])) {
         $this->logger->log("Transaction: " . $_POST['txn_id'] . " exists" . PHP_EOL);
         $ret = true;
         }

      else {
         $this->logger->log("Transaction: " . $_POST['txn_id'] .
                     " does not exist" . PHP_EOL);
         }
      return $ret;
      }


   private function verify() {
      $nl = PHP_EOL;
      //First, check for valid item number:
      if (!$this->db->itemExists("products", "id", $_POST ['item_number'])) {
         $this->logger->log("Item number: " . $_POST['item_number'] .
                     " doesn't exist in database$nl");
         return false;
         }
      else {
         $this->logger->log("Item number: " . $_POST['item_number'] .
                     " exists in database$nl");
         }
      //Check that we received the proper amount, or more, in the case of Texas taxes:
      $this->dbPrice = $this->db->getCellValue("price", "products", "id",
                  $_POST['item_number']);
      if ($_POST['mc_gross'] < $this->dbPrice) {
         $this->logger->log("Payment received (" . $_POST ['mc_gross'] .
                     ") less than item price. (" . $this->dbPrice . PHP_EOL);
         return false;
         }
      else {
         $this->logger->log("Adequate payment received (" . $_POST ['mc_gross'] .
                     ").$nl");
         }

      if ($_POST['mc_currency'] != "USD") {
         $this->logger->log("Paid in non-US funds - need to investigate.$nl");
         return false;
         }

      else {
         $this->logger->log("US Currency received - OK.$nl");
         }

      if ($_POST['receiver_email'] != "someone@somewhere.com"
                  && $_POST['receiver_email'] != "someone@somewhere.com") {
         $this->logger->log("Incorrect receiver email received (" .
                     $_POST['receiver_email'] . ")$nl");
         return false;
         }
      else {
         $this->logger->log("Correct email received (
                     " . $_POST['receiver_email'] . ")$nl");
         }

      //And the most important one:
      if ($_POST['payment_status'] != "Completed") {
         $this->logger->log("Payment incomplete from PayPal$nl");
         return false;
         }
      return true;
      }

   private function addOrderToDatabase() {
      //Everything will revolve around email address as of primary importance; if there is
      //one in the database the record will be updated to reflect any changes to the
      //account.  If one doesn't exist, one will be created.
      $this->logger->log("Updating database." . PHP_EOL);
      $this->db->addOrUpdateUser();
      $this->db->addOrder();
      return true;
      }


   private function sendProduct() {
      $nl = PHP_EOL;
      $mailHandler = new myMailer();
      $mailHandler->setLogger($this->logger);

      if ($this->testing) {
         $mailTo = 'someone@somewhere.com';
         }
      else {
         $mailTo = $_POST['payer_email'];
         }

      if ($_POST['item_number'] == "something") {
         doSomething(); //You get the idea...
         }
      }

   }
   
?>

In the above code block, for prettier formatting, I wrapped some lines which may need to be unwrapped when porting them to your site. They are probably fine as-is, but just in case, this is worth being aware of. Also, remember to change the email addresses used throughout this article.

That brings us to the payPalIpn class. As stated earlier, this handles the IPN processing, and sending the correct responses back to PayPal.

PHP
<?php

require_once(BASE_PATH . "phpLoggerClass.php");


class payPalIpn {

   private $logger;  //This will actually be a reference; see 'setLogger'
                     //for mechanism which accomplishes this.
   private $ipnVerifiedC;
   private $testingC;

   function __construct() {
      $this->ipnVerifiedC = false;
      $this->testingC = false;
      }


   function ipnVerified() { return $this->ipnVerifiedC; }


   function setTesting($state) { $this->testingC = $state; }


   function setLogger(myLogger &$logFile) { $this->logger = $logFile; }


   function processPost() {
      // Send an empty HTTP 200 OK response to acknowledge receipt of the notification 
      //header('HTTP/1.1 200 OK'); 
      $nl = PHP_EOL;
      //Log the contents if the logger is set to do so:
      $this->logger->log("RECEIVED:$nl" . var_export($_POST, true) . PHP_EOL);

      //Everything below here is originally from
      //http://designertuts.com/wp-content/uploads/2007/10/paypalipnphp.txt, which was said
      //to be a copy of the example code specified on the Paypal site.  Of course, I modified
      //formatting and necessary logic.  Quite a lot, actually.

      //Paypal POSTs HTML FORM variables to this page.  We must return all the variables
      //back to PayPal unchanged and add an extra parameter 'cmd 'with value
      //'_notify-validate'

      $response = 'cmd=_notify-validate';

      //Check for magic quotes (this comes from a PayPal pdf on IPN usage - don't remember
      //the URL):
      $magicQuotesFuncExists = false;
      if(function_exists('get_magic_quotes_gpc')) {
         $magicQuotesFuncExists = true;
         }

      //The following variable is used for a quick, dummy check to keep from sending a
      //response unless the POST was long, meaning it probably came from PayPal:
      $numPosts = 0;
      // go through each of the POSTed vars and add them to the variable
      foreach ($_POST as $key => $value) {
         $numPosts += 1;
         if($magicQuotesFuncExists == true && get_magic_quotes_gpc() == 1) {
            $value = urlencode(stripslashes($value));
            }
         else {
            $value = urlencode($value);
            }
         $response .= "&$key=$value";
         }
         
      $this->logger->log("AFTER MAGIC QUOTES:$nl".var_export($_POST, true).PHP_EOL);


      // post back to PayPal system to validate
      $header = "POST /cgi-bin/webscr HTTP/1.1$nl";
      $header .= "Host: www.sandbox.paypal.com$nl";
      $header .= "Content-Type: application/x-www-form-urlencoded$nl";
      $header .= "Content-Length: " . strlen($response) . PHP_EOL . PHP_EOL;

      //In a live application send it back to www.paypal.com, but during development use
      //the PayPal sandbox.  Paypal Sandbox only seems to accept using ssl connections,
      //and on port 443.

      if ($this->testingC) {
         $socket = fsockopen ('ssl://www.sandbox.paypal.com', 443, $socketErrNum,
                     $socketErrStr, 30);
         }
      else {
         $socket = fsockopen ('ssl://www.paypal.com', 443, $socketErrNum,
                     $socketErrStr, 30);
         }
      //Oldie:   $socket = fsockopen ('www.paypal.com', 80, $socketErrNum,
      //            $socketErrStr, 30);

      if ($socket) $this->logger->log("Socket successful$nl");
      else $this->logger->log("Socket failed!$nl");

      
      if (!$socket) {
         // HTTP ERROR Failed to connect.  Send me an email notification:
         $mail_Body = "Error from fsockopen:$nl" . $socketErrStr .
                     "$nl$nlOriginal PayPal Post Data (COULD BE BOGUS!)$nl$nl";
         foreach ($_POST as $key => $value) {
            $value = urlencode(stripslashes($value));
            $mail_Body .= "&$key=$value" . PHP_EOL;
            }
         mail($myEmail, "IPN Error Noficiation: Failed to connect to PayPal", $mail_Body,
                     "someone@somewhere.com");

         // The original code used "fwrite($fh, $socketErrStr)" for the following:
         $this->logger->log("Socket error: " . $socketErrStr);
         return;
         }

      //Now respond to PayPal's posting.  The previous 'if' statement returned
      //and terminated this if there was a socket error, so we can concentrate on what we
      //need.  First, send our response to the POST, and check for verification:

      $receivedVerification = false;
      $this->logger->log("Number of posts: $numPosts$nl");

      //Only send a response if it comes from PayPal.
      //The quick and dirty way to do so is see how many posts were in the original message:
      if ($numPosts > 3) {
         //Actually send the response:
         $this->logger->log("Sending the post back:$nl");
         fputs ($socket, $header . $response);

         $this->logger->log("SENT:$nl$nl$header$response$nl$nl");

         //And get their response.  First lines will be transaction data, finally followed by
         //'VERIFIED' or 'UNVERIFIED' (or something).  It took a
         //little work to find out that SSL had to be used for the Sandbox, and to get it
         //working correctly.  But the previous '$socket' initialization finally
         //succeeded.
         $receivedVerification = false;
         $endOfStreamReached = false;
         
         while (!feof($socket) && !$endOfStreamReached && !$receivedVerification) {
            $result = fgets ($socket, 1024);  //Get a line of response
            $this->logger->log("RECEIVED: $result");
            
            if (strncmp ($result, "VERIFIED", 8) == 0) {
               $receivedVerification = true;
               //$this->logger->log("VERIFIED! VERIFIED! VERIFIED!$nl");
               }
            //else {
            //   $this->logger->log("NOT VERIFIED HERE!$nl");
            //   }
             if (strncmp ($result, "0", 1) == 0) $endOfStreamReached = true;
            }
         }
      fclose ($socket);

      $result = false;
      if ($receivedVerification == false) {
         $this->logger->log(
            "$nl$nlINVALID TRANSACTION! (Improper PayPal response received)$nl");
         }
      else {
         $this->ipnVerifiedC = true;
         $this->logger->log("TRANSACTION VERIFIED!$nl");
         $result = true;
         }

      return $result;
      }

   }
   ?>

Next comes the database processing. The db_config.php file in the first line stores the database user and password information. It is critical that db_config.php is not visible to the outside world, which is why you must place it below the topmost public folder on your site.

PHP
<?php

require_once(BASE_PATH . "pp_db_config.php");
require_once(BASE_PATH . "phpLoggerClass.php");


class rmwStoreDB {

   private $loggerC; //This will actually be a reference;
                     //see 'setloggerC' for mechanism which accomplishes this.
   private $lastRowC;

   function setlogger(mylogger $logFile) { $this->loggerC = $logFile; }

   
   function tellDb($sql) {
      $ret = mysql_query($sql);
      if (!$ret) {
         $this->loggerC->log("DATABASE ERROR:" . PHP_EOL . mysql_error() . PHP_EOL .
                     "Query: " . HtmlEntities($sql));
         die();
         }
      return $ret;
      }

      
   function itemExists($table, $column, $value) {
      $rows = $this->tellDb("Select * from " . $table . " where " . $column . " = '" .
                  $value . "'");
      $lastRowC = mysql_fetch_array($rows);
      if ($lastRowC) return true;
      return false;
      }


   function getCellValue($what, $table, $column, $theId) {
      //No checking will be done to ensure only one row is returned, and default value
      //will be '0.00.'
      $rows = $this->tellDb("Select " . $what . " from " . $table . " where " . $column .
                  " = " . $theId);
      $row = mysql_fetch_array($rows);
      if ($row) return $row['price'];
      else return 0.00;
      }


   function addOrUpdateUser() {
      //We are using the $_POST data, which is global to the application.

      //These are the fields we are dealing with: 'first_name'  'last_name' 
      //'payer_email', and, for shipped products ONLY - 'address_name'
      //'address_street' (can be 2 lines separated by PHP_EOL?)  'address_city' 
      //'address_state'  'address_zip'  'address_country_code'  'address_country'

      //First, see if the user exists:
      $rows = $this->tellDb("Select * from customers where email = '" .
                  $_POST['payer_email'] . "'");
      //Simply use the first of the returned rows:
      $row = mysql_fetch_array($rows);
      if (!$row) {
         $this->loggerC->log("Adding user to database");
         $this->addUser();
         }
      else {
         $this->loggerC->log("User already exists in DB." . PHP_EOL);
         //See if the records match the ones we have, and if not, change it:
         $this->updateUser($row);
         }
      }


   private function addUser() {
      $cmd = "Insert into customers (firstName, lastName, shippingName, email," . 
               "addressLine1, city, state, zipCode, country) values ('"
               . $_POST['first_name'] .  "', '" .
               $_POST['last_name'] . "', '" .
               $_POST['address_name'] ."', '" .
               $_POST['payer_email'] . "', '" .
               $_POST['address_street'] . "', '" .
               $_POST['address_city'] . "', '" .
               $_POST['address_state'] . "', '" .
               $_POST['address_zip'] . "', '" .
               $_POST['address_country'] . "')";

      $this->tellDb($cmd);

      $this->loggerC->log("Added: '" . $_POST['first_name'] . "', '" .
               $_POST['last_name'] . "', '" .
               $_POST['address_name'] . "', '" .
               $_POST['payer_email'] . "', '" .
               $_POST['address_street'] . "', '" .
               $_POST['address_city'] . "', '" .
               $_POST['address_state'] . "', '" .
               $_POST['address_zip'] . "', '" .
               $_POST['address_country'] . "')");
      }


   private function updateUser(array $row) {
      //First, check old values:
      if ($row['firstName']    != $_POST['first_name']     ||
          $row['lastName']     != $_POST['last_name']      ||
          $row['shippingName'] != $_POST['address_name']   ||
          $row['email']        != $_POST['payer_email']    ||
          $row['addressLine1'] != $_POST['address_street'] ||
          $row['city']         != $_POST['address_city']   ||
          $row['state']        != $_POST['address_state']  ||
          $row['zipCode']      != $_POST['address_zip']    ||
          $row['country']      != $_POST['address_country']) {

         //Form a command string:
         $cmd = "UPDATE customers SET ";
         $cmd .= "firstName = '"    . $_POST['first_name']      . "', ";
         $cmd .= "lastName = '"     . $_POST['last_name']       . "', ";
         $cmd .= "shippingName = '" . $_POST['address_name']    . "', ";
         $cmd .= "addressLine1 = '" . $_POST['address_street']  . "', ";
         $cmd .= "city = '"         . $_POST['address_city']    . "', ";
         $cmd .= "state = '"        . $_POST['address_state']   . "', ";
         $cmd .= "zipCode = '"      . $_POST['address_zip']     . "', ";
         $cmd .= "country = '"      . $_POST['address_country'] . "' ";
         $cmd .= "WHERE email = '"  . $_POST['payer_email'] . "'";
         $this->loggerC->log(PHP_EOL . "Changing user with email " .
                     $_POST['payer_email'] . PHP_EOL);

         $old = $row['firstName']   . ", " . $row['lastName'] . ", " .
               $row['shippingName'] . ", " . $row['email']    . ", " .
               $row['addressLine1'] . ", " . $row['city']     . ", " . 
               $row['state']        . ", " . $row['zipCode']  . ", " .
               $row['country'] . PHP_EOL . PHP_EOL;

         $this->loggerC->log($old);
         $this->loggerC->log($cmd . PHP_EOL);

         $this->tellDb($cmd);
         }
      }


   function addOrder() {
      $nl = PHP_EOL;
      //Everything will be in the $_POST values
      //First, get the customer number:
      $cmd = "Select id from customers where email = '" . $_POST['payer_email'] . "'";
      //We just entered this information into the database, so error checking isn't
      //really needed? We will put something in just for grins.
      $rows = $this->tellDb($cmd);
      $row = mysql_fetch_array($rows);
      if (!$row) {
         $this->loggerC->log("HUGE PROBLEM! CUSTOMER ID NOT FOUND - ABORTING$nl");
         die();
         }
      $id = $row['id'];
      $theDate = date('F j, Y, g:i a');
      $tz = date('T');
      $ppID = $_POST['txn_id'];
      $grossPay = $_POST['mc_gross'];
      $shipping = $_POST['shipping'];
      $cmd = "Insert into orders (customer, date, timeZone, payPalTransId, grossPmt, " .
            "shipping) values ('$id', '$theDate', '$tz', '$ppID', '$grossPay', '$shipping')";

      $this->tellDb($cmd);
      $this->loggerC->log("Inserting order into orders table:$nl$cmd$nl$nl");

      //Now we have to add the order items into the orderItems table.
      //First, we need to get the order number from the record which was just entered:

      $cmd = "Select id from orders where payPalTransId = '$ppID'";
      $rows = $this->tellDb($cmd);
      $row = mysql_fetch_array($rows);
      //Should not need this, but...
      if (!$row) {
         $this->loggerC->log("HUGE PROBLEM! ORDER ID NOT FOUND - ABORTING$nl");
         die();
         }

      $id = $row['id'];
      //And the command to enter the item:
      $itemNum = $_POST['item_number'];
      $qty = $_POST['quantity'];
      $info = $_POST['option_selection1'];
      $cmd = "Insert into orderItems (orderNumber, item, quantity, extraInfo) " .
                  "values('$id', '$itemNum', '$qty', '$info')";
      $this->loggerC->log("Inserting into order items:$nl$cmd$nl");
      $this->tellDb($cmd);
      }

   }

?>

Yay! The following classes are much smaller than the previous! The first of them is the mailer unit. Of course, its brevity may have to do with the fact it could use more fleshing out, and I should be sending better-formatted HTML responses to purchases than I currently do. All in good time.

PHP
<?php

require_once(BASE_PATH . "pp_hiddenPasswords.php");
require_once(BASE_PATH . "phpLoggerClass.php");
require_once(BASE_PATH . "lib/swift_required.php");

class myMailer {

   private $myEmail;
   private $logger;  //This will actually be a reference;
                     //see 'setLogger' for mechanism which accomplishes this.
   
   
   function __construct() {
      $this->myEmail = "someone@somewhere.com";
      }
      
      
   function setLogger(myLogger &$logFile) { $this->logger = $logFile; }


   function sendEbook($ebookType, $mailTo) {
      //You can either send a link to the product or a file.  The choice is yours.
      //Refer to swift mailer documentation, and other online resources for the appropriate
      //steps to take for each option.  If you want to send a file, the 'mailWithAttachments'
      //routine may be useful.  The usage of it is:
      ...
      $this->mailWithAttachment($fileName, BASE_PATH, $mailTo, $this->myEmail, $from,
                  $replyTo, $subject, $msg);
      }

   
   private function mailWithAttachment($filename, $path, $mailTo, $from_mail, $from_name,
               $replyto, $subject, $message) {
   
      $transport = Swift_SmtpTransport::newInstance('mail.some_server.com', 465, 'ssl')
         ->setUsername(hiddenEmailAccount())
         ->setPassword(hiddenEmailPassword())
         ;
      $mailer = Swift_Mailer::newInstance($transport);
      $message = Swift_Message::newInstance()
         ->setSubject($subject)
         ->setFrom(array($from_mail => $from_name))
         ->setTo(array($mailTo))
         ->setBody($message)
         //->addPart('<p>Here is the message itself</p>', 'text/html')
         ->attach(Swift_Attachment::fromPath($path.$filename))
         ;
      $this->logger->forceLog(date('F jS\, Y h:i:s A') . ": Sending " . $filename .
                  " to " . $mailTo . ": ");
      $result = $mailer->send($message);
      $this->logger->forceLog("Result = " . $result . PHP_EOL);
      
      }
   }
?>

And lastly comes the logger class:

PHP
<?php
   
class myLogger {

   private $fileNameC;
   private $doLoggingC;
   
   function __construct() {
      $this->fileNameC = "log.txt";
      $this->doLoggingC = true;
      }
   
   function setLogFile($fileName) { $this->fileNameC = $fileName; }
   function setLogging($state) { $this->doLoggingC = $state; }
   
   function log($msg) {
      if ($this->doLoggingC == true) {
         file_put_contents($this->fileNameC, $msg, FILE_APPEND);
         }
      }   
   
   function forceLog($msg) {
      file_put_contents($this->fileNameC, $msg, FILE_APPEND);
      }
      
   }
   
   ?>

But there are still two more files necessary, although you could combine them into one if you wanted, with the appropriate modifications above. They are for storing the database information and the email information. Again, it is imperative these are not exposed to the outside world, and must be placed below the topmost visible folder on your site.

The contents of the db_config.php file are as follows. Except those aren't real users, passwords, and databases!

PHP
<?php
// Connection Parameters

$db_con = mysql_connect("localhost", "theUser", "thePassword", true) or die(mysql_error());
$db_selected = mysql_select_db("theDatabase") or die(mysql_error());

?>

And finally, the email password file ("emailPassword.php" in the above code), with the same caveat:

PHP
<?php
// Connection Parameters

function hiddenEmailAccount() {
   return "someone@somewhere.com";
   }

function hiddenEmailPassword() {
   return "thePassword";
   }

?>

I hope the previous information is useful if you go this route, and maybe even if you don't. Thank you for reading, and I send you wishes for happy coding! If you come up with any improvements, please post them in the comments.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)