Hello!
I don't travel here much as im mainly a part of the Developer section for Garry's Mod.
Today i need some help with my PayPal IPN, as it's been broken for about three months due to an update to the IPN.
Im not fluent in PHP so i wouldn't know where to start with this asides from where the IPN file(s) are located.
Could any of you help me figure this out?
ipn_order_model.php
[code]
<?php
/**
* Ipn_order_model
*
* @package codeigniter-paypal-ipn
* @author Alexander Dean, alex@keplarllp.com
* @version SVN: $Id: Builder.php 7490 2010-03-29 19:53:27Z jwage $
*
* This file is copyright (c) 2011 Alexander Dean, github@keplarllp.com
*
* This file is part of codeigniter-paypal-ipn
*
* codeigniter-paypal-ipn is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* codeigniter-paypal-ipn is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with
* codeigniter-paypal-ipn. If not, see <http://www.gnu.org/licenses/>.
*/
class Ipn_order_model extends CI_Model
{
// Define the order and order items table names
const ORDER_TABLE = 'ipn_orders';
const ORDER_ITEM_TABLE = 'ipn_order_items';
// Standard constructor
function __construct()
{
parent::__construct();
}
// Save the order, performing an insert or update as appropriate
function saveOrder($order, $orderItems, $transactionID)
{
// Define the transaction query once
$orderQuery = array('txn_id' => $transactionID);
// Check the transaction ID to see if the order needs an insert or update
$existingOrder = $this->db->get_where(self::ORDER_TABLE, $orderQuery, 1, 0);
// Set the update/insert date
$upsertTime = date('Y-m-d H:i:s');
$order['updated_at'] = $upsertTime; // All inserts/updates set the updated_at time
// If it exists, get the ID and do an update
if ($existingOrder->num_rows() > 0)
{
$orderID = $existingOrder->row()->id; // Grab the ID
$this->db->update(self::ORDER_TABLE, $order, $orderQuery); // Do the update
}
// Else do an insert and then get the ID
else
{
$order['created_at'] = $upsertTime; // A new order needs a created_at time too
$this->db->insert(self::ORDER_TABLE, $order);
$orderID = $this->db->insert_id();
}
// Now let's save the order's line items
foreach ($orderItems as $item)
{
// Define the order item query
$orderItemQuery = array('item_name' => $item['item_name'],
'order_id' => $orderID);
// Add the order ID and datestamp into the item to update/insert
$item['order_id'] = $orderID;
$item['updated_at'] = $upsertTime;
// Now try to retrieve the order item
$existingOrderItem = $this->db->get_where(self::ORDER_ITEM_TABLE, $orderItemQuery, 1, 0);
// If the order item exists, update
if ($existingOrderItem->num_rows() > 0)
{
$this->db->update(self::ORDER_ITEM_TABLE, $item, $orderItemQuery);
}
// Else insert the order item
else
{
$item['created_at'] = $upsertTime; // Insert needs a created_at time as well
$this->db->insert(self::ORDER_ITEM_TABLE, $item);
}
}
}
}
[/code]
ipn_log_model.php
[code]
<?php
/**
* Ipn_log_model
*
* @package codeigniter-paypal-ipn
* @author Alexander Dean, alex@keplarllp.com
* @version SVN: $Id: Builder.php 7490 2010-03-29 19:53:27Z jwage $
*
* This file is copyright (c) 2011 Alexander Dean, github@keplarllp.com
*
* This file is part of codeigniter-paypal-ipn
*
* codeigniter-paypal-ipn is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* codeigniter-paypal-ipn is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with
* codeigniter-paypal-ipn. If not, see <http://www.gnu.org/licenses/>.
*/
class Ipn_log_model extends CI_Model
{
// Holds the query for the cache
private $cacheQuery = array();
// Table name is a constant
const LOG_TABLE = 'ipn_log';
// Standard constructor
function __construct()
{
parent::__construct();
// Define once how we access the cache record
$this->cacheQuery = array('listener_name' => 'IPN',
'transaction_type' => 'cache');
}
// Retrieve the IPN cache row from the IPN log (if there is one)
function getCache()
{
$query = $this->db->get_where(self::LOG_TABLE, $this->cacheQuery);
return $query->row();
}
// Retrieve an IPN log record as identified by its IPN data hash
function getOneByHash($hash)
{
$query = $this->db->get_where(self::LOG_TABLE, array('ipn_data_hash' => $hash));
return $query->row();
}
// Store the IPN cache row in the IPN log (updating the existing cache record if there is one)
function saveCache($ipnDataRaw)
{
// Define the cache record to populate
$cacheRecord = $this->cacheQuery;
$cacheRecord['detail'] = serialize($ipnDataRaw);
// Now run the insert/update
$this->_upsert($cacheRecord, $this->cacheQuery);
}
// Store a row in the IPN log
function saveLog($logRecord, $logID)
{
// Define the query to determine whether to update or insert
$logQuery = (is_null($logID)) ? null : array('id' => $logID);
// Now run the insert/update, returning the ID
return $this->_upsert($logRecord, $logQuery);
}
// Helper function to insert or update depending on whether the relevant row exists already
function _upsert($record, $query = null)
{
// Check if the record exists already, using the supplied query
$exists = FALSE;
if (!is_null($query))
{
$existingRecord = $this->db->get_where(self::LOG_TABLE, $query, 1, 0);
if ($existingRecord->num_rows() > 0)
{
$exists = TRUE;
}
}
// Set the update/insert date
$upsertTime = date('Y-m-d H:i:s');
$record['updated_at'] = $upsertTime; // All inserts/updates set the updated_at time
// If it exists, then update
$retVal = null;
if ($exists)
{
$this->db->update(self::LOG_TABLE, $record, $query);
}
// Otherwise let's insert
else
{
$record['created_at'] = $upsertTime; // All inserts need a created_at time too
$this->db->insert(self::LOG_TABLE, $record);
$retVal = $this->db->insert_id();
}
return $retVal; // Return the ID for an insert or null for an update
}
}
[/code]
PayPal_IPN.php library
[code]
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* CodeIgniter
*
* An open source application development framework for PHP 4.3.2 or newer
*
* @package CodeIgniter
* @author ExpressionEngine Dev Team
* @copyright Copyright (c) 2006, EllisLab, Inc.
* @license http://codeigniter.com/user_guide/license.html
* @link http://codeigniter.com
* @since Version 1.0
* @filesource
*/
/**
* PayPal IPN Library
*
* A CodeIgniter library to act as a listener for the PayPal Instant Payment Notification
* (IPN) interface. Uses CodeIgniter active record
*
* DISCLAIMER: The author Alex Dean does not accept any liability for any processing errors made by
* this library, or any financial losses incurred through its use. In particular, this library
* does NOT fulfil the PayPal IPN requirement to "verify that the payment amount actually
* matches what you intend to charge. Although not technically an IPN issue, if you do
* not encrypt buttons, it is possible for someone to capture the original transmission
* and change the price. Without this check, you could accept a lesser payment than what
* you expected." (This verification step is out of scope for this library because it
* would require integration with your product catalogue.)
*
* This library focuses on the "post-payment" workflow, i.e. the processing required once
* the payment has been made and PayPal has posted an Instant Payment Notification call to
* the IPN controller.
*
* This library handles:
* - Validating the IPN call
* - Extracting the order and line item information from the IPN call
* - Interpreting PayPal's payment status
* - Storing the order and line item in the database
*
* Dependencies:
* - None
*
* All pre-payment functionality (e.g. posting the checkout information to PayPal) and custom
* post-payment workflow (e.g. sending emails) is left as an exercise to the reader.
*
* This library is inspired by:
* - Ran Aroussi's PayPal_Lib for CodeIgniter, http://aroussi.com/ci/
* - Micah Carrick's Paypal PHP class, http://www.micahcarrick.com
*
* For usage, please see the README.markdown file
*
* @package CodeIgniter
* @subpackage Libraries
* @category Commerce
* @author Alex Dean <alex@keplarllp.com>
* @link https://github.com/orderly/codeigniter-paypal-ipn
* @copyright Copyright (c) 2011 Alex Dean
* @version 0.2
*/
class PayPal_IPN
{
private $_ci; // CodeIgniter instance
public $ipnData = array(); // Contains the POST values for IPN
public $order = array(); // Contains the fields pertaining to an order
public $orderItems = array(); // Contains the order items within an order
public $orderStatus; // All-important variable: whether the order has been paid for yet or not
// Configuration constants
private $isLive; // The flag used to indicate we are operating in the live environment
private $ipnURL; // The PayPal IPN URL we're using
public $merchantEmail; // The merchant's email address connected to PayPal
// Used for logging
private $logID; // The ID of our IpnLog record
private $transactionID; // The transaction ID aka txn_id (from PayPal)
private $transactionType; // The type of transaction (from PayPal)
// Payment status constants we use, more user-friendly than the PayPal ones
const PAID = 'PAID';
const WAITING = 'WAITING';
const REJECTED = 'REJECTED';
// Allowed order fields. No point making this static as PHP arrays can always be modified later on anyway.
private $orderFields = array('notify_version', 'verify_sign', 'test_ipn', 'protection_eligibility', 'charset', 'btn_id', 'address_city', 'address_country',
'address_country_code', 'address_name', 'address_state', 'address_status', 'address_street', 'address_zip', 'first_name',
'last_name', 'payer_business_name', 'payer_email', 'payer_id', 'payer_status', 'contact_phone', 'residence_country',
'business', 'receiver_email', 'receiver_id', 'custom', 'invoice', 'memo', 'tax', 'auth_id', 'auth_exp', 'auth_amount', 'auth_status',
'num_cart_items', 'parent_txn_id', 'payment_date', 'payment_status', 'payment_type', 'pending_reason', 'reason_code',
'remaining_settle', 'shipping_method', 'shipping', 'transaction_entity', 'txn_id', 'txn_type', 'exchange_rate',
'mc_currency', 'mc_fee', 'mc_gross', 'mc_handling', 'mc_shipping', 'payment_fee', 'payment_gross', 'settle_amount',
'settle_currency', 'auction_buyer_id', 'auction_closing_date', 'auction_multi_item', 'for_auction', 'subscr_date',
'subscr_effective', 'period1', 'period2', 'period3', 'amount1', 'amount2', 'amount3', 'mc_amount1', 'mc_amount2',
'mc_amount3', 'recurring', 'reattempt', 'retry_at', 'recur_times', 'username', 'password', 'subscr_id', 'case_id',
'case_type', 'case_creation_date', 'order_status', 'discount', 'shipping_discount', 'ipn_track_id', 'transaction_subject');
// The constructor. Loads the helpers and configuration files, sets the configuration constants
function __construct()
{
$this->_ci =& get_instance();
$this->_ci->load->config('paypal_ipn'); // The custom configuration file for this library
// Configuration var
$this->isLive = $this->_ci->config->item('paypal_ipn_use_live_settings');
// Settings
$settings = $this->_ci->config->item('paypal_ipn_' . (($this->isLive) ? "live" : "sandbox") . '_settings');
$this->ipnURL = $settings['url'];
$this->merchantEmail = $settings['email'];
$this->debug = $settings['debug'];
// We load the two models here in the constructor because they are used throughout the library
$this->_ci->load->model('Ipn_log_model', 'IPNLog', TRUE);
$this->_ci->load->model('Ipn_order_model', 'IPNOrder', TRUE);
}
// The key functionality in this library. Extracts the fields from the IPN notification and then
// sends them back to PayPal to make sure that the post is not bogus.
// We also carry out the following validations as per PayPal's guidelines (https://cms.paypal.com/cgi-bin/marketingweb?cmd=_render-content&content_ID=developer/e_howto_admin_IPNIntro):
// - "Verify that you are the intended recipient of the IPN message by checking the email address in the message;
// this handles a situation where another merchant could accidentally or intentionally attempt to use your listener."
// - "Avoid duplicate IPN messages. Check that you have not already processed the transaction identified by the transaction
// ID returned in the IPN message. You may need to store transaction IDs returned by IPN messages in a file or database so
// that you can check for duplicates. If the transaction ID sent by PayPal is a duplicate, you should not process it again."
// Actually PayPal is wrong on this last one - duplicate transaction IDs are fine, PayPal can send multiple different
// messages with the same transaction ID. That's why we store the md5 instead.
public function validateIPN()
{
// Set these all to null
$this->logID = null;
$this->transactionID = null;
$this->transactionType = null;
// First check that we have post data.
$usingCache = FALSE;
if (!empty($_POST))
{
$ipnDataRaw = $_POST;
// If we're in debugging mode, then we cache this POST data in the log for potential use later
if ($this->debug)
{
$this->_cacheIPN($ipnDataRaw);
}
}
else
{
// If we're in debug mode, let's try to get the last cached IPN data instead
if ($this->debug)
{
if ($ipnDataRaw = $this->_getCachedIPN())
{
$usingCache = TRUE;
}
else
{
$this->_logTransaction('IPN', 'ERROR', 'No POST data and no cached IPN data');
return FALSE;
}
}
else // Not in debug mode
{
$this->_logTransaction('IPN', 'ERROR', 'No POST data'); // Nulls because we don't have a transaction type or ID yet
return FALSE;
}
}
// First thing we do is to log this transaction. This way if the script silently fails, there is still a record of the IPN call.
// (If the script succeeds, or another error occurs, then this error message will be overwritten).
$this->_logTransaction('IPN', 'ERROR', 'Script failed silently or is still processing, detail field contains the serialized data from PayPal', serialize($ipnDataRaw));
// Before doing anything else, let's clean up our post data.
foreach (array_keys($ipnDataRaw) as $field)
{
if (!$usingCache)
{
$value = $this->_ci->input->post($field); // Note that CodeIgniter standardises line returns to \n
// Put line feeds back to \r\n for PayPal otherwise multi-line data will be rejected as INVALID
$ipnDataRaw[$field] = str_replace('\n', '\r\n', $value);
}
// Let's also store this in this class, turning empty strings back to null to avoid breaking Doctrine later
$this->ipnData[$field] = ($ipnDataRaw[$field] == '') ? null : $ipnDataRaw[$field];
}
// Let's now set the transaction type and transaction ID, we'll use these for logging.
$this->transactionID = isset($this->ipnData['txn_id']) ? $this->ipnData['txn_id'] : null;
$this->transactionType = isset($this->ipnData['txn_type']) ? $this->ipnData['txn_type'] : null;
// TODO: which txn_types do we support? Only really web_accept and cart. Sometimes refunds have null txn_type (but we don't really support refunds)
// Now we need to check that we haven't received this message from PayPal before.
// Luckily we store an md5 hash of each IPN dataset so we can cross-check whether this one is new.
$ipnDataHash = md5(serialize($ipnDataRaw));
if (!$usingCache && $this->_checkForDuplicates($ipnDataHash))
{
$this->_logTransaction('IPN', 'ERROR', 'This is a duplicate call: md5 hash ' . $ipnDataHash . ' already logged');
return FALSE;
}
// Now we need to ask PayPal to tell us if it sent this notification
$ipnResponse = $this->_postData($this->ipnURL, array_merge(
array('cmd' => '_notify-validate'),
$ipnDataRaw
));
if ($ipnResponse === FALSE) // Bail out if we have an error.
{
return FALSE;
}
// Check that PayPal says that the IPN call we received is not invalid
if (stristr('INVALID', $ipnResponse))
{
// Invalid IPN transaction. Check the log for details.
$this->_logTransaction('IPN', 'ERROR', 'PayPal rejected the IPN call as invalid - potentially call was spoofed or was not checked within 30s', $ipnResponse);
return FALSE;
}
// The IPN transaction is a genuine one - now we need to validate its contents.
// First we check that the receiver email matches our email address.
if ($this->ipnData['receiver_email'] != $this->merchantEmail)
{
$this->_logTransaction('IPN', 'ERROR', 'Receiver email ' . $this->ipnData['receiver_email'] . ' does not match merchant\'s', $ipnResponse);
return FALSE;
}
// Now we check that PayPal and this listener agree on whether this is a test or not
$testIPN = (isset($this->ipnData['test_ipn']) && $this->ipnData['test_ipn'] == 1);
if ($testIPN == $this->isLive)
{
$this->_logTransaction('IPN', 'ERROR', 'Listener\'s environment does not match test_ipn flag ' . $this->ipnData['test_ipn'], $ipnResponse);
return FALSE;
}
// The final check is of the payment status. We need to surface this
// as a class variable so that the calling code can decide how to respond.
//
// PayPal has various different payment statuses' - we list them below,
// but for simplicity we generalise these into three categories:
// - PAID - meaning the merchant can now dispatch the order
// - WAITING - wait for another update from PayPal with this transaction_id
// - REJECTED - meaning that payment failed and the order should be rejected
// The following page was invaluable in understanding the different PayPal payment
// statuses: http://www.coderprofile.com/networks/discussion-forum/1305/paypal-help-ipn-payment_status
//
// We throw an error if the payment_status code is unrecognised.
switch ($this->ipnData['payment_status'])
{
// TODO: what about 'Canceled_Reversal' status? Maybe need to work with parent_txn_id
// TODO: 'Reversed' doesn't really affect the order - it's a new row which relates to a refund
case 'Completed': // Order has been paid for
$this->orderStatus = self::PAID;
break;
case 'Pending': // Payment is still waiting to go through
case 'Processed': // Mostly used to indicate that a cheque has been received and is currently going through the verification process
$this->orderStatus = self::WAITING;
break;
case 'Voided': // Bounced or cancelled check
case 'Expired': // Credit card company didn't recognise card
case 'Reversed': // Credit card holder has got the credit card co to reverse the charge
$this->orderStatus = self::REJECTED;
break;
default:
$this->_logTransaction('IPN', 'ERROR', 'Payment status of ' . $this->ipnData['payment_status'] . ' is not recognised', $ipnResponse);
return FALSE;
}
// Phew! We have a valid IPN transaction, log it.
$this->_logTransaction('IPN', 'SUCCESS', 'Parsing, authentication and validation complete', $ipnResponse);
return true;
}
// Function to extract the order and order items from the $ipnData
public function extractOrder()
{
// First extract the actual order record itself
foreach ($this->ipnData as $key=>$value)
{
// Only add the field if it's in our whitelist.
// We use a whitelist because PayPal adds new fields to the IPN system
// at random/without warning, and if we don't have a whitelist then
// the database insert/update will then break
if (in_array($key, $this->orderFields))
{
$this->order[$key] = $value;
}
}
// Let's store the payment status too
$this->order['order_status'] = $this->orderStatus;
// Now retrieve the line items which belong to this order
$hasCart = ($this->order['txn_type'] == 'cart');
$numItems = $hasCart ? (int)$this->order['num_cart_items'] : 1;
// Initialise the total before a discount
$totalBeforeDiscount = 0;
// Loop through each order item (for non-cart there will only be one item)
for ($i = 0; $i < $numItems; $i++)
{
// Suffixes are different depending on whether there are multiple items (a cart) or not
$suffix = $hasCart ? ($i + 1) : '';
$suffixUnderscore = $hasCart ? '_' . $suffix : $suffix;
// Now build the order item
$this->orderItems[$i] = array();
// The following are always set
$this->orderItems[$i]['item_name'] = $this->ipnData['item_name' . $suffix];
$this->orderItems[$i]['item_number'] = $this->ipnData['item_number' . $suffix];
$this->orderItems[$i]['quantity'] = $this->ipnData['quantity' . $suffix];
$this->orderItems[$i]['mc_gross'] = $this->ipnData['mc_gross' . $suffixUnderscore]; // Note the trailing underscore. PayPal oddity
$qty = $this->ipnData['quantity' . $suffix];
if($qty == 0)
$qty = 1;
$this->orderItems[$i]['cost_per_item'] = floatval($this->ipnData['mc_gross' . $suffixUnderscore]) / intval($qty); // Should be fine because quantity can never be 0
// Update the total before the discount was applied
$totalBeforeDiscount += $this->orderItems[$i]['mc_gross'];
// The following are not always set on each item, so need to be careful
$this->orderItems[$i]['mc_handling'] = isset($this->ipnData['mc_handling' . $suffix]) ? $this->ipnData['mc_handling' . $suffix] : null;
$this->orderItems[$i]['mc_shipping'] = isset($this->ipnData['mc_shipping' . $suffix]) ? $this->ipnData['mc_shipping' . $suffix] : null;
$this->orderItems[$i]['tax'] = isset($this->ipnData['tax' . $suffix]) ? $this->ipnData['tax' . $suffix] : null;
// Set the order item options if any
// $count = 7 because PayPal allows you to set a maximum of 7 options per item
// Reference: https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_html_Appx_websitestandard_htmlvariables
for ($ii = 1, $count = 7; $ii < $count; $ii++)
{
$this->orderItems[$i]['option_name_'.$ii] = isset($this->ipnData['option_name'.$ii.'_'.$suffix]) ? $this->ipnData['option_name'.$ii.'_'.$suffix] : null;
$this->orderItems[$i]['option_selection_'.$ii] = isset($this->ipnData['option_selection'.$ii.'_'.$suffix]) ? $this->ipnData['option_selection'.$ii.'_'.$suffix] : null;
}
}
// And calculate the discount, as it's useful to add this into emails etc
$this->order['discount'] = $totalBeforeDiscount - $this->order['mc_gross'];
}
// Ugly function to deal with the fact that a lot of people don't have curl or similar setup.
// If you do have curl setup in PHP, and @philsturgeon's cURL library for CodeIgniter installed,
// you can replace the contents of this function with:
// $this->_ci->load->library('curl');
// $response = $this->_ci->curl->simple_post($url, $postData);
// // Error logging
// return $response;
function _postData($url, $postData)
{
// Put the postData into a string
$postString = '';
foreach ($postData as $field=>$value)
{
$postString .= $field . '=' . urlencode(stripslashes($value)) . '&'; // Trailing & at end of post string is forgivable
}
$parsedURL = parse_url($url);
// fsockopen is a bit odd - it just takes the host without the scheme - unless you want to use
// ssl, in which case you need to use ssl://, not http://
$ipnURL = ($parsedURL['scheme'] == "https") ? "ssl://" . $parsedURL['host'] : $parsedURL['host'];
// Likewise, if using ssl, then need to change port from 80 to 443
$ipnPort = ($parsedURL['scheme'] == "https") ? 443 : 80;
$fp = fsockopen($ipnURL, $ipnPort, $errorNumber, $errorString, 30);
// Log and return if we have an error
if (!$fp)
{
$this->_logTransaction("IPN", "ERROR", "fsockopen error number $errorNumber: $errorString connecting to " . $parsedURL['host'] . " on port " . $ipnPort);
return FALSE;
}
fputs($fp, "POST $parsedURL[path] HTTP/1.1\r\n");
fputs($fp, "Host: $parsedURL[host]\r\n");
fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
fputs($fp, "Content-length: " . strlen($postString) . "\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $postString . "\r\n\r\n");
$response = '';
while (!feof($fp))
{
$response .= fgets($fp, 1024);
}
fclose($fp); // Close connection
if (strlen($response) == 0)
{
$this->_logTransaction("IPN", "ERROR", "Response from PayPal was empty");
return FALSE;
}
return $response;
}
/* Code below this point is all ORM-specific. In this version, it uses the standard CodeIgniter ActiveRecord pattern */
// Save an IPN record (insert/update depending on if there is an existing row or not)
// CI ActiveRecord version
function _cacheIPN($ipnDataRaw)
{
// Save the cache
$this->_ci->IPNLog->saveCache($ipnDataRaw);
}
// Retrieve the cached IPN record if there is one, false if there isn't
// CI ActiveRecord version
function _getCachedIPN()
{
// Save the cache
if (!($cache = $this->_ci->IPNLog->getCache()))
{
return FALSE;
}
else
{
return unserialize($cache->detail);
}
}
// Check for a duplicate IPN call using the md5 hash
// CI ActiveRecord version
function _checkForDuplicates($hash)
{
// Return the IPN record if we can find one
return $this->_ci->IPNLog->getOneByHash($hash);
}
// Function to persist the order and the order items to the database.
// Note is that an order may already exist in the system, and this IPN
// call is just to update the record - e.g. changing its payment status.
// CI ActiveRecord version
public function saveOrder()
{
// Save the order
$this->_ci->IPNOrder->saveOrder($this->order, $this->orderItems, $this->ipnData['txn_id']);
}
// The transaction logger. Currently tracks:
// - Successful and failed calls by the PayPal IPN
// - Successful and failed calls by one or more third-party APIs
// - (In sandbox mode) Caches the last transaction fields so we can run the IPN script directly
// CI ActiveRecord version
function _logTransaction($listenerName, $transactionStatus, $transactionMessage, $ipnResponse = null)
{
// Populate the IPN log record
$ipnLog = array();
$ipnLog['listener_name'] = $listenerName;
$ipnLog['transaction_type'] = $this->transactionType;
$ipnLog['transaction_id'] = $this->transactionID;
$ipnLog['status'] = $transactionStatus;
$ipnLog['message'] = $transactionMessage;
$ipnLog['ipn_data_hash'] = md5(serialize($this->ipnData));
// We also log the ipnResponse and the ipnData if we have an error and/or are in the sandbox (test) environment.
if ($transactionStatus == 'ERROR' || !$this->isLive)
{
$detailJSON = array();
$detailJSON['ipn_data'] = $this->ipnData;
$detailJSON['ipn_response'] = $ipnResponse;
$ipnLog['detail'] = json_encode($detailJSON);
} else {
$ipnLog['detail'] = null; // Turn it back to null (as it might have been set previously)
}
// Finally save down the log record.
$insertID = $this->_ci->IPNLog->saveLog($ipnLog, $this->logID);
// Save the log ID for next time if we need to
if (!is_null($insertID))
{
$this->logID = $insertID;
}
}
}[/code]
any help would be appreciated, if i even have the right files here.
You could start by telling us what's wrong with it.
my specific issue is that confirmation of the donation never returns to the webserver.
Have you considered contacting the author/viewing their github repo for updated files? As far as I am aware the only recent change to the IPN which may effect compatibility is quoted below (ripped from a PP newsletter e-mail).
[QUOTE=Paypal]Starting the 7th of October, 2013, we will require all incoming requests to have a “Host” header which complies with HTTP 1.1 Specifications. This header was not required under HTTP 1.0. IPN and PDT scripts using HTTP 1.0 may start failing with “HTTP/1.0 400 Bad Request” errors after the 7th of October, 2013, which will result in IPN messages not being validated successfully, or PDT scripts not being able to retrieve transaction information.
Action Required before the 7th of October, 2013
Merchants need to update their IPN and/or PDT scripts to use HTTP 1.1, and include the “Host” and “Connection: close” HTTP header in the IPN postback script.
Example with Host as [url]www.paypal.com[/url] (please make necessary changes if you are using ipnpb.paypal.com):
ASP
//Set values for the request back
req.Method="POST";
req.Host="'www.paypal.com'";
req.ContentType="application/x-www-form-urlencoded";
Perl
$req=HTTP::Request->new('POST', 'https://www.paypal.com/cgi-bin/webscr');
$req->content_type('application/x-www-form-urlencoded');
$req->header(Host=> 'www.paypal.com');
$req->header(Connection=> 'close');
PHP
// post back to PayPal system to validate
$header="POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .="Content-Type: application/x-www-form-urlencoded\r\n";
$header .="Host: www.paypal.com\r\n";
$header .="Connection: close\r\n\r\n";
Java
HttpsURLConnection uc=(HttpsURLConnection) u.openConnection();
uc.setDoOutput(true);
uc.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
uc.setRequestProperty("Host", "www.paypal.com");
uc.setRequestProperty("Connection", "Close");
The PayPal Sandbox has been configured to reject any HTTP requests without the “Host” header with HTTP 400 error. Merchants can use the Sandbox environment to certify the changes to their IPN and PDT scripts.[/QUOTE]
[QUOTE=Archemyde;42548417]my specific issue is that confirmation of the donation never returns to the webserver.[/QUOTE]
Have you set up your IPN listener URL and PDT return URL on the paypal website?
Sorry, you need to Log In to post a reply to this thread.