Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 309 lines (266 sloc) 9.574 kB
5d681ae @MicahCarrick first commit
MicahCarrick authored
1 <?php
2 /**
3 * PayPal IPN Listener
4 *
5 * A class to listen for and handle Instant Payment Notifications (IPN) from
6 * the PayPal server.
7 *
8 * https://github.com/Quixotix/PHP-PayPal-IPN
9 *
10 * @package PHP-PayPal-IPN
11 * @author Micah Carrick
12 * @copyright (c) 2011 - Micah Carrick
ba1d243 @MicahCarrick Fixed Issue #5 by disabling CURLOPT_FOLLOWLOCATION. This option can b…
MicahCarrick authored
13 * @version 2.0.5
5d681ae @MicahCarrick first commit
MicahCarrick authored
14 * @license http://opensource.org/licenses/gpl-3.0.html
15 */
16 class IpnListener {
17
18 /**
19 * If true, the recommended cURL PHP library is used to send the post back
20 * to PayPal. If flase then fsockopen() is used. Default true.
21 *
22 * @var boolean
23 */
24 public $use_curl = true;
25
26 /**
c031d72 @MicahCarrick Issue #3 - Added force_ssl_v3 parameter for cURL using GnuTLS.
MicahCarrick authored
27 * If true, explicitly sets cURL to use SSL version 3. Use this if cURL
28 * is compiled with GnuTLS SSL.
29 *
30 * @var boolean
31 */
ba1d243 @MicahCarrick Fixed Issue #5 by disabling CURLOPT_FOLLOWLOCATION. This option can b…
MicahCarrick authored
32 public $force_ssl_v3 = true;
33
34 /**
35 * If true, cURL will use the CURLOPT_FOLLOWLOCATION to follow any
36 * "Location: ..." headers in the response.
37 *
38 * @var boolean
39 */
40 public $follow_location = false;
c031d72 @MicahCarrick Issue #3 - Added force_ssl_v3 parameter for cURL using GnuTLS.
MicahCarrick authored
41
42 /**
5d681ae @MicahCarrick first commit
MicahCarrick authored
43 * If true, an SSL secure connection (port 443) is used for the post back
44 * as recommended by PayPal. If false, a standard HTTP (port 80) connection
45 * is used. Default true.
46 *
47 * @var boolean
48 */
49 public $use_ssl = true;
50
51 /**
52 * If true, the paypal sandbox URI www.sandbox.paypal.com is used for the
53 * post back. If false, the live URI www.paypal.com is used. Default false.
54 *
55 * @var boolean
56 */
57 public $use_sandbox = false;
58
59 /**
60 * The amount of time, in seconds, to wait for the PayPal server to respond
61 * before timing out. Default 30 seconds.
62 *
63 * @var int
64 */
65 public $timeout = 30;
66
64570e2 @MicahCarrick Added optional $post_data parameter to processIpn() method.
MicahCarrick authored
67 private $post_data = array();
5d681ae @MicahCarrick first commit
MicahCarrick authored
68 private $post_uri = '';
69 private $response_status = '';
70 private $response = '';
71
72 const PAYPAL_HOST = 'www.paypal.com';
7b75938 @MicahCarrick Typo in sandbox URL in last commit. Oops.
MicahCarrick authored
73 const SANDBOX_HOST = 'www.sandbox.paypal.com';
5d681ae @MicahCarrick first commit
MicahCarrick authored
74
75 /**
76 * Post Back Using cURL
77 *
78 * Sends the post back to PayPal using the cURL library. Called by
79 * the processIpn() method if the use_curl property is true. Throws an
80 * exception if the post fails. Populates the response, response_status,
81 * and post_uri properties on success.
82 *
83 * @param string The post data as a URL encoded string
84 */
85 protected function curlPost($encoded_data) {
86
87 if ($this->use_ssl) {
88 $uri = 'https://'.$this->getPaypalHost().'/cgi-bin/webscr';
89 $this->post_uri = $uri;
90 } else {
91 $uri = 'http://'.$this->getPaypalHost().'/cgi-bin/webscr';
92 $this->post_uri = $uri;
93 }
94
95 $ch = curl_init();
96
97 curl_setopt($ch, CURLOPT_URL, $uri);
98 curl_setopt($ch, CURLOPT_POST, true);
99 curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded_data);
ba1d243 @MicahCarrick Fixed Issue #5 by disabling CURLOPT_FOLLOWLOCATION. This option can b…
MicahCarrick authored
100 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $this->follow_location);
5d681ae @MicahCarrick first commit
MicahCarrick authored
101 curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
102 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
103 curl_setopt($ch, CURLOPT_HEADER, true);
104
c031d72 @MicahCarrick Issue #3 - Added force_ssl_v3 parameter for cURL using GnuTLS.
MicahCarrick authored
105 if ($this->force_ssl_v3) {
106 curl_setopt($ch, CURLOPT_SSLVERSION, 3);
107 }
108
5d681ae @MicahCarrick first commit
MicahCarrick authored
109 $this->response = curl_exec($ch);
ff703ea @MicahCarrick Modified the cURL response check for Issue #3
MicahCarrick authored
110 $this->response_status = strval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
ca678b4 @MicahCarrick Added error descriptions for failed cURL post
MicahCarrick authored
111
ff703ea @MicahCarrick Modified the cURL response check for Issue #3
MicahCarrick authored
112 if ($this->response === false || $this->response_status == '0') {
ca678b4 @MicahCarrick Added error descriptions for failed cURL post
MicahCarrick authored
113 $errno = curl_errno($ch);
114 $errstr = curl_error($ch);
115 throw new Exception("cURL error: [$errno] $errstr");
116 }
5d681ae @MicahCarrick first commit
MicahCarrick authored
117 }
118
119 /**
120 * Post Back Using fsockopen()
121 *
122 * Sends the post back to PayPal using the fsockopen() function. Called by
123 * the processIpn() method if the use_curl property is false. Throws an
124 * exception if the post fails. Populates the response, response_status,
125 * and post_uri properties on success.
126 *
127 * @param string The post data as a URL encoded string
128 */
129 protected function fsockPost($encoded_data) {
130
131 if ($this->use_ssl) {
132 $uri = 'ssl://'.$this->getPaypalHost();
133 $port = '443';
134 $this->post_uri = $uri.'/cgi-bin/webscr';
135 } else {
136 $uri = $this->getPaypalHost(); // no "http://" in call to fsockopen()
137 $port = '80';
138 $this->post_uri = 'http://'.$uri.'/cgi-bin/webscr';
139 }
140
141 $fp = fsockopen($uri, $port, $errno, $errstr, $this->timeout);
142
143 if (!$fp) {
144 // fsockopen error
145 throw new Exception("fsockopen error: [$errno] $errstr");
146 }
147
58e029c @MicahCarrick Fixed un-initialized variable error in fsockopenPost()
MicahCarrick authored
148 $header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
5d681ae @MicahCarrick first commit
MicahCarrick authored
149 $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
150 $header .= "Content-Length: ".strlen($encoded_data)."\r\n";
151 $header .= "Connection: Close\r\n\r\n";
152
153 fputs($fp, $header.$encoded_data."\r\n\r\n");
154
155 while(!feof($fp)) {
156 if (empty($this->response)) {
157 // extract HTTP status from first line
158 $this->response .= $status = fgets($fp, 1024);
159 $this->response_status = trim(substr($status, 9, 4));
160 } else {
161 $this->response .= fgets($fp, 1024);
162 }
163 }
164
165 fclose($fp);
166 }
167
168 private function getPaypalHost() {
169 if ($this->use_sandbox) return IpnListener::SANDBOX_HOST;
170 else return IpnListener::PAYPAL_HOST;
171 }
172
173 /**
174 * Get POST URI
175 *
176 * Returns the URI that was used to send the post back to PayPal. This can
177 * be useful for troubleshooting connection problems. The default URI
178 * would be "ssl://www.sandbox.paypal.com:443/cgi-bin/webscr"
179 *
180 * @return string
181 */
182 public function getPostUri() {
183 return $this->post_uri;
184 }
185
186 /**
187 * Get Response
188 *
189 * Returns the entire response from PayPal as a string including all the
190 * HTTP headers.
191 *
192 * @return string
193 */
194 public function getResponse() {
195 return $this->response;
196 }
197
198 /**
199 * Get Response Status
200 *
201 * Returns the HTTP response status code from PayPal. This should be "200"
202 * if the post back was successful.
203 *
204 * @return string
205 */
206 public function getResponseStatus() {
207 return $this->response_status;
208 }
209
210 /**
211 * Get Text Report
212 *
213 * Returns a report of the IPN transaction in plain text format. This is
214 * useful in emails to order processors and system administrators. Override
215 * this method in your own class to customize the report.
216 *
217 * @return string
218 */
219 public function getTextReport() {
220
221 $r = '';
222
223 // date and POST url
224 for ($i=0; $i<80; $i++) { $r .= '-'; }
225 $r .= "\n[".date('m/d/Y g:i A').'] - '.$this->getPostUri();
226 if ($this->use_curl) $r .= " (curl)\n";
227 else $r .= " (fsockopen)\n";
228
229 // HTTP Response
230 for ($i=0; $i<80; $i++) { $r .= '-'; }
231 $r .= "\n{$this->getResponse()}\n";
232
233 // POST vars
234 for ($i=0; $i<80; $i++) { $r .= '-'; }
235 $r .= "\n";
236
64570e2 @MicahCarrick Added optional $post_data parameter to processIpn() method.
MicahCarrick authored
237 foreach ($this->post_data as $key => $value) {
5d681ae @MicahCarrick first commit
MicahCarrick authored
238 $r .= str_pad($key, 25)."$value\n";
239 }
240 $r .= "\n\n";
241
242 return $r;
243 }
244
245 /**
246 * Process IPN
247 *
248 * Handles the IPN post back to PayPal and parsing the response. Call this
249 * method from your IPN listener script. Returns true if the response came
250 * back as "VERIFIED", false if the response came back "INVALID", and
251 * throws an exception if there is an error.
252 *
64570e2 @MicahCarrick Added optional $post_data parameter to processIpn() method.
MicahCarrick authored
253 * @param array
254 *
5d681ae @MicahCarrick first commit
MicahCarrick authored
255 * @return boolean
256 */
64570e2 @MicahCarrick Added optional $post_data parameter to processIpn() method.
MicahCarrick authored
257 public function processIpn($post_data=null) {
2bd6277 @MicahCarrick Issue #1 - Moved reqeust method check into requirePostMethod()
MicahCarrick authored
258
5d681ae @MicahCarrick first commit
MicahCarrick authored
259 $encoded_data = 'cmd=_notify-validate';
260
64570e2 @MicahCarrick Added optional $post_data parameter to processIpn() method.
MicahCarrick authored
261 if ($post_data === null) {
262 // use raw POST data
263 if (!empty($_POST)) {
264 $this->post_data = $_POST;
265 $encoded_data .= '&'.file_get_contents('php://input');
5d681ae @MicahCarrick first commit
MicahCarrick authored
266 } else {
64570e2 @MicahCarrick Added optional $post_data parameter to processIpn() method.
MicahCarrick authored
267 throw new Exception("No POST data found.");
268 }
269 } else {
270 // use provided data array
271 $this->post_data = $post_data;
272
273 foreach ($this->post_data as $key => $value) {
5d681ae @MicahCarrick first commit
MicahCarrick authored
274 $encoded_data .= "&$key=".urlencode($value);
275 }
276 }
64570e2 @MicahCarrick Added optional $post_data parameter to processIpn() method.
MicahCarrick authored
277
5d681ae @MicahCarrick first commit
MicahCarrick authored
278 if ($this->use_curl) $this->curlPost($encoded_data);
279 else $this->fsockPost($encoded_data);
280
281 if (strpos($this->response_status, '200') === false) {
282 throw new Exception("Invalid response status: ".$this->response_status);
283 }
284
285 if (strpos($this->response, "VERIFIED") !== false) {
286 return true;
287 } elseif (strpos($this->response, "INVALID") !== false) {
288 return false;
289 } else {
290 throw new Exception("Unexpected response from PayPal.");
291 }
292 }
2bd6277 @MicahCarrick Issue #1 - Moved reqeust method check into requirePostMethod()
MicahCarrick authored
293
294 /**
295 * Require Post Method
296 *
297 * Throws an exception and sets a HTTP 405 response header if the request
298 * method was not POST.
299 */
300 public function requirePostMethod() {
301 // require POST requests
302 if ($_SERVER['REQUEST_METHOD'] && $_SERVER['REQUEST_METHOD'] != 'POST') {
303 header('Allow: POST', true, 405);
304 throw new Exception("Invalid HTTP request method.");
305 }
306 }
5d681ae @MicahCarrick first commit
MicahCarrick authored
307 }
308 ?>
Something went wrong with that request. Please try again.