Skip to content

Instantly share code, notes, and snippets.

@butlerblog
Last active January 16, 2019 19:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save butlerblog/feda5f705daeb37e91d691cfd86b102e to your computer and use it in GitHub Desktop.
Save butlerblog/feda5f705daeb37e91d691cfd86b102e to your computer and use it in GitHub Desktop.
Remove price validation check in IPN listener
<?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