$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; } */ //TODO: Handle refund and payment dispute IPNs 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 so we can update their total amount paid and booking status $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; } //update the database for this person db_update('booking_person') ->fields(array( 'booking_amount_paid' => $total, 'booking_status' => $status, )) ->condition('nid', $nid) ->execute(); //handle workflow emails and spouse payment updates _booking_postpayment_trigger($nid, $person, $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); } } /** * Landing page after returning from paypal */ function booking_payment_completed_page() { //get some configuration information global $event; $output = ""; $return_array = array(); /* $parameters = drupal_get_query_parameters(); //check if we got a transaction token from paypal if (isset($parameters['token'])) { //check to make sure the value is something we're expecting $paypal_token = $parameters['token']; if (! preg_match('/^[0-9A-Fa-f\-]+$/', $paypal_token)) { //parameter from url is not what we were expecting so ignore it and just use the site-wide tokens for this page $tokens = booking_define_tokens(); } else { //query the payments table to find the attendee that this paypal token belongs to } } */ //set the page title $bookingTitle = !empty($event->booking_eventname) ? $event->booking_eventname : 'Event'; drupal_set_title($bookingTitle . ' Registration Completed'); $input = variable_get('booking_regn_completed_page'); //watchdog('booking_debug', "
Paypal landing page token:\n@info", array('@info' => print_r($input['value'] , true))); $output = token_replace($input['value'], booking_define_tokens()); $return_array[] = array( 'paragraph' => array( '#type' => 'markup', '#markup' => $output ) ); return $return_array; }