-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlm_paypal.ipn.inc
412 lines (357 loc) · 12 KB
/
lm_paypal.ipn.inc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
<?php
/**
* Handle an incoming IPN.
*
* PayPal sends one or more IPNs here for each transaction that takes place. The
* IPN is present as a form submission which this routine unravels and saves for
* processing.
*/
function lm_paypal_ipn_in() {
// file_put_contents('/tmp/ipn-post.txt', serialize($_POST));
watchdog(LM_PAYPAL, 'lm_paypal_ipn_in called', NULL);
// Don't bother with these fields - but don't flag them as errors
static $ignore_fields = array(
// 'notify_version',
'receipt_id',
'charset',
'mc_gross1',
);
// Process the incoming form results to check for unexpected values.
$schema = drupal_get_schema('lm_paypal_ipns');
foreach ($_POST as $key => $value) {
if ($value == '' || in_array($key, $ignore_fields)) {
continue;
}
if (!isset($schema['fields'][$key])) {
watchdog(LM_PAYPAL, 'IPN unknown field ignored: !key => !value', array('!key' => check_plain($key), '!value' => check_plain($value)), WATCHDOG_ERROR);
}
}
$validated = _lm_paypal_validate_ipn($_POST);
// Sanitize and remove risky fields from $_POST to make it suitable for
// drupal_write_record. Include the entire POST data for debugging.
$ipn = $_POST;
$ipn['timestamp'] = time();
$ipn['ipn'] = serialize($_POST);
unset($ipn['processed']);
foreach ($ipn as $key => $value) {
if ($value == '') {
$ipn[$key] = NULL;
}
}
// Add the IPN to our database (whether it validated or not).
$wr = drupal_write_record('lm_paypal_ipns', $ipn);
if ($wr == SAVED_NEW) {
$last = $ipn['id'];
watchdog(LM_PAYPAL, 'lm_paypal_ipn_in $last = ' . $ipn['id'], NULL);
$link = l(t('view'), "admin/config/lm_paypal/id/$ipn[id]");
if ($validated) {
watchdog(LM_PAYPAL, 'IPN incoming %type', array('%type' => check_plain($ipn['txn_type'])), WATCHDOG_NOTICE, $link);
lm_paypal_process_in((object) $ipn);
}
else {
// @todo actualy return ->data
$validate_result->data = '';
watchdog(LM_PAYPAL, 'IPN incoming NOT VERIFIED %type got %ret', array('%type' => check_plain($_POST['txn_type']), '%ret' => check_plain($validate_result->data)), WATCHDOG_ERROR, $link);
}
}
else {
watchdog(LM_PAYPAL, 'IPN in failed to run sql: %sql', array('%sql' => check_plain($sql)), WATCHDOG_ERROR);
// Return an HTTP error and hopefully PayPal will resend the IPN to me
// later on and then I can try again! Maybe PayPal is very busy
// or there is a network problem at the moment
}
return 'IPN: Only PayPal will ever see this page - humans go away!';
}
/**
* Process a newly arrived IPN message that has been verified and saved.
*
* @param $id
* The id of the saved IPN to be processed.
*/
function lm_paypal_process_in($ipn) {
$business = lm_paypal_api_get_business();
$link = l(t('view'), "admin/config/lm_paypal/id/$ipn->id");
if ($ipn->test_ipn != '' && !variable_get('lm_paypal_obey_test_ipns', FALSE)) {
watchdog(LM_PAYPAL, 'test_ipn received - ignoring', array(), WATCHDOG_WARNING, $link);
return;
}
if (strcasecmp(trim($ipn->receiver_email), trim($business)) != 0) {
watchdog(
LM_PAYPAL,
'Incoming IPN received email does not match business email (received %received, business %business)',
array('%received' => check_plain($ipn->receiver_email), '%business' => check_plain($business)),
WATCHDOG_ERROR,
$link);
return;
}
// Every implementor of hook_lm_paypal_ipn($ipn) is free to take whatever
// actions they want, based on the IPN, and return a boolean describing
// whether they handled the IPN. No attempt is made to prevent multiple
// modules handling the same IPN.
$results1 = module_invoke_all('lm_paypal_ipn', $ipn);
// Additionally, IPNs are displatched to a txn_type-specific hook.
$txn_type = preg_replace('/[^a-z0-9_]*/', '', $ipn->txn_type);
$results2 = module_invoke_all('lm_paypal_ipn_' . $txn_type, $ipn);
// Determine whether _some_ module handled the IPN, and warn if not.
$processed = FALSE;
foreach ($results1 as $res) {
if ($res) {
$processed = TRUE;
break;
}
}
if (!$processed) {
foreach ($results2 as $res) {
if ($res) {
$processed = TRUE;
break;
}
}
}
if (!$processed) {
watchdog(LM_PAYPAL, 'No processor for this IPN, ignoring: %type', array('%type' => check_plain($ipn->txn_type)), WATCHDOG_WARNING, $link);
}
return;
/*
// Don't check for processed txn_id's here as txn_id's are not provided
// for all subscr messages. Check then in the message type specific processors
// Find a processor.
// It's either lm_paypal_process_in_<type> (e.g.: ..._in_subscr_payment)
// or if not then strip any trailing _XXX and try the remaining
// (e.g.: ..._in_subscr)
$in = 'lm_paypal_process_in_';
$f = $in . $ipn->txn_type;
if (function_exists($f)) {
$res = $f($ipn);
module_invoke_all('lm_paypal_process_in', $ipn, $f, $res); // TODO: ->actions?
return $res;
}
$u = strpos($ipn->txn_type, '_');
if ($u > 0) {
$f = $in . substr($ipn->txn_type, 0, $u);
if (function_exists($f)) {
$res = $f($ipn);
module_invoke_all('lm_paypal_process_in', $ipn, $f, $res); // TODO: -> actions
return $res;
}
}
*/
}
/**
* Implementation of hook_lm_paypal_ipn.
*/
/*
function lm_paypal_lm_paypal_ipn($ipn) {
error_log("Called lm_paypal_lm_paypal_ipn($ipn->txn_id)");
return TRUE;
}
*/
/**
* Implementation of hook_lm_paypal_ipn_TXNTYPE.
* Process a send_money IPN message.
*/
function lm_paypal_lm_paypal_ipn_send_money($ipn) {
// error_log("Called lm_paypal_lm_paypal_ipn_send_money($ipn->txn_id)");
if (lm_paypal_debug()) {
watchdog(LM_PAYPAL, 'lm_paypal_lm_paypal_ipn_send_money (passing to web_accept)');
}
return lm_paypal_process_in_web_accept($ipn);
}
/**
* Implementation of hook_lm_paypal_ipn_TXNTYPE.
* Process a web_accept IPN message.
*/
function lm_paypal_lm_paypal_ipn_web_accept($ipn) {
// error_log("Called lm_paypal_lm_paypal_ipn_web_accept($ipn->txn_id)");
_lm_paypal_process_in_web_accept($ipn);
return TRUE;
}
/**
* Process a newly arrived web_accept IPN message
*
* @param $ipn
*/
function _lm_paypal_process_in_web_accept($ipn) {
$debug = variable_get('lm_paypal_debug', LM_PAYPAL_DEBUG_DEFAULT);
if ($debug) {
watchdog(LM_PAYPAL, 'in_web_accept');
}
$link = l(t('view'), "admin/config/lm_paypal/id/$ipn->id");
if (lm_paypal_already_processed($ipn->txn_id)) {
watchdog(
LM_PAYPAL,
'This transaction has already been processed, ignored: %id',
array('%id' => check_plain($ipn->txn_id)),
WATCHDOG_WARNING,
$link);
return;
}
lm_paypal_mark_processed($ipn);
if ($ipn->payment_status == 'Pending') {
watchdog(
LM_PAYPAL,
'Ignoring IPN with status: Pending. Check your PayPal account to see why it is pending. Note: pending_reason: %reason',
array('%reason' => check_plain($ipn->pending_reason)),
WATCHDOG_ERROR,
$link);
return;
}
$custom = lm_paypal_unpack_ipn_custom($ipn);
$uid = (isset($custom['uid']))?$custom['uid']:'';
$other = (isset($custom['other']))?$custom['other']:'';
if ($uid == '') {
if ($debug) {
watchdog(
LM_PAYPAL,
'No uid, try to lookup payer_email',
array(),
WATCHDOG_WARNING,
$link);
}
// In 6.x this was using lower on the mail from the database and $ipn
// @todo: find the D7 way to do LOWER()
$uid = db_select('users', 'u')
->fields('u', array('uid'))
->condition('mail', $ipn->payer_email)
->execute()->fetchField();
if (!$uid) {
watchdog(
LM_PAYPAL,
'IPN web_accept no uid presuming uid 0, cannot find payer_email: %email',
array('%email' => check_plain($ipn->payer_email)),
WATCHDOG_WARNING,
$link);
$uid = 0;
}
else {
watchdog(
LM_PAYPAL,
'IPN web_accept no uid, found payer_email %email for uid %uid',
array('%email' => check_plain($ipn->payer_email), '%uid' => $uid),
WATCHDOG_WARNING,
$link);
}
}
elseif (!is_numeric($uid) || intval($uid) != $uid || $uid < 0) {
watchdog(
LM_PAYPAL,
'Invalid uid, ignoring IPN: %uid',
array('%uid' => $uid),
WATCHDOG_WARNING,
$link);
return;
}
// If I receive a web_accept without a uid then presume it came from anon
if ($uid != '') {
// Is this uid valid?
$user = db_select('users', 'u')
->fields('u')
->condition('uid', $uid)
->execute();
if (!$user) {
watchdog(
LM_PAYPAL,
'IPN web_accept unknown uid, presuming uid 0: %uid',
array('%uid' => check_plain($uid)),
WATCHDOG_ERROR,
$link);
$uid = 0;
}
}
// Use the item_number to select the kind of payment coming in
$item_number = $ipn->item_number;
// If you use the Send Money menu item on PayPal I treat this pretty the
// same as a donation (item_number = 0)
if ($ipn->txn_type == 'send_money') {
if ($debug) {
watchdog(LM_PAYPAL, "send_money - being converted to web_accept");
}
$item_number = 0;
}
elseif ($item_number == '') {
if ($debug) {
watchdog(LM_PAYPAL, "empty item_number - being converted to web_accept");
}
$item_number = 0;
}
elseif (!is_numeric($item_number) || intval($item_number) != $item_number || $item_number < 0) {
watchdog(
LM_PAYPAL,
'Invalid item_number, ignoring IPN: %item_number',
array('%item_number' => check_plain($item_number)),
WATCHDOG_WARNING,
$link);
return;
}
return lm_paypal_web_accept_invoke($ipn, $link, $uid, $other, $item_number);
}
/**
* @todo Please document this function.
* @see http://drupal.org/node/1354
*/
function lm_paypal_web_accept_invoke($ipn, $link, $uid, $other, $item_number) {
// error_log("lm_paypal_web_accept_invoke($ipn->txn_id)");
// HACK! Remove this, and figure out how the web_accept path relates to top-
// level IPNs.
if (module_exists('lm_paypal_donations')) {
include_once drupal_get_path('module', 'lm_paypal_donations') . '/lm_paypal_donations.module';
if ($item_number >= 0) {
return _lm_paypal_process_in_donate($ipn, $link, $uid, $other, $item_number);
}
}
/*
// Find the correct web_accept processor
$ranges = lm_paypal_web_accept_register();
if ($ranges) {
foreach ($ranges as $r) {
$f = $r['fun'];
$min = $r['min'];
$max = $r['max'];
//watchdog(LM_PAYPAL,"found $f $min $max");
if ($min <= $item_number && $item_number <= $max) {
return $f($ipn, $link, $uid, $other, $item_number);
}
}
}
*/
watchdog(
LM_PAYPAL,
'No web_accept processor registered for this item_number: %item_number',
array('%item_number' => check_plain($item_number)),
WATCHDOG_WARNING,
$link);
}
/**
* Given the full $_POST data from an incoming IPN, check with PayPal that this
* is a real IPN. Without this check, IPNs may be spoofed.
*
* @param array $post
* The key-value pairs from an incoming IPN (presumably straight from $_POST)
* @return
* TRUE if PayPal successfully validated the request. This will not happen if
* there was an error communicating with the service, the IPN did not
* originate from PayPal, or it was tampered with in any way.
*/
function _lm_paypal_validate_ipn($post) {
// If lm_paypal_skip_validation is set, then don't validate the incoming IPN
// with PayPal. Never enable this on a real website!!
if (variable_get('lm_paypal_skip_validation', FALSE)) {
return TRUE;
}
// Build the IPN validation request from the $post fields
$req = 'cmd=_notify-validate';
foreach ($post as $key => $value) {
$req .= "&$key=" . urlencode(stripslashes($value));
}
// Validate this incoming IPN by sending it to PayPal to be checked
$lm_paypal_host = variable_get('lm_paypal_host', LM_PAYPAL_HOST_DEFAULT);
$validate_result = drupal_http_request(
lm_paypal_api_get_host(TRUE),
array(
'headers' => array('Content-Type' => 'application/x-www-form-urlencoded'),
'method' => 'POST',
'data' => $req
)
);
return $validate_result->data == 'VERIFIED';
}