| 1 |
<?php |
| 2 |
// @codingStandardsIgnoreFile |
| 3 |
/** |
| 4 |
* s2Member's PayPal IPN handler (inner processing routines). |
| 5 |
* |
| 6 |
* Copyright: © 2009-2011 |
| 7 |
* {@link http://websharks-inc.com/ WebSharks, Inc.} |
| 8 |
* (coded in the USA) |
| 9 |
* |
| 10 |
* Released under the terms of the GNU General Public License. |
| 11 |
* You should have received a copy of the GNU General Public License, |
| 12 |
* along with this software. In the main directory, see: /licensing/ |
| 13 |
* If not, see: {@link http://www.gnu.org/licenses/}. |
| 14 |
* |
| 15 |
* @package s2Member\PayPal |
| 16 |
* @since 3.5 |
| 17 |
*/ |
| 18 |
if(!defined('WPINC')) // MUST have WordPress. |
| 19 |
exit ('Do not access this file directly.'); |
| 20 |
|
| 21 |
if(!class_exists('c_ws_plugin__s2member_paypal_notify_in')) |
| 22 |
{ |
| 23 |
/** |
| 24 |
* s2Member's PayPal IPN handler (inner processing routines). |
| 25 |
* |
| 26 |
* @package s2Member\PayPal |
| 27 |
* @since 3.5 |
| 28 |
*/ |
| 29 |
class c_ws_plugin__s2member_paypal_notify_in |
| 30 |
{ |
| 31 |
//260907.2110 TO-DO: Before final PayPal Standard deprecation, review IPN/return replay protection against the shared fulfillment/dedupe invariants; provider-hosted button creation itself does not need Gateway Checkout creation orchestration. |
| 32 |
/** |
| 33 |
* Handles PayPal IPN processing. |
| 34 |
* |
| 35 |
* These same routines also handle s2Member Pro/PayPal Pro operations; |
| 36 |
* giving you the ability *(as needed)* to Hook into these routines using |
| 37 |
* WordPress Hooks/Filters; as seen in the source code below. |
| 38 |
* |
| 39 |
* Please do NOT modify the source code directly. |
| 40 |
* Instead, use WordPress Hooks/Filters. |
| 41 |
* |
| 42 |
* For example, if you'd like to add your own custom conditionals, use: |
| 43 |
* ``add_filter ('ws_plugin__s2member_during_paypal_notify_conditionals', 'your_function');`` |
| 44 |
* |
| 45 |
* @package s2Member\PayPal |
| 46 |
* @since 3.5 |
| 47 |
* |
| 48 |
* @attaches-to ``add_action('init');`` |
| 49 |
*/ |
| 50 |
public static function paypal_notify() |
| 51 |
{ |
| 52 |
global $current_site, $current_blog; |
| 53 |
|
| 54 |
do_action('ws_plugin__s2member_before_paypal_notify', get_defined_vars()); |
| 55 |
|
| 56 |
//260831.0135 Normalize optional s2Member proxy fields once; direct PayPal IPNs omit them, while proxied gateway integrations provide strings. |
| 57 |
if(!isset($_REQUEST['s2member_paypal_proxy']) || !is_string($_REQUEST['s2member_paypal_proxy'])) |
| 58 |
$_REQUEST['s2member_paypal_proxy'] = ''; |
| 59 |
if(!isset($_REQUEST['s2member_paypal_proxy_use']) || !is_string($_REQUEST['s2member_paypal_proxy_use'])) |
| 60 |
$_REQUEST['s2member_paypal_proxy_use'] = ''; |
| 61 |
|
| 62 |
if(!empty($_GET['s2member_paypal_notify']) && ($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_business'] || !empty($_REQUEST['s2member_paypal_proxy']))) |
| 63 |
{ |
| 64 |
@ignore_user_abort(TRUE); // Important. Continue processing even if/when the connection is broken by the sending party. |
| 65 |
|
| 66 |
include_once ABSPATH.'wp-admin/includes/admin.php'; // Get administrative functions. Needed for `wp_delete_user()`. |
| 67 |
|
| 68 |
$email_configs_were_on = c_ws_plugin__s2member_email_configs::email_config_status(); // Filters on? |
| 69 |
c_ws_plugin__s2member_email_configs::email_config_release(); // Release s2Member Filters. |
| 70 |
|
| 71 |
$paypal = array(); // Initialize PayPal array; we also reference this with a variable for a possible proxy handler. |
| 72 |
if(!empty($_REQUEST['s2member_paypal_proxy']) && in_array($_REQUEST['s2member_paypal_proxy'], array('alipay', 'stripe', 'authnet', 'clickbank', 'ccbill', 'google'), TRUE)) |
| 73 |
${esc_html(trim(stripslashes($_REQUEST['s2member_paypal_proxy'])))} = & $paypal; // Internal alias by reference. |
| 74 |
|
| 75 |
if(is_array($paypal = c_ws_plugin__s2member_paypal_utilities::paypal_postvars()) && ($_paypal = $paypal) && ($_paypal_s = serialize($_paypal))) |
| 76 |
{ |
| 77 |
$paypal['s2member_log'][] = 'IPN received on: '.date('D M j, Y g:i:s a T'); |
| 78 |
$paypal['s2member_log'][] = 's2Member POST vars verified '.((!empty($paypal['proxy_verified'])) ? 'with a Proxy Key' : 'through a POST back to PayPal.'); |
| 79 |
|
| 80 |
$payment_status_issues = '/^(failed|denied|expired|refunded|partially_refunded|reversed|reversal|canceled_reversal|voided)$/i'; |
| 81 |
|
| 82 |
$paypal['subscr_gateway'] = (!empty($_REQUEST['s2member_paypal_proxy'])) ? esc_html(trim(stripslashes($_REQUEST['s2member_paypal_proxy']))) : 'paypal'; |
| 83 |
|
| 84 |
$coupon = (!empty($_REQUEST['s2member_paypal_proxy_coupon']) && is_array($_REQUEST['s2member_paypal_proxy_coupon'])) ? stripslashes_deep($_REQUEST['s2member_paypal_proxy_coupon']) : array(); |
| 85 |
$coupon = (isset($coupon['full_coupon_code'], $coupon['coupon_code'], $coupon['affiliate_id']) && is_string($coupon['full_coupon_code']) && is_string($coupon['coupon_code']) && is_string($coupon['affiliate_id'])) ? $coupon : array('full_coupon_code' => '', 'coupon_code' => '', 'affiliate_id' => ''); |
| 86 |
$coupon = c_ws_plugin__s2member_utils_strings::strip_php_tags_deep($coupon); //260910.2249 Normalize proxy coupon data consistently with other notification values. |
| 87 |
|
| 88 |
if(!empty($paypal['txn_type']) && $paypal['txn_type'] === 'merch_pmt') |
| 89 |
// This is mostly irrelevant, but it helps to keep the logs cleaner. |
| 90 |
sleep(15); // Wait for Pro-Form procesing to complete. |
| 91 |
|
| 92 |
if(empty($paypal['custom']) && !empty($paypal['recurring_payment_id'])) // Recurring Profile ID. |
| 93 |
$paypal['custom'] = c_ws_plugin__s2member_utils_users::get_user_custom_with($paypal['recurring_payment_id']); |
| 94 |
|
| 95 |
else if(empty($paypal['custom']) && !empty($paypal['mp_id'])) // Billing Agreement ID. |
| 96 |
$paypal['custom'] = c_ws_plugin__s2member_utils_users::get_user_custom_with($paypal['mp_id']); |
| 97 |
|
| 98 |
//250522 At least set it to an empty string. |
| 99 |
else if(empty($paypal['custom'])) |
| 100 |
$paypal['custom'] = ''; |
| 101 |
|
| 102 |
//260910.0356 Resolve the expected host consistently across different web-server request environments. |
| 103 |
$ipn_host = (!empty($_SERVER['HTTP_HOST']) && is_string($_SERVER['HTTP_HOST'])) ? preg_replace('/\:([0-9]+)$/', '', $_SERVER['HTTP_HOST']) : (string)parse_url(home_url('/'), PHP_URL_HOST); |
| 104 |
|
| 105 |
//250606 Added option to skip domain validation. |
| 106 |
if ($GLOBALS['WS_PLUGIN__']['s2member']['o']['skip_ipn_domain_validation'] |
| 107 |
|| ($ipn_host !== '' && !empty($paypal['custom']) && preg_match('/^'.preg_quote($ipn_host, '/').'/i', $paypal['custom']))) |
| 108 |
{ |
| 109 |
if ($GLOBALS['WS_PLUGIN__']['s2member']['o']['skip_ipn_domain_validation']) |
| 110 |
$paypal['s2member_log'][] = 's2Member originating domain validation was skipped.'; |
| 111 |
else |
| 112 |
$paypal['s2member_log'][] = 's2Member originating domain (`$_SERVER["HTTP_HOST"]`) validated.'; |
| 113 |
|
| 114 |
foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v; |
| 115 |
if(!apply_filters('ws_plugin__s2member_during_paypal_notify_conditionals', FALSE, get_defined_vars())) |
| 116 |
{ |
| 117 |
unset($__refs, $__v); // From the filter above. |
| 118 |
|
| 119 |
if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_virtual_terminal::cp(get_defined_vars()))) |
| 120 |
$paypal = $_paypal_cp; |
| 121 |
|
| 122 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_express_checkout::cp(get_defined_vars()))) |
| 123 |
$paypal = $_paypal_cp; |
| 124 |
|
| 125 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_cart::cp(get_defined_vars()))) |
| 126 |
$paypal = $_paypal_cp; |
| 127 |
|
| 128 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_send_money::cp(get_defined_vars()))) |
| 129 |
$paypal = $_paypal_cp; |
| 130 |
|
| 131 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_web_accept_sp::cp(get_defined_vars()))) |
| 132 |
$paypal = $_paypal_cp; |
| 133 |
|
| 134 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_wa_ccaps_wo_level::cp(get_defined_vars()))) |
| 135 |
$paypal = $_paypal_cp; |
| 136 |
|
| 137 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_wa_w_level::cp(get_defined_vars()))) |
| 138 |
$paypal = $_paypal_cp; |
| 139 |
|
| 140 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_rec_profile_creation_w_level::cp(get_defined_vars()))) |
| 141 |
$paypal = $_paypal_cp; |
| 142 |
|
| 143 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_modify_w_level::cp(get_defined_vars()))) |
| 144 |
$paypal = $_paypal_cp; |
| 145 |
|
| 146 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_rp_payment_w_level::cp(get_defined_vars()))) |
| 147 |
$paypal = $_paypal_cp; |
| 148 |
|
| 149 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_rp_payment_failed_w_level::cp(get_defined_vars()))) |
| 150 |
$paypal = $_paypal_cp; |
| 151 |
|
| 152 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_rp_cancellation_w_level::cp(get_defined_vars()))) |
| 153 |
$paypal = $_paypal_cp; |
| 154 |
|
| 155 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_rp_eots_w_level::cp(get_defined_vars()))) |
| 156 |
$paypal = $_paypal_cp; |
| 157 |
|
| 158 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_sp_refund_reversal::cp(get_defined_vars()))) |
| 159 |
$paypal = $_paypal_cp; |
| 160 |
|
| 161 |
else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_billing_agreement_signup::cp(get_defined_vars()))) |
| 162 |
$paypal = $_paypal_cp; |
| 163 |
|
| 164 |
else // Ignoring this IPN request. The txn_type/status does NOT require any action. |
| 165 |
$paypal['s2member_log'][] = 'Ignoring this IPN request. The `txn_type/status` does NOT require any action on the part of s2Member.'; |
| 166 |
} |
| 167 |
else unset($__refs, $__v); // Else a custom conditional has been applied by Filters. |
| 168 |
} |
| 169 |
else if(!empty($paypal['txn_type']) && preg_match('/^recurring_payment_profile_cancel$/i', $paypal['txn_type'])) |
| 170 |
{ |
| 171 |
$paypal['s2member_log'][] = 'Transaction type (`recurring_payment_profile_cancel`), but there is no match to an existing account; so verification of `$_SERVER["HTTP_HOST"]` was not possible.'; |
| 172 |
$paypal['s2member_log'][] = 'It\'s likely this account was just upgraded/downgraded by s2Member Pro; so the Subscr. ID has probably been updated on-site; nothing to worry about here.'; |
| 173 |
} |
| 174 |
else if(!empty($paypal['txn_type']) && preg_match('/^recurring_/i', $paypal['txn_type'])) // Otherwise, is this a ^recurring_ txn_type? |
| 175 |
$paypal['s2member_log'][] = 'Transaction type (`^recurring_?`), but there is no match to an existing account; so verification of `$_SERVER["HTTP_HOST"]` was not possible.'; |
| 176 |
|
| 177 |
else // Else, use the default ``$_SERVER['HTTP_HOST']`` error. |
| 178 |
$paypal['s2member_log'][] = 'Unable to verify `$_SERVER["HTTP_HOST"]`. Please check the `custom` value in your Button Code. It MUST start with your domain name.'; |
| 179 |
} |
| 180 |
else // Extensive log reporting here. This is an area where many site owners find trouble. Depending on server configuration; remote HTTPS connections may fail. |
| 181 |
{ |
| 182 |
if (!empty($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_SERVER['HTTP_USER_AGENT'])) { |
| 183 |
if (preg_match('/(msie|trident|gecko|webkit|presto|konqueror|playstation)[\/ ]([0-9\.]+)/i', $_SERVER['HTTP_USER_AGENT'])) { |
| 184 |
$paypal['s2member_indicator'] = 'This PayPal IPN Handler by s2Member® is active & listening.'; |
| 185 |
} |
| 186 |
} |
| 187 |
$paypal['s2member_log'][] = 'Unable to verify $_POST vars. This is most likely related to an invalid configuration of s2Member, or a problem with server compatibility.'; |
| 188 |
$paypal['s2member_log'][] = 'Please see this KB article: `http://www.s2member.com/kb/server-scanner/`. We suggest that you run the s2Member Server Scanner.'; |
| 189 |
$paypal['s2member_log'][] = var_export($_REQUEST, TRUE); // Recording _POST + _GET vars for analysis and debugging. |
| 190 |
} |
| 191 |
if($email_configs_were_on) // Back on? |
| 192 |
c_ws_plugin__s2member_email_configs::email_config(); |
| 193 |
/* |
| 194 |
Add IPN proxy (when available) to the ``$paypal`` array. |
| 195 |
*/ |
| 196 |
if(!empty($_REQUEST['s2member_paypal_proxy'])) |
| 197 |
$paypal['s2member_paypal_proxy'] = esc_html(trim(stripslashes((string)$_REQUEST['s2member_paypal_proxy']))); |
| 198 |
/* |
| 199 |
Add IPN proxy use vars (when available) to the ``$paypal`` array. |
| 200 |
*/ |
| 201 |
if(!empty($_REQUEST['s2member_paypal_proxy_use'])) |
| 202 |
$paypal['s2member_paypal_proxy_use'] = esc_html(trim(stripslashes((string)$_REQUEST['s2member_paypal_proxy_use']))); |
| 203 |
/* |
| 204 |
Add IPN proxy coupon vars (when available) to the ``$paypal`` array. |
| 205 |
*/ |
| 206 |
if(!empty($_REQUEST['s2member_paypal_proxy_coupon'])) |
| 207 |
$paypal['s2member_paypal_proxy_coupon'] = stripslashes_deep((array)$_REQUEST['s2member_paypal_proxy_coupon']); |
| 208 |
/* |
| 209 |
Also add IPN proxy self-verification (when available) to the ``$paypal`` array. |
| 210 |
*/ |
| 211 |
if(!empty($_REQUEST['s2member_paypal_proxy_verification'])) |
| 212 |
$paypal['s2member_paypal_proxy_verification'] = esc_html(trim(stripslashes((string)$_REQUEST['s2member_paypal_proxy_verification']))); |
| 213 |
/* |
| 214 |
Log this IPN post-processing event now. |
| 215 |
*/ |
| 216 |
c_ws_plugin__s2member_utils_logs::log_entry('gateway-core-ipn', $paypal); |
| 217 |
/* |
| 218 |
Hook during core IPN post-processing might be useful for developers. |
| 219 |
*/ |
| 220 |
foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v; |
| 221 |
do_action('ws_plugin__s2member_during_paypal_notify', get_defined_vars()); |
| 222 |
unset($__refs, $__v); |
| 223 |
/* |
| 224 |
260322 Log effective response state before/after forcing a 200. |
| 225 |
Output response headers & content body. |
| 226 |
*/ |
| 227 |
$paypal['s2member_log'][] = 'Pre-response `headers_sent`: '.((headers_sent($headers_sent_file, $headers_sent_line)) ? 'yes @ '.$headers_sent_file.':'.$headers_sent_line : 'no'); |
| 228 |
$paypal['s2member_log'][] = 'Pre-response `http_response_code()`: '.((function_exists('http_response_code')) ? (string)http_response_code() : 'n/a'); |
| 229 |
$paypal['s2member_log'][] = 'Pre-response headers: '.var_export(headers_list(), TRUE); |
| 230 |
|
| 231 |
while(@ob_end_clean()); // Clean output buffers. |
| 232 |
|
| 233 |
if(function_exists('http_response_code')) |
| 234 |
http_response_code(200); |
| 235 |
|
| 236 |
if(!empty($_SERVER['SERVER_PROTOCOL'])) |
| 237 |
header($_SERVER['SERVER_PROTOCOL'].' 200 OK', TRUE, 200); |
| 238 |
else |
| 239 |
header('HTTP/1.1 200 OK', TRUE, 200); |
| 240 |
|
| 241 |
status_header(200); // OK status code. |
| 242 |
header('Content-Type: text/plain; charset=UTF-8'); |
| 243 |
|
| 244 |
$paypal['s2member_log'][] = 'Post-response `http_response_code()`: '.((function_exists('http_response_code')) ? (string)http_response_code() : 'n/a'); |
| 245 |
$paypal['s2member_log'][] = 'Post-response headers: '.var_export(headers_list(), TRUE); |
| 246 |
|
| 247 |
if (!empty($paypal['s2member_paypal_proxy_return_url'])) { |
| 248 |
if(preg_match('/^https?%3A/ui', $paypal['s2member_paypal_proxy_return_url'])) { |
| 249 |
$paypal['s2member_paypal_proxy_return_url'] = urldecode($paypal['s2member_paypal_proxy_return_url']); |
| 250 |
} // See: <https://github.com/websharks/s2member/issues/1024> |
| 251 |
exit($paypal['s2member_paypal_proxy_return_url']); |
| 252 |
} elseif (!empty($paypal['s2member_indicator'])) { |
| 253 |
exit($paypal['s2member_indicator']); |
| 254 |
} else { |
| 255 |
exit(); // Default behavior. |
| 256 |
} |
| 257 |
} |
| 258 |
foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v; |
| 259 |
do_action('ws_plugin__s2member_after_paypal_notify', get_defined_vars()); |
| 260 |
unset($__refs, $__v); |
| 261 |
} |
| 262 |
} |
| 263 |
} |
| 264 |
|