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

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

Many minor fixes during pulso development

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 $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 $key; ?>" value="<?php echo $val; ?>" />
221                    <?php
222                }
223            }
224        }
225        ?>
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']; ?>" />
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.