PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / 260909
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions v260909
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 260909, at src/includes/classes/paypal-notify-in.inc.php

260 lines 13.9 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
87 if(!empty($paypal['txn_type']) && $paypal['txn_type'] === 'merch_pmt')
88 // This is mostly irrelevant, but it helps to keep the logs cleaner.
89 sleep(15); // Wait for Pro-Form procesing to complete.
90
91 if(empty($paypal['custom']) && !empty($paypal['recurring_payment_id'])) // Recurring Profile ID.
92 $paypal['custom'] = c_ws_plugin__s2member_utils_users::get_user_custom_with($paypal['recurring_payment_id']);
93
94 else if(empty($paypal['custom']) && !empty($paypal['mp_id'])) // Billing Agreement ID.
95 $paypal['custom'] = c_ws_plugin__s2member_utils_users::get_user_custom_with($paypal['mp_id']);
96
97 //250522 At least set it to an empty string.
98 else if(empty($paypal['custom']))
99 $paypal['custom'] = '';
100
101 //250606 Added option to skip domain validation.
102 if ($GLOBALS['WS_PLUGIN__']['s2member']['o']['skip_ipn_domain_validation']
103 || (!empty($paypal['custom']) && preg_match('/^'.preg_quote(preg_replace('/\:([0-9]+)$/', '', $_SERVER['HTTP_HOST']), '/').'/i', $paypal['custom'])))
104 {
105 if ($GLOBALS['WS_PLUGIN__']['s2member']['o']['skip_ipn_domain_validation'])
106 $paypal['s2member_log'][] = 's2Member originating domain validation was skipped.';
107 else
108 $paypal['s2member_log'][] = 's2Member originating domain (`$_SERVER["HTTP_HOST"]`) validated.';
109
110 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
111 if(!apply_filters('ws_plugin__s2member_during_paypal_notify_conditionals', FALSE, get_defined_vars()))
112 {
113 unset($__refs, $__v); // From the filter above.
114
115 if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_virtual_terminal::cp(get_defined_vars())))
116 $paypal = $_paypal_cp;
117
118 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_express_checkout::cp(get_defined_vars())))
119 $paypal = $_paypal_cp;
120
121 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_cart::cp(get_defined_vars())))
122 $paypal = $_paypal_cp;
123
124 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_send_money::cp(get_defined_vars())))
125 $paypal = $_paypal_cp;
126
127 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_web_accept_sp::cp(get_defined_vars())))
128 $paypal = $_paypal_cp;
129
130 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_wa_ccaps_wo_level::cp(get_defined_vars())))
131 $paypal = $_paypal_cp;
132
133 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_wa_w_level::cp(get_defined_vars())))
134 $paypal = $_paypal_cp;
135
136 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_rec_profile_creation_w_level::cp(get_defined_vars())))
137 $paypal = $_paypal_cp;
138
139 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_modify_w_level::cp(get_defined_vars())))
140 $paypal = $_paypal_cp;
141
142 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_rp_payment_w_level::cp(get_defined_vars())))
143 $paypal = $_paypal_cp;
144
145 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_rp_payment_failed_w_level::cp(get_defined_vars())))
146 $paypal = $_paypal_cp;
147
148 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_rp_cancellation_w_level::cp(get_defined_vars())))
149 $paypal = $_paypal_cp;
150
151 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_subscr_or_rp_eots_w_level::cp(get_defined_vars())))
152 $paypal = $_paypal_cp;
153
154 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_sp_refund_reversal::cp(get_defined_vars())))
155 $paypal = $_paypal_cp;
156
157 else if(($_paypal_cp = c_ws_plugin__s2member_paypal_notify_in_billing_agreement_signup::cp(get_defined_vars())))
158 $paypal = $_paypal_cp;
159
160 else // Ignoring this IPN request. The txn_type/status does NOT require any action.
161 $paypal['s2member_log'][] = 'Ignoring this IPN request. The `txn_type/status` does NOT require any action on the part of s2Member.';
162 }
163 else unset($__refs, $__v); // Else a custom conditional has been applied by Filters.
164 }
165 else if(!empty($paypal['txn_type']) && preg_match('/^recurring_payment_profile_cancel$/i', $paypal['txn_type']))
166 {
167 $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.';
168 $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.';
169 }
170 else if(!empty($paypal['txn_type']) && preg_match('/^recurring_/i', $paypal['txn_type'])) // Otherwise, is this a ^recurring_ txn_type?
171 $paypal['s2member_log'][] = 'Transaction type (`^recurring_?`), but there is no match to an existing account; so verification of `$_SERVER["HTTP_HOST"]` was not possible.';
172
173 else // Else, use the default ``$_SERVER['HTTP_HOST']`` error.
174 $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.';
175 }
176 else // Extensive log reporting here. This is an area where many site owners find trouble. Depending on server configuration; remote HTTPS connections may fail.
177 {
178 if (!empty($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_SERVER['HTTP_USER_AGENT'])) {
179 if (preg_match('/(msie|trident|gecko|webkit|presto|konqueror|playstation)[\/ ]([0-9\.]+)/i', $_SERVER['HTTP_USER_AGENT'])) {
180 $paypal['s2member_indicator'] = 'This PayPal IPN Handler by s2Member® is active & listening.';
181 }
182 }
183 $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.';
184 $paypal['s2member_log'][] = 'Please see this KB article: `http://www.s2member.com/kb/server-scanner/`. We suggest that you run the s2Member Server Scanner.';
185 $paypal['s2member_log'][] = var_export($_REQUEST, TRUE); // Recording _POST + _GET vars for analysis and debugging.
186 }
187 if($email_configs_were_on) // Back on?
188 c_ws_plugin__s2member_email_configs::email_config();
189 /*
190 Add IPN proxy (when available) to the ``$paypal`` array.
191 */
192 if(!empty($_REQUEST['s2member_paypal_proxy']))
193 $paypal['s2member_paypal_proxy'] = esc_html(trim(stripslashes((string)$_REQUEST['s2member_paypal_proxy'])));
194 /*
195 Add IPN proxy use vars (when available) to the ``$paypal`` array.
196 */
197 if(!empty($_REQUEST['s2member_paypal_proxy_use']))
198 $paypal['s2member_paypal_proxy_use'] = esc_html(trim(stripslashes((string)$_REQUEST['s2member_paypal_proxy_use'])));
199 /*
200 Add IPN proxy coupon vars (when available) to the ``$paypal`` array.
201 */
202 if(!empty($_REQUEST['s2member_paypal_proxy_coupon']))
203 $paypal['s2member_paypal_proxy_coupon'] = stripslashes_deep((array)$_REQUEST['s2member_paypal_proxy_coupon']);
204 /*
205 Also add IPN proxy self-verification (when available) to the ``$paypal`` array.
206 */
207 if(!empty($_REQUEST['s2member_paypal_proxy_verification']))
208 $paypal['s2member_paypal_proxy_verification'] = esc_html(trim(stripslashes((string)$_REQUEST['s2member_paypal_proxy_verification'])));
209 /*
210 Log this IPN post-processing event now.
211 */
212 c_ws_plugin__s2member_utils_logs::log_entry('gateway-core-ipn', $paypal);
213 /*
214 Hook during core IPN post-processing might be useful for developers.
215 */
216 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
217 do_action('ws_plugin__s2member_during_paypal_notify', get_defined_vars());
218 unset($__refs, $__v);
219 /*
220 260322 Log effective response state before/after forcing a 200.
221 Output response headers & content body.
222 */
223 $paypal['s2member_log'][] = 'Pre-response `headers_sent`: '.((headers_sent($headers_sent_file, $headers_sent_line)) ? 'yes @ '.$headers_sent_file.':'.$headers_sent_line : 'no');
224 $paypal['s2member_log'][] = 'Pre-response `http_response_code()`: '.((function_exists('http_response_code')) ? (string)http_response_code() : 'n/a');
225 $paypal['s2member_log'][] = 'Pre-response headers: '.var_export(headers_list(), TRUE);
226
227 while(@ob_end_clean()); // Clean output buffers.
228
229 if(function_exists('http_response_code'))
230 http_response_code(200);
231
232 if(!empty($_SERVER['SERVER_PROTOCOL']))
233 header($_SERVER['SERVER_PROTOCOL'].' 200 OK', TRUE, 200);
234 else
235 header('HTTP/1.1 200 OK', TRUE, 200);
236
237 status_header(200); // OK status code.
238 header('Content-Type: text/plain; charset=UTF-8');
239
240 $paypal['s2member_log'][] = 'Post-response `http_response_code()`: '.((function_exists('http_response_code')) ? (string)http_response_code() : 'n/a');
241 $paypal['s2member_log'][] = 'Post-response headers: '.var_export(headers_list(), TRUE);
242
243 if (!empty($paypal['s2member_paypal_proxy_return_url'])) {
244 if(preg_match('/^https?%3A/ui', $paypal['s2member_paypal_proxy_return_url'])) {
245 $paypal['s2member_paypal_proxy_return_url'] = urldecode($paypal['s2member_paypal_proxy_return_url']);
246 } // See: <https://github.com/websharks/s2member/issues/1024>
247 exit($paypal['s2member_paypal_proxy_return_url']);
248 } elseif (!empty($paypal['s2member_indicator'])) {
249 exit($paypal['s2member_indicator']);
250 } else {
251 exit(); // Default behavior.
252 }
253 }
254 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
255 do_action('ws_plugin__s2member_after_paypal_notify', get_defined_vars());
256 unset($__refs, $__v);
257 }
258 }
259 }
260