PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / 260917
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions v260917
260917 260913 260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 All 189 releases
s2member / src / includes / classes / paypal-notify-in.inc.php

paypal-notify-in.inc.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions 260917, at src/includes/classes/paypal-notify-in.inc.php

264 lines 14.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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