Last active
January 16, 2019 19:48
-
-
Save butlerblog/feda5f705daeb37e91d691cfd86b102e to your computer and use it in GitHub Desktop.
Remove price validation check in IPN listener
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* The WP-Members PayPal IPN Listener Class. | |
* | |
* This class handles processing PayPal IPN payments for the WP-Members | |
* PayPal Subscriptions Extention. | |
* | |
* @package WP-Members | |
* @subpackage WP-Members PayPal Subscriptions | |
* @since 0.7.0 | |
*/ | |
class WP_Members_PayPal_Subscriptions_Listener { | |
/** | |
* Container for data to be passed to PayPal for validation. | |
* | |
* @since 0.7.0 | |
* @access private | |
* @var array | |
*/ | |
private $encoded_data; | |
/** | |
* Result returned from PayPal. | |
* | |
* @since 0.7.0 | |
* @access private | |
* @var string | |
*/ | |
private $curl_result; | |
/** | |
* Container for errors. | |
* | |
* @since 0.9.0 | |
* @access public | |
* @var bool|array | |
*/ | |
public $error; | |
/** | |
* The class contructor. | |
* | |
* @since 0.9.0 | |
*/ | |
function __construct() { | |
add_action( 'wpmem_ipn_validation', array( $this, 'ipn_validation' ) ); | |
//add_action( 'wpmem_ipn_success', array( $this, 'ipn_db_log' ), 10, 3 ); | |
//add_action( 'wpmem_ipn_invalid', array( $this, 'ipn_db_log' ), 10, 3 ); | |
} | |
/** | |
* IPN Listener function. | |
* | |
* @since 0.7.0 | |
* @access public | |
* | |
* @global object $wpmem_pp_sub The WP-Members PayPal IPN object. | |
*/ | |
public function do_ipn() { | |
global $wpmem_pp_sub; | |
/** | |
* Fires at the beginning of IPN processing. | |
* | |
* @since Unknown | |
*/ | |
do_action( 'wpmem_ipn_start' ); | |
// If logging, start log file entry. | |
$log_entry = ( 1 == $wpmem_pp_sub->ipn_debug ) ? "Start of IPN Log Run: " . current_time( 'mysql' ) . "\n" : false; | |
// Read IPN post data. | |
$raw_post_data = file_get_contents( 'php://input' ); | |
$raw_post_array = explode( '&', $raw_post_data ); | |
$post_array = array(); | |
// Assemble raw post data into array. | |
foreach ( $raw_post_array as $keyval ) { | |
$keyval = explode ( '=', $keyval ); | |
if ( 2 == count( $keyval ) ) | |
$post_array[ $keyval[0] ] = urldecode( $keyval[1] ); | |
} | |
// Prepare validation return response. | |
$encoded_data = 'cmd=_notify-validate'; | |
// Assemble validation return response. | |
foreach ( $post_array as $key => $value ) { | |
if ( function_exists( 'get_magic_quotes_gpc' ) && 1 == get_magic_quotes_gpc() ) { | |
$value = urlencode( stripslashes( $value ) ); | |
} else { | |
$value = urlencode( $value ); | |
} | |
$encoded_data .= "&$key=$value"; | |
} | |
// Post IPN data back to paypal to validate (requires CURL!). | |
$curl_result = $this->use_curl( $encoded_data ); | |
// Inspect IPN validation result and act accordingly. | |
// If the result was "VERIFIED" continue to process. | |
if ( 0 == strcmp( $curl_result, "VERIFIED" ) ) { | |
// Assign posted variables to local array. | |
$details = array(); | |
foreach ( $_POST as $key => $val ) { | |
$details[ $key ] = $val; | |
} | |
// User ID is passed through "custom" | |
$user_id = $details['custom']; | |
// If this is a recurring subscription signup notification, skip it and kill the script. | |
if ( isset( $details['txn_type'] ) && 'subscr_signup' == $details['txn_type'] ) { | |
die(); | |
} | |
if ( 1 == $wpmem_pp_sub->ipn_debug && $log_entry ) { | |
$txn_id = ( $details['txn_id'] == '' ) ? 'none given' : $details['txn_id']; | |
$payment_status = ( $details['payment_status'] == '' ) ? 'none given' : $details['payment_status']; | |
$log_entry .= "Transaction ID: " . $txn_id . "\n"; | |
$log_entry .= "Payment Status: " . $payment_status . "\n"; | |
if ( 'Pending' == $details['payment_status'] ) { | |
$log_entry .= "Pending reason: " . $_POST['pending_reason'] . "\n"; | |
} | |
} | |
/** | |
* Fire the IPN validation action. | |
* | |
* @since 0.9.0 | |
* | |
* @param array $details The IPN message details. | |
*/ | |
do_action( 'wpmem_ipn_validation', $details ); | |
// If no errors in validation, we are OK to process. | |
if ( ! $wpmem_pp_sub->ipn->error ) { | |
// What kind of transaction? | |
switch ( $details['payment_status'] ) { | |
case 'Completed': | |
$wpmem_exp = wpmem_set_exp( $user_id, true ); | |
$log_entry .= ( 1 == $wpmem_pp_sub->ipn_debug && $log_entry ) ? "Set date for user ID # " . $user_id . " to " . $wpmem_exp . "\n" : false; | |
break; | |
case 'Refunded': | |
if ( 1 == $wpmem_pp_sub->refunds ) { | |
$wpmem_exp = wpmem_exp_expire_user( $user_id ); | |
$log_entry .= ( 1 == $wpmem_pp_sub->ipn_debug && $log_entry ) ? "Expired user ID # " . $user_id . " to " . $wpmem_exp . "\n" : false; | |
} else { | |
$log_entry .= ( 1 == $wpmem_pp_sub->ipn_debug && $log_entry ) ? "No action on user ID # " . $user_id . "\n" : false; | |
} | |
break; | |
} | |
// Record transaction in the db. | |
$this->record_transaction( $details ); | |
/** | |
* Fires after successful IPN process. | |
* | |
* @since 0.6.0 | |
* @since 0.9.4 Added third param to identify action. | |
* | |
* @param int $user_id The user's numeric WP ID. | |
* @param array $details The PayPal transaction details. | |
* @param string Identifies the action. | |
*/ | |
do_action( 'wpmem_ipn_success', $user_id, $details, 'success' ); | |
} | |
} elseif ( strcmp( $curl_result, "INVALID" ) == 0 ) { | |
// The result was "INVALID" - log for manual investigation | |
$log_entry .= ( 1 == $wpmem_pp_sub->ipn_debug && $log_entry ) ?"INVALID\n" : false; | |
/** | |
* Fires after invalid IPN process. | |
* | |
* @since 0.6.0 | |
* @since 0.9.4 Added third param to identify action. | |
* | |
* @param int $user_id The user's numeric WP ID. | |
* @param array $details The PayPal transaction details. | |
* @param string Identifies the action. | |
*/ | |
do_action( 'wpmem_ipn_invalid', $user_id, $details, 'invalid' ); | |
} | |
// If logging, process the log entry. | |
if ( 1 == $wpmem_pp_sub->ipn_debug ) { | |
$this->do_log( $log_entry ); | |
} | |
} | |
/** | |
* Post to PayPal using cURL. | |
* | |
* @since 0.7.0 | |
* @access private | |
* | |
* @global object $wpmem_pp_sub The WP_Members_IPN object. | |
* | |
* @param string $encoded_data The data to post back to PayPal for validation. | |
* @return string $curl_result The result of the verification response (Completed|INVALID). | |
*/ | |
private function use_curl( $encoded_data ) { | |
global $wpmem_pp_sub; | |
$defaults = array( | |
'CURLOPT_HTTP_VERSION' => 'CURL_HTTP_VERSION_1_1', | |
'CURLOPT_POST' => '1', | |
'CURLOPT_RETURNTRANSFER' => '1', | |
'CURLOPT_POSTFIELDS' => $encoded_data, | |
'CURLOPT_SSL_VERIFYPEER' => '1', | |
'CURLOPT_SSL_VERIFYHOST' => '2', | |
'CURLOPT_FORBID_REUSE' => '1', | |
'CURLOPT_HTTPHEADER' => array( 'Connection: Close' ), | |
); | |
/** | |
* Filter settings for cURL options. | |
* | |
* @since 0.9.2 | |
*/ | |
$args = apply_filters( 'wpmem_exp_curl_options', '' ); | |
// Merge filtered options with defaults. | |
$args = wp_parse_args( $args, $defaults ); | |
$ch = curl_init( $wpmem_pp_sub->paypal_url ); | |
curl_setopt( $ch, CURLOPT_HTTP_VERSION, $args['CURLOPT_HTTP_VERSION'] ); | |
curl_setopt( $ch, CURLOPT_POST, $args['CURLOPT_POST'] ); | |
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, $args['CURLOPT_RETURNTRANSFER'] ); | |
curl_setopt( $ch, CURLOPT_POSTFIELDS, $args['CURLOPT_POSTFIELDS'] ); | |
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, $args['CURLOPT_SSL_VERIFYPEER'] ); | |
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, $args['CURLOPT_SSL_VERIFYHOST'] ); | |
curl_setopt( $ch, CURLOPT_FORBID_REUSE, $args['CURLOPT_FORBID_REUSE'] ); | |
curl_setopt( $ch, CURLOPT_HTTPHEADER, $args['CURLOPT_HTTPHEADER'] ); | |
if ( isset( $args['CURLOPT_SSLVERSION'] ) ) { | |
curl_setopt( $ch, CURLOPT_SSLVERSION, $args['CURLOPT_HTTPHEADER'] ); | |
} | |
if ( isset( $args['CURLOPT_SSL_CIPHER_LIST'] ) ) { | |
curl_setopt( $ch, CURLOPT_SSL_CIPHER_LIST, $args['CURLOPT_SSL_CIPHER_LIST'] ); | |
} | |
$curl_result = curl_exec( $ch ); | |
// If result is an error, log and kill processing. | |
if ( false === $curl_result ) { | |
if ( 1 == $wpmem_pp_sub->ipn_debug && $log_entry ) { | |
$log_entry.= 'Curl error: ' . curl_error( $ch ); | |
} | |
die( curl_error( $ch ) ); | |
} | |
// Done with curl. | |
curl_close( $ch ); | |
return $curl_result; | |
} | |
/** | |
* If logging is enabled, write the log file entries. | |
* | |
* @since 0.7.0 | |
* @access private | |
* | |
* @global object $wpmem_pp_sub The WP_Members_IPN object. | |
* | |
* @param string $log_entry The assembled log entry from the listener. | |
*/ | |
private function do_log( $log_entry ) { | |
global $wpmem_pp_sub; | |
// Check for errors. | |
if ( ! empty( $this->error ) ) { | |
$log_entry.= 'Errors present: '; | |
foreach ( $this->error as $key => $val ) { | |
$log_entry.= $key . '=>' . $val . ' '; | |
} | |
$log_entry.= "\n"; | |
} | |
// Open the log file. | |
$fp = fopen( $wpmem_pp_sub->log_file, 'a' ); | |
// Write the entry. | |
fwrite( $fp, $log_entry ); | |
// For verbose output, filter wpmem_exp_ipn_defaults to add array( 'verbose_log' => true ). | |
if ( isset( $wpmem_pp_sub->verbose_log ) && $wpmem_pp_sub->verbose_log === true ) { | |
fwrite( $fp, "---------------------------------------\n" ); | |
fwrite( $fp, "Verbose Output: \n" ); | |
foreach ( $_POST as $key => $val ) { | |
fwrite( $fp, $key . " => " . $val . "\n" ); | |
} | |
} | |
fwrite( $fp, "=======================================\n" ); | |
// Close the file. | |
fclose( $fp ); | |
} | |
/** | |
* IPN validation. | |
* | |
* IPN validation is triggered by the wpmem_ipn_validation action. Custom | |
* validation can be implemented by removing this function from the action | |
* hook and replacing it with a custom validation function. Care should be | |
* taken in custom validation to make sure that appropriate checks are done | |
* to prevent fraudulent transactions from being completed. | |
* | |
* @since 0.9.0 | |
* | |
* @global object $wpmem_pp_sub The WP_Members Subscription Object. | |
* @param array $details The IPN details. | |
*/ | |
function ipn_validation( $details ) { | |
global $wpmem_pp_sub; | |
$this->error = array(); | |
// Check whether the payment_status is Completed. | |
if ( 'Completed' != $details['payment_status'] && 'Refunded' != $details['payment_status'] ) { | |
$this->error['payment_status'] = $details['payment_status']; | |
} | |
// Check that receiver_email is your Primary PayPal email. | |
if ( $details['receiver_email'] != $wpmem_pp_sub->paypal_id ) { | |
$this->error['receiver_email'] = $details['receiver_email'] . "|" . $wpmem_pp_sub->paypal_id; | |
} | |
// Check that payment_amount/payment_currency are correct. | |
//if ( $details['mc_gross'] != $wpmem_pp_sub->subscriptions['default']['subscription_cost'] ) { | |
// if ( 'Completed' == $details['payment_status'] ) { | |
// $this->error['mc_gross'] = $details['mc_gross'] . "|" . $wpmem_pp_sub->subscriptions['default']['subscription_cost']; | |
// } | |
//} | |
// Check that txn_id is not empty (recurring initialization has no txn_id). | |
if ( '' == $details['txn_id'] ) { | |
$this->error['txn_id'] = 'no txn_id'; | |
} | |
} | |
/** | |
* Records the transaction data. | |
* | |
* @since 0.9.4 | |
*/ | |
function record_transaction( $details ) { | |
global $wpdb; | |
$sql_cols = "user_id, timestamp, "; | |
$sql_vals = "'" . $details['custom'] . "','" . current_time( 'mysql' ) . "',"; | |
$txn_cols = $this->record_transaction_columns(); | |
foreach ( $txn_cols as $column ) { | |
if ( isset( $details[ $column ] ) ) { | |
$sql_cols.= $column . ","; | |
$sql_vals.= "'" . $details[ $column ] . "',"; | |
} | |
} | |
$sql = "INSERT INTO " . $wpdb->prefix . 'wpmem_paypal_transactions' . " ( " . rtrim( $sql_cols, ',' ) . " ) VALUES ( " . rtrim( $sql_vals, ',' ) . " )"; | |
$result = $wpdb->query( $sql ); | |
// Log errors. | |
if ( 1 == $wpmem_pp_sub->ipn_debug ) { | |
if ( ! $result || null == $result || empty( $result ) ) { | |
$log_entry = "Error writing transaction log to database \r\n"; | |
if ( isset( $wpmem_pp_sub->verbose_log ) && $wpmem_pp_sub->verbose_log === true ) { | |
$log_entry.= "SQL Query: \r\n" . $sql . "\r\n"; | |
} | |
$this->do_log( $log_entry ); | |
} | |
} | |
return; | |
} | |
/** | |
* Logs IPN messages in db. | |
* | |
* @since 0.9.4 | |
* | |
* @global object $wpdb | |
* @param int $user_id | |
* @param string $details | |
* @param string $action | |
*/ | |
function ipn_db_log( $user_id, $details, $action ) { | |
global $wpdb; | |
$status = ( isset( $details['payment_status'] ) ) ? $details['payment_status'] : ''; | |
$reason = ( isset( $details['pending_reason'] ) ) ? $details['pending_reason'] : ''; | |
$data = http_build_query( $details ); | |
$sql = "INSERT INTO " . $wpdb->prefix . 'wpmem_ipn_messages' . " ( timestamp, result, payment_status, pending_reason, ipn_detail ) VALUES ( " | |
. '"' . current_time( 'mysql' ) . ',' | |
. '"' . $action . '",' | |
. '"' . $status . '",' | |
. '"' . $reason . '",' | |
. '"' . $data . '" ' | |
. " )"; | |
$result = $wpdb->query( $sql ); | |
} | |
/** | |
* Possible columns for transaction recording. | |
* | |
* @since 0.9.4 | |
* | |
* @return array $columns | |
*/ | |
function record_transaction_columns() { | |
$columns = array( | |
'payment_date', | |
'receiver_email', | |
'item_name', | |
'item_number', | |
'payment_status', | |
'pending_reason', | |
'mc_gross', | |
'mc_fee', | |
'tax', | |
'mc_currency', | |
'txn_id', | |
'txn_type', | |
'transaction_subject', | |
'first_name', | |
'last_name', | |
'address_name', | |
'address_street', | |
'address_city', | |
'address_state', | |
'address_zip', | |
'address_country', | |
'address_country_code', | |
'residence_country', | |
'address_status', | |
'payer_email', | |
'payer_status', | |
'payment_type', | |
'payment_gross', | |
'payment_fee', | |
'notify_version', | |
'verify_sign', | |
'referrer_id', | |
'business', | |
'ipn_track_id', | |
); | |
return $columns; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment