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 | */ |
---|
12 | class 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', getDump($options)), 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', getDump($options)), LOG_WARNING, __FILE__, __LINE__); |
---|
120 | return false; |
---|
121 | } |
---|
122 | |
---|
123 | if (isset($this->_buttons[$name])) { |
---|
124 | $app->logMsg(sprintf('Overwriting existing button name: %s', getDump($this->_buttons[$name])), 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', getDump(getFormData())), 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', getDump(getFormData())), 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 | ?> |
---|