* Copyright 2001-2012 Strangecode, LLC
*
* This file is part of The Strangecode Codebase.
*
* The Strangecode Codebase is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* The Strangecode Codebase 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* The Strangecode Codebase. If not, see .
*/
/**
* PayPal.inc.php
*
* The PayPal class provides functions for creating PayPal buttons and for
* receiving PayPal's Instant Payment Notification (IPN) service.
*
* @author Quinn Comendant
* @version 1.0
*/
class PayPal
{
// General object parameters.
protected $_params = array(
'paypal_url' => 'https://www.paypal.com/cgi-bin/webscr',
'test_mode' => false,
);
// Options used for specific buttons and links.
protected $_default_button_options = array();
// Array of buttons created by newButton().
protected $_buttons = array();
// Store the response from the last IPN.
protected $_ipn_response;
/**
* Constructor.
*
* @param bool $test_mode Use PayPal sandbox for testing.
*/
public function __construct($test_mode=false)
{
if ($test_mode) {
$this->setParam(array('test_mode' => true));
// Use PayPal sandbox when in test mode.
$url = 'www.sandbox.paypal.com';
} else {
$url = 'www.paypal.com';
}
$this->_default_button_options = array(
'_global' => array(
'business' => null,
),
'web_accept' => array(
'cmd' => '_xclick',
'button_url' => 'https://' . $url . '/cgi-bin/webscr',
'link_url' => 'https://' . $url . '/xclick/',
'submit_img' => 'https://' . $url . '/en_US/i/btn/x-click-but23.gif',
'submit_text' => _("Pay with PayPal"),
),
'subscriptions' => array(
'cmd' => '_xclick-subscriptions',
'button_url' => 'https://' . $url . '/cgi-bin/webscr',
'link_url' => 'https://' . $url . '/subscriptions/',
'submit_img' => 'https://' . $url . '/en_US/i/btn/x-click-but20.gif',
'submit_text' => _("Subscribe with PayPal"),
),
);
}
/**
* Updates the _default_button_options array with options used for
* specific buttons, or all buttons if $type is null.
*
* @access public
*
* @param mixed $type The type of button to set defaults. If null,
* sets the global button types.
* @param array $options Options to set for button.
*
* @return bool True on success, false on failure.
*/
public function setButtonDefaults($type, $options)
{
$app =& App::getInstance();
if (!is_array($options) || empty($options)) {
$app->logMsg(sprintf('Invalid options: %s', truncate(getDump($options, true), 128, 'end')), LOG_WARNING, __FILE__, __LINE__);
return false;
}
if (is_null($type) || '_global' == $type) {
$this->_default_button_options['_global'] = array_merge($this->_default_button_options['_global'], $options);
} else if (!isset($this->_default_button_options[$type])) {
$app->logMsg(sprintf('Invalid button type: %s', $type), LOG_WARNING, __FILE__, __LINE__);
return false;
}
$this->_default_button_options[$type] = array_merge($this->_default_button_options[$type], $options);
return true;
}
/**
* Creates a new element in the _buttons array. Uses _default_button_options
* merged with provided options.
*
* @access public
*
* @param string $type Type of button to create.
* @param string $name Name of button to create.
* @param array $options Options of button. Overwrites _default_button_options.
*
* @return bool True on success, false on failure.
*/
public function newButton($type, $name, $options=null)
{
$app =& App::getInstance();
if (!isset($this->_default_button_options[$type])) {
$app->logMsg(sprintf('Invalid button type: %s', $type), LOG_WARNING, __FILE__, __LINE__);
return false;
}
if (!is_array($options) || empty($options)) {
$app->logMsg(sprintf('Invalid options: %s', truncate(getDump($options, true), 128, 'end')), LOG_WARNING, __FILE__, __LINE__);
return false;
}
if (isset($this->_buttons[$name])) {
$app->logMsg(sprintf('Overwriting existing button name: %s', truncate(getDump($this->_buttons[$name], true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
}
$this->_buttons[$name] = array(
'type' => $type,
'options' => array_merge($this->_default_button_options['_global'], $this->_default_button_options[$type], $options)
);
return true;
}
/**
* Returns the URL link for specified button.
*
* @access public
*
* @param string $name Name of button for which to generate link.
*
* @return mixed Link of button, or false on failure.
*/
public function getLink($name)
{
$app =& App::getInstance();
if (!isset($this->_buttons[$name])) {
$app->logMsg(sprintf('Button does not exist: %s', $name), LOG_WARNING, __FILE__, __LINE__);
return false;
}
$query_string = '';
$delim = '';
if (is_array($this->_buttons[$name]['options']) && !empty($this->_buttons[$name]['options'])) {
foreach ($this->_buttons[$name]['options'] as $key=>$val) {
if (!in_array($key, array('button_url', 'link_url', 'cmd', 'submit_img', 'submit_text'))) {
$query_string .= $delim . $key . '=' . urlencode($val);
$delim = '&';
}
}
}
// PayPal links do not like urlencoded slashes for some stupid reason.
$search = array('/%2F/');
$replace = array('/');
return $this->_buttons[$name]['options']['link_url'] . preg_replace($search, $replace, $query_string);
}
/**
* Prints the link returned by getLink().
*
* @access public
*
* @param string $name Name of button for which to generate link.
*/
public function printLink($name)
{
echo $this->getLink($name);
}
/**
* Prints button with specified name.
*
* @access public
*
* @param string $name Name of button to print.
*/
public function printButton($name)
{
?>
val pairs).
*/
public function setParam($params)
{
$app =& App::getInstance();
if (isset($params) && is_array($params)) {
// Merge new parameters with old overriding only those passed.
$this->_params = array_merge($this->_params, $params);
} else {
$app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
}
}
/**
* Return the value of a parameter, if it exists.
*
* @access public
* @param string $param Which parameter to return.
* @return mixed Configured parameter value.
*/
public function getParam($param)
{
$app =& App::getInstance();
if (array_key_exists($param, $this->_params)) {
return $this->_params[$param];
} else {
$app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
return null;
}
}
/**
* Tests if the HTTP request is a valid IPN request from PayPal.
*
* @access public
*
* @return bool True if valid, false if invalid.
*/
public function incomingIPNRequest()
{
if ($_SERVER['REQUEST_METHOD'] == 'POST'
&& $_SERVER['CONTENT_TYPE'] == 'application/x-www-form-urlencoded'
&& !empty($_POST)) {
return true;
} else {
return false;
}
}
/**
* Process incoming IPN.
*
* @access public
*
* @return bool True on success, false on failure.
*/
public function processIPN()
{
$app =& App::getInstance();
if (getPost('test_ipn') == '1' || $this->getParam('test_mode')) {
$app->logMsg(sprintf('Processing PayPal IPN in test mode: %s', truncate(getDump(getFormData(), true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
$url = parse_url('https://www.sandbox.paypal.com/cgi-bin/webscr');
} else {
$app->logMsg(sprintf('Processing PayPal IPN: %s', truncate(getDump(getFormData(), true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
$url = parse_url($this->getParam('paypal_url'));
}
// Read POST request and add 'cmd'.
$received_data = getPost();
$return_data = 'cmd=_notify-validate';
foreach ($received_data as $post_key => $post_val) {
$return_data .= '&' . $post_key . '=' . urlencode($post_val);
}
// Set the port number based on the scheme.
if ($url['scheme'] == "https") {
$url['port'] = 443;
$ssl = 'ssl://';
} else {
$url['port'] = 80;
$ssl = '';
}
// Open connection to PayPal server.
$fp = fsockopen($ssl . $url['host'], $url['port'], $errnum, $errstr, 30);
if (!$fp) {
$app->logMsg(sprintf('Connection to PayPal URL %s failed with error: %s (%s)', $ssl . $url['host'], $errstr, $errnum), LOG_WARNING, __FILE__, __LINE__);
return false;
} else {
fputs($fp, "POST {$url['path']} HTTP/1.1\r\n");
fputs($fp, "Host: {$url['host']}\r\n");
fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
fputs($fp, "Content-length: " . mb_strlen($return_data) . "\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $return_data . "\r\n\r\n");
// Loop through the response lines from the server.
$this->_ipn_response = '';
while (!feof($fp)) {
$this->_ipn_response .= fgets($fp, 1024);
}
fclose($fp);
$app->logMsg(sprintf('IPN response received: %s', $this->_ipn_response), LOG_NOTICE, __FILE__, __LINE__);
return true;
}
}
/**
* Checks the response received from PayPal's IPN upon calling processIPN().
*
* @access public
*
* @return bool True if response contains VERIFIED, false otherwise.
*/
public function verifiedIPN()
{
$app =& App::getInstance();
if (!isset($this->_ipn_response)) {
$app->logMsg(sprintf('Cannot verify IPN, response not received.', null), LOG_WARNING, __FILE__, __LINE__);
return false;
}
if (empty($this->_ipn_response)) {
$app->logMsg(sprintf('Cannot verify IPN, response empty.', null), LOG_WARNING, __FILE__, __LINE__);
return false;
}
if (preg_match('/VERIFIED/', $this->_ipn_response)) {
$app->logMsg(sprintf('IPN verified!', null), LOG_DEBUG, __FILE__, __LINE__);
return true;
} else if (preg_match('/INVALID/', $this->_ipn_response)) {
$app->logMsg(sprintf('IPN invalid.', null), LOG_DEBUG, __FILE__, __LINE__);
return false;
} else {
$app->logMsg(sprintf('IPN unknown.', null), LOG_WARNING, __FILE__, __LINE__);
return false;
}
}
} // End of class.