$value) { $post .= $key. '='. urlencode($value). '&'; } $post .= 'cmd=_notify-validate'; return $post; } 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. url=@url vars=@vars', array( '@vars' => print_r($vars, TRUE) ), 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))); if(!_booking_paypal_ipn_verify($ipn)) return; /* 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; //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; //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); } 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 //$payment = db_query("SELECT booking_amount_paid, booking_status, booking_total_pay_reqd, booking_partner_id FROM {booking_person} where nid = :nid", // array(':nid' => $nid)) // ->fetchObject(); $payment = db_select('booking_person', 'p') ->condition('p.nid', $nid,'=') ->fields('p', array('booking_amount_paid', 'booking_status', 'booking_total_pay_reqd', 'booking_partner_id')) ->execute() ->fetchObject(); if ($payment) { watchdog('booking', 'Found matching user with node id: !id; event id: !eid; existing payment !payment', array('!id' => $nid, '!eid' => $eid, '!payment' => $payment->booking_amount_paid)); //if successful, update their total payment amount $total = $payment->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 ' . $payment->booking_status); if (_booking_check_bookings_full() == True || $payment->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 = $payment->booking_status; //$status = $payment->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(); //send a notification email //If there is no outstanding balance, send a payment complete email if ($total >= $payment->booking_total_pay_reqd) { //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) && ($payment->booking_partner_id > 0)) { //check spouse booking_status and payment info $spouse = db_select('booking_person', 'p') ->condition('p.nid', $payment->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' => $payment->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 !status', array('!id' => $spouse_new_amount_reqd, '!new' => $spouse_status, '!status' => $spouse->booking_amount_paid)); //update the database for this person db_update('booking_person') ->fields(array( 'booking_total_pay_reqd' => $spouse_new_amount_reqd, 'booking_status' => $spouse_status, )) ->condition('nid', $payment->booking_partner_id) ->execute(); //send an email _booking_registration_email($payment->booking_partner_id, $balance_payment); } //end spouse check } else //this person still has an outstanding balance so just send a confirmation email { _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); } }