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

Last change on this file since 331 was 331, checked in by quinn, 16 years ago

Truncating output from getDump when used for logging.

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