source: trunk/lib/PayPal.inc.php

Last change on this file was 763, checked in by anonymous, 2 years ago

Include boomerang in hidden input on login form so the user will be redirected if the revisit the login form after session is garbage collected. Add escape values used in html attributes.

File size: 13.2 KB
Line 
1<?php
2/**
3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
5 * Copyright 2001-2012 Strangecode, LLC
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/**
24 * PayPal.inc.php
25 *
26 * The PayPal class provides functions for creating PayPal buttons and for
27 * receiving PayPal's Instant Payment Notification (IPN) service.
28 *
29 * @author  Quinn Comendant <quinn@strangecode.com>
30 * @version 1.0
31 */
32class PayPal
33{
34
35    // General object parameters.
36    protected $_params = array(
37        'paypal_url' => 'https://www.paypal.com/cgi-bin/webscr',
38        'test_mode' => false,
39    );
40
41    // Options used for specific buttons and links.
42    protected $_default_button_options = array();
43
44    // Array of buttons created by newButton().
45    protected $_buttons = array();
46
47    // Store the response from the last IPN.
48    protected $_ipn_response;
49
50    /**
51     * Constructor.
52     *
53     * @param   bool    $test_mode  Use PayPal sandbox for testing.
54     */
55    public function __construct($test_mode=false)
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        }
64
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    }
85
86    /**
87     * Updates the _default_button_options array with options used for
88     * specific buttons, or all buttons if $type is null.
89     *
90     * @access  public
91     *
92     * @param   mixed   $type       The type of button to set defaults. If null,
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     */
98    public function setButtonDefaults($type, $options)
99    {
100        $app =& App::getInstance();
101
102        if (!is_array($options) || empty($options)) {
103            $app->logMsg(sprintf('Invalid options: %s', truncate(getDump($options, true), 128, 'end')), LOG_WARNING, __FILE__, __LINE__);
104            return false;
105        }
106
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])) {
110            $app->logMsg(sprintf('Invalid button type: %s', $type), LOG_WARNING, __FILE__, __LINE__);
111            return false;
112        }
113
114        $this->_default_button_options[$type] = array_merge($this->_default_button_options[$type], $options);
115        return true;
116    }
117
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     */
130    public function newButton($type, $name, $options=null)
131    {
132        $app =& App::getInstance();
133
134        if (!isset($this->_default_button_options[$type])) {
135            $app->logMsg(sprintf('Invalid button type: %s', $type), LOG_WARNING, __FILE__, __LINE__);
136            return false;
137        }
138
139        if (!is_array($options) || empty($options)) {
140            $app->logMsg(sprintf('Invalid options: %s', truncate(getDump($options, true), 128, 'end')), LOG_WARNING, __FILE__, __LINE__);
141            return false;
142        }
143
144        if (isset($this->_buttons[$name])) {
145            $app->logMsg(sprintf('Overwriting existing button name: %s', truncate(getDump($this->_buttons[$name], true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
146        }
147
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     */
165    public function getLink($name)
166    {
167        $app =& App::getInstance();
168
169        if (!isset($this->_buttons[$name])) {
170            $app->logMsg(sprintf('Button does not exist: %s', $name), LOG_WARNING, __FILE__, __LINE__);
171            return false;
172        }
173
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('/');
188
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     */
199    public function printLink($name)
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     */
211    public function printButton($name)
212    {
213        ?>
214        <form action="<?php echo oTxt($this->_buttons[$name]['options']['button_url']); ?>" method="post">
215        <?php
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                    ?>
220                    <input type="hidden" name="<?php echo oTxt($key); ?>" value="<?php echo oTxt($val); ?>" />
221                    <?php
222                }
223            }
224        }
225        ?>
226        <input type="image" src="<?php echo oTxt($this->_buttons[$name]['options']['submit_img']); ?>" border="0" name="submit" alt="<?php echo oTxt($this->_buttons[$name]['options']['submit_text']); ?>" />
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     */
237    public function setParam($params)
238    {
239        $app =& App::getInstance();
240
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 {
245            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
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     */
256    public function getParam($param)
257    {
258        $app =& App::getInstance();
259
260        if (array_key_exists($param, $this->_params)) {
261            return $this->_params[$param];
262        } else {
263            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
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     */
275    public function incomingIPNRequest()
276    {
277        if ($_SERVER['REQUEST_METHOD'] == 'POST'
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     */
293    public function processIPN()
294    {
295        $app =& App::getInstance();
296
297        if (getPost('test_ipn') == '1' || $this->getParam('test_mode')) {
298            $app->logMsg(sprintf('Processing PayPal IPN in test mode: %s', truncate(getDump(getFormData(), true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
299            $url = parse_url('https://www.sandbox.paypal.com/cgi-bin/webscr');
300        } else {
301            $app->logMsg(sprintf('Processing PayPal IPN: %s', truncate(getDump(getFormData(), true), 128, 'end')), LOG_DEBUG, __FILE__, __LINE__);
302            $url = parse_url($this->getParam('paypal_url'));
303        }
304
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        }
311
312        // Set the port number based on the scheme.
313        if ($url['scheme'] == "https") {
314            $url['port'] = 443;
315            $ssl = 'ssl://';
316        } else {
317            $url['port'] = 80;
318            $ssl = '';
319        }
320
321        // Open connection to PayPal server.
322        $fp = fsockopen($ssl . $url['host'], $url['port'], $errnum, $errstr, 30);
323
324        if (!$fp) {
325            $app->logMsg(sprintf('Connection to PayPal URL %s failed with error: %s (%s)', $ssl . $url['host'], $errstr, $errnum), LOG_WARNING, __FILE__, __LINE__);
326            return false;
327        } else {
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");
331            fputs($fp, "Content-length: " . mb_strlen($return_data) . "\r\n");
332            fputs($fp, "Connection: close\r\n\r\n");
333            fputs($fp, $return_data . "\r\n\r\n");
334
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);
341
342            $app->logMsg(sprintf('IPN response received: %s', $this->_ipn_response), LOG_NOTICE, __FILE__, __LINE__);
343            return true;
344        }
345    }
346
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     */
354    public function verifiedIPN()
355    {
356        $app =& App::getInstance();
357
358        if (!isset($this->_ipn_response)) {
359            $app->logMsg(sprintf('Cannot verify IPN, response not received.', null), LOG_WARNING, __FILE__, __LINE__);
360            return false;
361        }
362
363        if (empty($this->_ipn_response)) {
364            $app->logMsg(sprintf('Cannot verify IPN, response empty.', null), LOG_WARNING, __FILE__, __LINE__);
365            return false;
366        }
367
368        if (preg_match('/VERIFIED/', $this->_ipn_response)) {
369            $app->logMsg(sprintf('IPN verified!', null), LOG_DEBUG, __FILE__, __LINE__);
370            return true;
371        } else if (preg_match('/INVALID/', $this->_ipn_response)) {
372            $app->logMsg(sprintf('IPN invalid.', null), LOG_DEBUG, __FILE__, __LINE__);
373            return false;
374        } else {
375            $app->logMsg(sprintf('IPN unknown.', null), LOG_WARNING, __FILE__, __LINE__);
376            return false;
377        }
378    }
379
380
381} // End of class.
382
Note: See TracBrowser for help on using the repository browser.