$value) { $post .= $key. '='. urlencode($value). '&'; } $post .= 'cmd=_notify-validate'; return $post; } //see https://github.com/paypal/ipn-code-samples/blob/master/paypal_ipn.php for more details function _booking_paypal_ipn_verify($vars = array()) { if (variable_get('booking_paypal_sandbox', 0)) { watchdog('booking', 'Setting IPN verify to true, running in sandbox mode'); // PayPal sandbox requires login in order to even access, so for sandbox mode, simply return true. return TRUE; } $ch = curl_init(BOOKING_PAYPAL_SUBMIT_URL); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, _booking_paypal_post($vars)); ob_start(); if (curl_exec($ch)) { $info = ob_get_contents(); curl_close($ch); ob_end_clean(); if (preg_match('/VERIFIED/i', $info)) { watchdog('booking', 'Payment verification completed successfully: @info', array('@info' => $info)); return TRUE; } else { watchdog('booking', 'Payment verification not successful: @info', array('@info' => $info)); return FALSE; } } else { watchdog('booking_paypal', 'Call to curl_exec() failed with error @error. vars=@vars', array( '@vars' => print_r($vars, TRUE), '@error' => curl_error($ch)), WATCHDOG_ERROR); return FALSE; } } /** * Handles an incoming PayPal IPN. */ function booking_paypal_ipn() { $ipn = $_POST; watchdog('booking', 'Payment notification received: @info', array('@info' => var_export($ipn, TRUE))); //verify the notification with paypal if(!_booking_paypal_ipn_verify($ipn)) { watchdog('booking_paypal', 'Payment verification did not succeed. Retrying...'); //retry the attempt to verify the payment 5 times, sleeping in between each attempt $continue = TRUE; $i = 1; while ($continue && $i < 5) { if(!_booking_paypal_ipn_verify($ipn)) { watchdog('booking_paypal', "Payment verification did not succeed on attempt $i."); $i++; sleep(3); } else { $continue = FALSE; watchdog('booking_paypal', "Payment verification succeeded on attempt $i."); } } //exited the loop above with an unsuccessful verification, so return from the function now if ($continue) { watchdog('booking_paypal', 'Payment verification was unsuccessful.'); return; } } //verification was successful if we reach this point /* if ($ipn['payment_status'] != 'Pending' && variable_get('booking_paypal_sandbox', 0) == 1) { watchdog('booking', 'Running in sandbox mode but type is not pending'); return; } */ if (empty($ipn['payment_status']) || ($ipn['payment_status'] != 'Completed' && variable_get('booking_paypal_sandbox', 0) == 0)) return; //if (strcasecmp($ipn['receiver_email'], variable_get('booking_paypal_account', '') <> 0)) if ($ipn['receiver_email'] !== variable_get('booking_paypal_account', '')) { watchdog('booking_paypal', 'Receiving address "!receiver" for paypal payment doesnt match configured address "!configured". Full POST: !id', array('!id' => var_export($ipn, TRUE), '!receiver' => $ipn['receiver_email'], '!configured' => variable_get('booking_paypal_account', '')), WATCHDOG_ERROR); } //Insert record into database and remove temporary booking id field from user _booking_process_payment($ipn); } function _booking_process_payment($data) { global $event; $balance_payment = false; $amount_owing = 0; //extract the person node id from the invoice $pos = strpos($data['invoice'], "_"); if (($pos === false) || ($pos == 0)) { watchdog('booking', 'Unable to process payment with invalid invoice information: !id', array('!id' => $data['invoice']), WATCHDOG_ERROR); return; } //get the part of the invoice up to the first underscore $nid = substr($data['invoice'], 0, $pos); //get the data between the first and second underscore $eid = substr($data['invoice'], $pos + 1, strrpos($data['invoice'], "_") - $pos - 1); if (substr($eid,0,3) == "bal") { $balance_payment = true; watchdog('booking', 'Balance payment for user with node id: !id', array('!id' => $nid)); } //verify paypal hasn't already sent through a notification for this payment $duplicate_check = db_query("SELECT payid, booking_person_nid FROM {booking_payment} where booking_ipn_track_id = :ipn_id ", array(':ipn_id' => $data['ipn_track_id'])) ->fetchObject(); if ($duplicate_check) { watchdog('booking', 'Detected duplicate paypal notifications for transaction id !id, registration id !nid', array('!id' => $data['ipn_track_id'], '!nid' => $nid), WATCHDOG_ERROR); return; } //watchdog('booking', 'Adding payment for user with node id: !id; event id: !eid', array('!id' => $nid, '!eid' => $eid)); $result = db_insert('booking_payment') ->fields(array( 'booking_person_nid' => $nid, 'booking_eventid' => $event->eid, 'booking_mc_gross' => $data['mc_gross'], 'booking_mc_currency' => $data['mc_currency'], 'booking_mc_fee' => $data['mc_fee'], 'booking_quantity' => $data['quantity'], 'booking_invoice' => $data['invoice'], 'booking_payer_id' => $data['payer_id'], 'booking_payment_date' => strtotime($data['payment_date']), 'booking_payment_status' => $data['payment_status'], 'booking_first_name' => $data['first_name'], 'booking_last_name' => $data['last_name'], 'booking_buyer_email' => $data['payer_email'], 'booking_payer_status' => $data['payer_status'], 'booking_item_name' => $data['item_name'], 'booking_ipn_track_id' => $data['ipn_track_id'], )) ->execute(); //Get the person's info $person = node_load($nid); //check if we found a person matching this payment if ($person) { watchdog('booking', 'Found matching user with node id: !id; event id: !eid; existing payment !payment', array('!id' => $nid, '!eid' => $eid, '!payment' => $person->booking_amount_paid)); //if successful, update their total payment amount $total = $person->booking_amount_paid + $data['mc_gross']; //only recalculate their booking status if this is the initial payment, not a payment of the outstanding balance if ($balance_payment == FALSE) { watchdog('booking', 'Processing an initial payment. Booking status is currently ' . $person->booking_status); if ($person->booking_status == 1) { watchdog('booking', 'This registration has been manually assigned to the booked-in list.'); $status = 1; } elseif (_booking_check_bookings_full() == True || $person->booking_status == 2) { watchdog('booking', 'This registration belongs on the waiting list.'); $status = 2; } else { watchdog('booking', 'This registration made it to the booked-in list.'); $status = 1; } } else //this is a balance payment { watchdog('booking', 'Processing a balance payment.'); //if this is a payment of outstanding balance, keep the booking_status the same $status = $person->booking_status; //$status = $person->booking_status == 2 ? 2 : 1; } //update the database for this person db_update('booking_person') ->fields(array( 'booking_amount_paid' => $total, 'booking_status' => $status, )) ->condition('nid', $nid) ->execute(); //replace the code below with a call to the helper function _booking_postpayment_trigger($nid, $person, $balance_payment); /* //If there is no outstanding balance, send a payment complete email $amount_owing = _booking_amount_owing($person); //if ($total >= $person->booking_total_pay_reqd) if ($amount_owing == 0) { //set the payment complete flag db_update('booking_person') ->fields(array( 'booking_payment_complete' => 'Y', )) ->condition('nid', $nid) ->execute(); //this should always be a balance payment type email, since that tells the user that their payment is completed _booking_registration_email($nid, TRUE); //if we are combining payments, and this person has a linked spouse if ((variable_get('booking_enable_combined_pricing', 0) == 1) && ($person->booking_partner_id > 0)) { //check spouse booking_status and payment info $spouse = db_select('booking_person', 'p') ->condition('p.nid', $person->booking_partner_id,'=') ->fields('p', array('booking_amount_paid', 'booking_status', 'booking_total_pay_reqd')) ->execute() ->fetchObject(); //update the spouse's status from "not paid" if required $spouse_status = $spouse->booking_status == 0 ? 1 : $spouse->booking_status; watchdog('booking', 'Setting status for spouse id !id to !new from !status', array('!id' => $person->booking_partner_id, '!new' => $spouse_status, '!status' => $spouse->booking_status)); //set the spouse's payment required to be zero or equal to their previous payment total $spouse_new_amount_reqd = $spouse->booking_amount_paid > 0 ? $spouse->booking_amount_paid : 0; watchdog('booking', 'Setting amount owing for spouse id !id to !new from !old.', array('!id' => $person->booking_partner_id, '!new' => $spouse_new_amount_reqd, '!old' => $spouse->booking_total_pay_reqd)); //update the database for this person db_update('booking_person') ->fields(array( 'booking_total_pay_reqd' => $spouse_new_amount_reqd, 'booking_status' => $spouse_status, 'booking_payment_complete' => 'Y', )) ->condition('nid', $person->booking_partner_id) ->execute(); //send an email to the spouse _booking_registration_email($person->booking_partner_id, TRUE); } } //if this was an initial payment we might need to send a notification elseif ($balance_payment == FALSE) { //send a notification email if we didn't automatically send one earlier if (variable_get('booking_auto_confirm_email', 0) == 0) { _booking_registration_email($nid, FALSE); } } else //this person still has an outstanding balance so just send a confirmation email { watchdog('booking', 'This balance payment of !payment was insufficient for !id to completely pay the total outstanding of !outstanding.', array('!id' => $nid, '!payment' => $data['mc_gross'], '!outstanding' => $amount_owing)); //send the person an email thanking them for their partial payment _booking_partialbalance_payment_email($nid); //TODO: create an email specifically for partial-balance payments //_booking_registration_email($nid, $balance_payment); } */ } else //couldn't find a matching nid for this invoice { watchdog('booking', "Unable to process payment for user with node id: '!id'", array('!id' => $nid), WATCHDOG_ERROR); //db_query("UPDATE {booking_person} SET booking_tempid='' WHERE nid = %d", $nid); } }