source: trunk/lib/PayPal.inc.php @ 704

Last change on this file since 704 was 502, checked in by anonymous, 9 years ago

Many minor fixes during pulso development

File size: 13.2 KB
RevLine 
[1]1<?php
2/**
[362]3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
[396]5 * Copyright 2001-2012 Strangecode, LLC
[362]6 *
7 * This file is part of The Strangecode Codebase.
8 *
9 * The Strangecode Codebase is free software: you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your option)
12 * any later version.
13 *
14 * The Strangecode Codebase is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * details.
18 *
19 * You should have received a copy of the GNU General Public License along with
20 * The Strangecode Codebase. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/**
[136]24 * PayPal.inc.php
25 *
26 * The PayPal class provides functions for creating PayPal buttons and for
[1]27 * receiving PayPal's Instant Payment Notification (IPN) service.
28 *
29 * @author  Quinn Comendant <quinn@strangecode.com>
30 * @version 1.0
31 */
[502]32class PayPal
33{
[42]34
[1]35    // General object parameters.
[484]36    protected $_params = array(
[1]37        'paypal_url' => 'https://www.paypal.com/cgi-bin/webscr',
38        'test_mode' => false,
39    );
[42]40
41    // Options used for specific buttons and links.
[484]42    protected $_default_button_options = array();
[42]43
[1]44    // Array of buttons created by newButton().
[484]45    protected $_buttons = array();
[42]46
[1]47    // Store the response from the last IPN.
[484]48    protected $_ipn_response;
[42]49
[1]50    /**
51     * Constructor.
52     *
53     * @param   bool    $test_mode  Use PayPal sandbox for testing.
54     */
[468]55    public function __construct($test_mode=false)
[1]56    {
57        if ($test_mode) {
58            $this->setParam(array('test_mode' => true));
59            // Use PayPal sandbox when in test mode.
60            $url = 'www.sandbox.paypal.com';
61        } else {
62            $url = 'www.paypal.com';
63        }
[42]64
[1]65        $this->_default_button_options = array(
66            '_global' => array(
67                'business' => null,
68            ),
69            'web_accept' => array(
70                'cmd' => '_xclick',
71                'button_url' => 'https://' . $url . '/cgi-bin/webscr',
72                'link_url' => 'https://' . $url . '/xclick/',
73                'submit_img' => 'https://' . $url . '/en_US/i/btn/x-click-but23.gif',
74                'submit_text' => _("Pay with PayPal"),
75            ),
76            'subscriptions' => array(
77                'cmd' => '_xclick-subscriptions',
78                'button_url' => 'https://' . $url . '/cgi-bin/webscr',
79                'link_url' => 'https://' . $url . '/subscriptions/',
80                'submit_img' => 'https://' . $url . '/en_US/i/btn/x-click-but20.gif',
81                'submit_text' => _("Subscribe with PayPal"),
82            ),
83        );
84    }
[42]85
[1]86    /**
[42]87     * Updates the _default_button_options array with options used for
[1]88     * specific buttons, or all buttons if $type is null.
89     *
90     * @access  public
91     *
[42]92     * @param   mixed   $type       The type of button to set defaults. If null,
[1]93     *                              sets the global button types.
94     * @param   array   $options    Options to set for button.
95     *
96     * @return  bool    True on success, false on failure.
97     */
[468]98    public function setButtonDefaults($type, $options)
[1]99    {
[479]100        $app =& App::getInstance();
[136]101
[1]102        if (!is_array($options) || empty($options)) {
[331]103            $app->logMsg(sprintf('Invalid options: %s', truncate(getDump($options, true), 128, 'end')), LOG_WARNING, __FILE__, __LINE__);
[1]104            return false;
105        }
[42]106
[1]107        if (is_null($type) || '_global' == $type) {
108            $this->_default_button_options['_global'] = array_merge($this->_default_button_options['_global'], $options);
109        } else if (!isset($this->_default_button_options[$type])) {
[136]110            $app->logMsg(sprintf('Invalid button type: %s', $type), LOG_WARNING, __FILE__, __LINE__);
[1]111            return false;
112        }
[42]113
[1]114        $this->_default_button_options[$type] = array_merge($this->_default_button_options[$type], $options);
115        return true;
116    }
[42]117
[1]118    /**
119     * Creates a new element in the _buttons array. Uses _default_button_options
120     * merged with provided options.
121     *
122     * @access  public
123     *
124     * @param   string  $type       Type of button to create.
125     * @param   string  $name       Name of button to create.
126     * @param   array   $options    Options of button. Overwrites _default_button_options.
127     *
128     * @return  bool    True on success, false on failure.
129     */
[468]130    public function newButton($type, $name, $options=null)
[1]131    {
[479]132        $app =& App::getInstance();
[136]133
[1]134        if (!isset($this->_default_button_options[$type])) {
[136]135            $app->logMsg(sprintf('Invalid button type: %s', $type), LOG_WARNING, __FILE__, __LINE__);
[1]136            return false;
137        }
[42]138
[1]139        if (!is_array($options) || empty($options)) {
[331]140            $app->logMsg(sprintf('Invalid options: %s', truncate(getDump($options, true), 128, 'end')), LOG_WARNING, __FILE__, __LINE__);
[1]141            return false;
142        }
[42]143
[1]144        if (isset($this->_buttons[$name])) {
[331]145            $app->logMsg(sprintf('Overwriting existing button name: %s', truncate(getDump($this->_buttons[$name], true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
[1]146        }
[42]147
[1]148        $this->_buttons[$name] = array(
149            'type' => $type,
150            'options' => array_merge($this->_default_button_options['_global'], $this->_default_button_options[$type], $options)
151        );
152
153        return true;
154    }
155
156    /**
157     * Returns the URL link for specified button.
158     *
159     * @access  public
160     *
161     * @param   string  $name   Name of button for which to generate link.
162     *
163     * @return  mixed   Link of button, or false on failure.
164     */
[468]165    public function getLink($name)
[1]166    {
[479]167        $app =& App::getInstance();
[136]168
[1]169        if (!isset($this->_buttons[$name])) {
[136]170            $app->logMsg(sprintf('Button does not exist: %s', $name), LOG_WARNING, __FILE__, __LINE__);
[1]171            return false;
172        }
[42]173
[1]174        $query_string = '';
175        $delim = '';
176        if (is_array($this->_buttons[$name]['options']) && !empty($this->_buttons[$name]['options'])) {
177            foreach ($this->_buttons[$name]['options'] as $key=>$val) {
178                if (!in_array($key, array('button_url', 'link_url', 'cmd', 'submit_img', 'submit_text'))) {
179                    $query_string .= $delim . $key . '=' . urlencode($val);
180                    $delim = '&';
181                }
182            }
183        }
184
185        // PayPal links do not like urlencoded slashes for some stupid reason.
186        $search = array('/%2F/');
187        $replace = array('/');
[42]188
[1]189        return $this->_buttons[$name]['options']['link_url'] . preg_replace($search, $replace, $query_string);
190    }
191
192    /**
193     * Prints the link returned by getLink().
194     *
195     * @access  public
196     *
197     * @param   string  $name   Name of button for which to generate link.
198     */
[468]199    public function printLink($name)
[1]200    {
201        echo $this->getLink($name);
202    }
203
204    /**
205     * Prints button with specified name.
206     *
207     * @access  public
208     *
209     * @param   string  $name   Name of button to print.
210     */
[468]211    public function printButton($name)
[1]212    {
213        ?>
214        <form action="<?php echo $this->_buttons[$name]['options']['button_url']; ?>" method="post">
[42]215        <?php
[1]216        if (is_array($this->_buttons[$name]['options']) && !empty($this->_buttons[$name]['options'])) {
217            foreach ($this->_buttons[$name]['options'] as $key=>$val) {
218                if (!in_array($key, array('button_url', 'link_url', 'submit_img', 'submit_text'))) {
219                    ?>
[20]220                    <input type="hidden" name="<?php echo $key; ?>" value="<?php echo $val; ?>" />
[1]221                    <?php
222                }
223            }
[42]224        }
[1]225        ?>
[20]226        <input type="image" src="<?php echo $this->_buttons[$name]['options']['submit_img']; ?>" border="0" name="submit" alt="<?php echo $this->_buttons[$name]['options']['submit_text']; ?>" />
[1]227        </form>
228        <?php
229    }
230
231    /**
232     * Set (or overwrite existing) parameters by passing an array of new parameters.
233     *
234     * @access public
235     * @param  array    $params     Array of parameters (key => val pairs).
236     */
[468]237    public function setParam($params)
[1]238    {
[479]239        $app =& App::getInstance();
[136]240
[1]241        if (isset($params) && is_array($params)) {
242            // Merge new parameters with old overriding only those passed.
243            $this->_params = array_merge($this->_params, $params);
244        } else {
[136]245            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
[1]246        }
247    }
248
249    /**
250     * Return the value of a parameter, if it exists.
251     *
252     * @access public
253     * @param string $param        Which parameter to return.
254     * @return mixed               Configured parameter value.
255     */
[468]256    public function getParam($param)
[1]257    {
[479]258        $app =& App::getInstance();
[136]259   
[478]260        if (array_key_exists($param, $this->_params)) {
[1]261            return $this->_params[$param];
262        } else {
[146]263            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
[1]264            return null;
265        }
266    }
267
268    /**
269     * Tests if the HTTP request is a valid IPN request from PayPal.
270     *
271     * @access  public
272     *
273     * @return  bool    True if valid, false if invalid.
274     */
[468]275    public function incomingIPNRequest()
[1]276    {
[42]277        if ($_SERVER['REQUEST_METHOD'] == 'POST'
[1]278        && $_SERVER['CONTENT_TYPE'] == 'application/x-www-form-urlencoded'
279        && !empty($_POST)) {
280            return true;
281        } else {
282            return false;
283        }
284    }
285
286    /**
287     * Process incoming IPN.
288     *
289     * @access  public
290     *
291     * @return  bool    True on success, false on failure.
292     */
[468]293    public function processIPN()
[42]294    {
[479]295        $app =& App::getInstance();
[136]296
[1]297        if (getPost('test_ipn') == '1' || $this->getParam('test_mode')) {
[331]298            $app->logMsg(sprintf('Processing PayPal IPN in test mode: %s', truncate(getDump(getFormData(), true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
[1]299            $url = parse_url('https://www.sandbox.paypal.com/cgi-bin/webscr');
300        } else {
[331]301            $app->logMsg(sprintf('Processing PayPal IPN: %s', truncate(getDump(getFormData(), true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
[1]302            $url = parse_url($this->getParam('paypal_url'));
303        }
[42]304
[1]305        // Read POST request and add 'cmd'.
306        $received_data = getPost();
307        $return_data = 'cmd=_notify-validate';
308        foreach ($received_data as $post_key => $post_val) {
309            $return_data .= '&' . $post_key . '=' . urlencode($post_val);
310        }
[42]311
[1]312        // Set the port number based on the scheme.
[42]313        if ($url['scheme'] == "https") {
[1]314            $url['port'] = 443;
315            $ssl = 'ssl://';
316        } else {
317            $url['port'] = 80;
318            $ssl = '';
319        }
[42]320
[1]321        // Open connection to PayPal server.
[42]322        $fp = fsockopen($ssl . $url['host'], $url['port'], $errnum, $errstr, 30);
[1]323
324        if (!$fp) {
[136]325            $app->logMsg(sprintf('Connection to PayPal URL %s failed with error: %s (%s)', $ssl . $url['host'], $errstr, $errnum), LOG_WARNING, __FILE__, __LINE__);
[1]326            return false;
327        } else {
[42]328            fputs($fp, "POST {$url['path']} HTTP/1.1\r\n");
329            fputs($fp, "Host: {$url['host']}\r\n");
330            fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
[247]331            fputs($fp, "Content-length: " . mb_strlen($return_data) . "\r\n");
[42]332            fputs($fp, "Connection: close\r\n\r\n");
333            fputs($fp, $return_data . "\r\n\r\n");
334
[1]335            // Loop through the response lines from the server.
336            $this->_ipn_response = '';
337            while (!feof($fp)) {
338                $this->_ipn_response .= fgets($fp, 1024);
339            }
340            fclose($fp);
[42]341
[136]342            $app->logMsg(sprintf('IPN response received: %s', $this->_ipn_response), LOG_NOTICE, __FILE__, __LINE__);
[1]343            return true;
344        }
345    }
[42]346
[1]347    /**
348     * Checks the response received from PayPal's IPN upon calling processIPN().
349     *
350     * @access  public
351     *
352     * @return  bool    True if response contains VERIFIED, false otherwise.
353     */
[468]354    public function verifiedIPN()
[1]355    {
[479]356        $app =& App::getInstance();
[136]357
[1]358        if (!isset($this->_ipn_response)) {
[136]359            $app->logMsg(sprintf('Cannot verify IPN, response not received.', null), LOG_WARNING, __FILE__, __LINE__);
[1]360            return false;
361        }
[42]362
[1]363        if (empty($this->_ipn_response)) {
[136]364            $app->logMsg(sprintf('Cannot verify IPN, response empty.', null), LOG_WARNING, __FILE__, __LINE__);
[1]365            return false;
366        }
[42]367
[1]368        if (preg_match('/VERIFIED/', $this->_ipn_response)) {
[136]369            $app->logMsg(sprintf('IPN verified!', null), LOG_DEBUG, __FILE__, __LINE__);
[1]370            return true;
371        } else if (preg_match('/INVALID/', $this->_ipn_response)) {
[136]372            $app->logMsg(sprintf('IPN invalid.', null), LOG_DEBUG, __FILE__, __LINE__);
[1]373            return false;
374        } else {
[136]375            $app->logMsg(sprintf('IPN unknown.', null), LOG_WARNING, __FILE__, __LINE__);
[1]376            return false;
377        }
378    }
[42]379
380
[1]381} // End of class.
382
Note: See TracBrowser for help on using the repository browser.