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 / menu-pages / paypal-ops.inc.php

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

1,962 lines 152.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 /**
4 * Menu page for the s2Member plugin (PayPal Options page).
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\Menu_Pages
16 * @since 3.0
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_menu_page_paypal_ops"))
22 {
23 /**
24 * Menu page for the s2Member plugin (PayPal Options page).
25 *
26 * @package s2Member\Menu_Pages
27 * @since 110531
28 */
29 class c_ws_plugin__s2member_menu_page_paypal_ops
30 {
31 public function __construct()
32 {
33 $ppco_webhook_notice = '';
34 $ppco_creds_notice = '';
35 $ppco_cache_notice = '';
36
37 $ppco_r = !empty($_POST) ? $_POST : $_GET;
38 if(!empty($ppco_r['s2member_ppco_webhook']) && empty($ppco_r['s2member_ppco_creds_test']) && empty($ppco_r['s2member_ppco_clear_cache']) && current_user_can('manage_options'))
39 {
40 if(!empty($ppco_r['_wpnonce']) && wp_verify_nonce((string)$ppco_r['_wpnonce'], 's2member_ppco_webhook'))
41 {
42 $env = (!empty($ppco_r['ppco_webhook_env']) && $ppco_r['ppco_webhook_env'] === 'sandbox') ? 'sandbox' : 'live';
43 $env_label = ($env === 'sandbox') ? esc_html__('Sandbox', 's2member') : esc_html__('Live', 's2member');
44
45 $client_id = '';
46 $secret = '';
47
48 if($env === 'sandbox')
49 {
50 $client_id = (string)$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_id"];
51 $secret = (string)$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_secret"];
52 }
53 else // live
54 {
55 $client_id = (string)$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_id"];
56 $secret = (string)$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_secret"];
57 }
58
59 if(!empty($client_id) && !empty($secret))
60 {
61 $scheme = strtolower((string)c_ws_plugin__s2member_utils_urls::parse_url(home_url('/'), PHP_URL_SCHEME));
62
63 //260224 Reverse-proxy/Cloudflare fallback for admin-side HTTPS detection.
64 // This only affects the admin gating check here. It does NOT change the webhook URL itself.
65 $proxy_https = false;
66 if(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && stripos(strtolower((string)$_SERVER['HTTP_X_FORWARDED_PROTO']), 'https') !== false)
67 $proxy_https = true;
68 else if(!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && strtolower((string)$_SERVER['HTTP_X_FORWARDED_SSL']) === 'on')
69 $proxy_https = true;
70 else if(!empty($_SERVER['HTTPS']) && in_array(strtolower((string)$_SERVER['HTTPS']), array('on', '1'), true))
71 $proxy_https = true;
72 else if(!empty($_SERVER['HTTP_CF_VISITOR']) && stripos((string)$_SERVER['HTTP_CF_VISITOR'], '"scheme":"https"') !== false)
73 $proxy_https = true;
74
75 if($scheme !== 'https' && !$proxy_https)
76 $ppco_webhook_notice = '<div class="error"><p>'.esc_html__('Unable to create/update PayPal Checkout webhook. PayPal requires an HTTPS (SSL) webhook URL, but your site URL appears to be configured without HTTPS. Please enable SSL and update your WordPress Site URL/Home URL to use https://, then try again.', 's2member').'</p></div>'."\n";
77 else
78 {
79 //260216 IMPORTANT:
80 // Do not block webhook creation/update based on a server-side "loopback" probe.
81 // Many shared hosts/WAFs return false negatives for loopback requests (e.g., HEAD blocked,
82 // ModSecurity rules, disabled loopback/outbound HTTP). PayPal can still deliver webhooks
83 // successfully from the outside.
84
85 $r = c_ws_plugin__s2member_paypal_utilities::paypal_checkout_webhook_upsert($env);
86
87 if(!empty($r['id']))
88 {
89 $ppco_webhook_notice = '<div class="updated"><p>'.sprintf(esc_html__('PayPal Checkout %1$s webhook created/updated successfully.', 's2member'), $env_label).'</p></div>'."\n";
90
91 //260824.0507 The persistent warning was rendered before this page action ran; remove that now-resolved copy immediately.
92 $ppco_webhook_notice .= '<script>(function(){var e=document.querySelector(".s2member-ppco-webhook-upgrade-notice-'.esc_js($env).'");if(e&&e.closest){e=e.closest(".notice");if(e)e.remove();}})();</script>'."\n";
93 }
94 else
95 $ppco_webhook_notice = '<div class="error"><p>'.sprintf(esc_html__('Unable to create/update PayPal Checkout %1$s webhook. See the s2Member paypal-checkout.log for details.', 's2member'), $env_label).' <a href="'.esc_attr(admin_url("/admin.php?page=ws-plugin--s2member-logs")).'">'.esc_html__('Log Viewer', 's2member').'</a></p></div>'."\n";
96 }
97 }
98 else
99 $ppco_webhook_notice = '<div class="error"><p>'.sprintf(esc_html__('PayPal Checkout %1$s credentials are not configured.', 's2member'), $env_label).'</p></div>'."\n";
100 }
101 else
102 $ppco_webhook_notice = '<div class="error"><p>'.esc_html__('Invalid request. Please try again.', 's2member').'</p></div>'."\n";
103 }
104
105 // No redirect; display notice inline.
106
107 if(!empty($ppco_r['s2member_ppco_creds_test']) && empty($ppco_r['s2member_ppco_webhook']) && empty($ppco_r['s2member_ppco_clear_cache']) && current_user_can('manage_options'))
108 {
109 if(!empty($ppco_r['_wpnonce']) && wp_verify_nonce((string)$ppco_r['_wpnonce'], 's2member_ppco_creds_test'))
110 {
111 $env = (!empty($ppco_r['ppco_creds_env']) && $ppco_r['ppco_creds_env'] === 'sandbox') ? 'sandbox' : 'live';
112 $env_label = ($env === 'sandbox') ? esc_html__('Sandbox', 's2member') : esc_html__('Live', 's2member');
113
114 $client_id = '';
115 $secret = '';
116
117 if($env === 'sandbox')
118 {
119 $client_id = (string)$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_id"];
120 $secret = (string)$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_secret"];
121 }
122 else // live
123 {
124 $client_id = (string)$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_id"];
125 $secret = (string)$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_secret"];
126 }
127
128 if(!empty($client_id) && !empty($secret))
129 {
130 $ok = c_ws_plugin__s2member_paypal_utilities::paypal_checkout_creds_test($env);
131
132 if($ok)
133 $ppco_creds_notice = '<div class="updated"><p>'.sprintf(esc_html__('PayPal Checkout %1$s credentials test OK.', 's2member'), $env_label).'</p></div>'."\n";
134 else
135 $ppco_creds_notice = '<div class="error"><p>'.sprintf(esc_html__('PayPal Checkout %1$s credentials test failed. Please verify that the Client ID and Secret were copied from the same PayPal %1$s REST app, paste them again, save options, and test again. See the s2Member paypal-checkout.log for details.', 's2member'), $env_label).' <a href="'.esc_attr(admin_url("/admin.php?page=ws-plugin--s2member-logs")).'">'.esc_html__('Log Viewer', 's2member').'</a></p></div>'."\n";
136 }
137 else
138 $ppco_creds_notice = '<div class="error"><p>'.sprintf(esc_html__('PayPal Checkout %1$s credentials are not configured.', 's2member'), $env_label).'</p></div>'."\n";
139 }
140 else
141 $ppco_creds_notice = '<div class="error"><p>'.esc_html__('Invalid request. Please try again.', 's2member').'</p></div>'."\n";
142 }
143
144 // No redirect; display notice inline.
145
146 if(!empty($ppco_r['s2member_ppco_clear_cache']) && empty($ppco_r['s2member_ppco_webhook']) && empty($ppco_r['s2member_ppco_creds_test']) && current_user_can('manage_options'))
147 {
148 if(!empty($ppco_r['_wpnonce']) && wp_verify_nonce((string)$ppco_r['_wpnonce'], 's2member_ppco_clear_cache'))
149 {
150 $env = (!empty($ppco_r['ppco_clear_env']) && $ppco_r['ppco_clear_env'] === 'sandbox') ? 'sandbox' : 'live';
151 $env_label = ($env === 'sandbox') ? esc_html__('Sandbox', 's2member') : esc_html__('Live', 's2member');
152
153 c_ws_plugin__s2member_paypal_utilities::paypal_checkout_clear_cache($env);
154
155 $ppco_cache_notice = '<div class="updated"><p>'.sprintf(esc_html__('PayPal Checkout cache cleared for %1$s.', 's2member'), $env_label).'</p></div>'."\n";
156 }
157 else
158 $ppco_cache_notice = '<div class="error"><p>'.esc_html__('Invalid request. Please try again.', 's2member').'</p></div>'."\n";
159 }
160
161 // No redirect; display notice inline.
162
163 echo '<div class="wrap ws-menu-page">'."\n";
164 echo $ppco_webhook_notice;
165 echo $ppco_creds_notice;
166 echo $ppco_cache_notice;
167
168 echo '<div class="wp-header-end"></div>'."\n";
169
170 echo '<div class="ws-menu-page-toolbox">'."\n";
171 c_ws_plugin__s2member_menu_pages_tb::display();
172 echo '</div>'."\n";
173
174 echo '<h2>PayPal Options</h2>'."\n";
175
176 echo '<table class="ws-menu-page-table">'."\n";
177 echo '<tbody class="ws-menu-page-table-tbody">'."\n";
178 echo '<tr class="ws-menu-page-table-tr">'."\n";
179 echo '<td class="ws-menu-page-table-l">'."\n";
180
181 do_action("ws_plugin__s2member_during_paypal_ops_page_before_left_sections_form", get_defined_vars());
182
183 echo '<form method="post" name="ws_plugin__s2member_options_form" id="ws-plugin--s2member-options-form" autocomplete="off">'."\n";
184 echo '<input type="hidden" name="ws_plugin__s2member_options_save" id="ws-plugin--s2member-options-save" value="'.esc_attr(wp_create_nonce("ws-plugin--s2member-options-save")).'" />'."\n";
185 echo '<input type="hidden" name="ws_plugin__s2member_configured" id="ws-plugin--s2member-configured" value="1" />'."\n";
186
187 do_action("ws_plugin__s2member_during_paypal_ops_page_before_left_sections", get_defined_vars());
188
189 if(apply_filters("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_display_paypal_account_details", TRUE, get_defined_vars()))
190 {
191 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_before_paypal_account_details", get_defined_vars());
192
193 //260106 New PayPal Checkout panel.
194 //260824.0507 Allow administrative notices to open and jump directly to the PayPal Checkout panel.
195 echo '<div id="ws-plugin--s2member-paypal-checkout"></div>'."\n";
196 echo '<div class="ws-menu-page-group" title="PayPal Checkout (Beta)"'.((!empty($_GET['s2member-open-panel']) && $_GET['s2member-open-panel'] === 'paypal-checkout') ? ' default-state="open"' : '').'>'."\n";
197
198 echo '<div class="ws-menu-page-section ws-plugin--s2member-paypal-checkout-account-details-section">'."\n";
199 echo '<a href="https://s2member.com/r/paypal/" target="_blank"><img src="'.esc_attr($GLOBALS["WS_PLUGIN__"]["s2member"]["c"]["dir_url"]).'/src/images/paypal-pp-logo-200px.png" class="ws-menu-page-right s2m-ppco-paypal-logo" style="width:250px; max-width:25%; height:auto; margin:0 20px 20px 0;" alt="PayPal" /></a>'."\n";
200
201 echo '<div class="ws-menu-page-notice ws-menu-page-notice-info">'."\n";
202 //260818.2340 PayPal Checkout now powers both Framework buttons and the PayPal option in s2Member Pro-Forms without shortcode edits.
203 echo '<p>'.esc_html__('When enabled, existing s2Member PayPal Button shortcodes and s2Member Pro-Forms that offer PayPal are powered by PayPal Checkout without requiring shortcode edits. If disabled, they continue using their existing PayPal Standard or Express Checkout flow.', 's2member').'</p>'."\n";
204
205 echo '<p style="margin:0 0 6px 0;"><strong>'.esc_html__('Quick Setup (Live or Sandbox)', 's2member').'</strong></p>'."\n";
206 echo '<ol style="margin:0 0 0 20px;">'."\n";
207 echo '<li>'.esc_html__('Enable PayPal Checkout below, choose the environment (Live or Sandbox).', 's2member').'</li>'."\n";
208 echo '<li>'.esc_html__('Go to PayPal\'s “Apps & Credentials”, and click “Create App” for your site (s2Member).', 's2member').' <a href="https://developer.paypal.com/dashboard/applications/live" target="_blank" rel="external">'.esc_html__('Live', 's2member').'</a> / <a href="https://developer.paypal.com/dashboard/applications/sandbox" target="_blank" rel="external">'.esc_html__('Sandbox', 's2member').'</a></li>'."\n";
209 echo '<li>'.esc_html__('Copy the app\'s Client ID and Secret (REST API, not NVP) and paste them below.', 's2member').'</li>'."\n";
210 echo '<li>'.esc_html__('Click “Save All Changes”.', 's2member').'</li>'."\n";
211 echo '<li>'.esc_html__('Click “Test Credentials” to verify the saved Client ID and Secret are correct.', 's2member').'</li>'."\n";
212 echo '<li>'.esc_html__('Click “Create/Update Webhook” to register your webhook URL with PayPal and populate the Webhook ID field.', 's2member').'</li>'."\n";
213 echo '<li>'.esc_html__('Click “Test Webhook Endpoint” to check it.', 's2member').'</li>'."\n";
214 echo '</ol>'."\n";
215
216 echo '<p style="margin:12px 0 0 0;"><strong>'.esc_html__('Useful PayPal links', 's2member').'</strong></p>'."\n";
217 echo '<ul style="margin:0 0 0 20px; list-style:disc;">'."\n";
218 echo '<li><a href="https://developer.paypal.com/api/rest/webhooks/" target="_blank" rel="external">'.esc_html__('Webhooks overview (PayPal docs)', 's2member').'</a></li>'."\n";
219 echo '<li><a href="https://developer.paypal.com/api/rest/webhooks/simulator/" target="_blank" rel="external">'.esc_html__('Webhooks simulator (connectivity testing)', 's2member').'</a></li>'."\n";
220 echo '<li><a href="https://developer.paypal.com/api/rest/webhooks/events-dashboard/" target="_blank" rel="external">'.esc_html__('Webhooks Events dashboard (view/re-deliver events)', 's2member').'</a></li>'."\n";
221 echo '</ul>'."\n";
222
223 echo '</div>'."\n";
224
225 echo '<p><em>'.esc_html__('Leave these fields blank to continue using your existing PayPal Standard and Express Checkout integrations.', 's2member').'</em></p>'."\n";
226
227 $ppco_https_ready = true;
228 $ppco_https_reason = '';
229
230 $ppco_https_scheme = strtolower((string)c_ws_plugin__s2member_utils_urls::parse_url(home_url('/'), PHP_URL_SCHEME));
231
232 //260224 Reverse-proxy/Cloudflare fallback for admin-side HTTPS detection.
233 // This keeps PayPal Checkout configuration available when WordPress is behind a proxy
234 // and the current admin request is clearly HTTPS at the edge.
235 $ppco_proxy_https = false;
236 if(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && stripos(strtolower((string)$_SERVER['HTTP_X_FORWARDED_PROTO']), 'https') !== false)
237 $ppco_proxy_https = true;
238 else if(!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && strtolower((string)$_SERVER['HTTP_X_FORWARDED_SSL']) === 'on')
239 $ppco_proxy_https = true;
240 else if(!empty($_SERVER['HTTPS']) && in_array(strtolower((string)$_SERVER['HTTPS']), array('on', '1'), true))
241 $ppco_proxy_https = true;
242 else if(!empty($_SERVER['HTTP_CF_VISITOR']) && stripos((string)$_SERVER['HTTP_CF_VISITOR'], '"scheme":"https"') !== false)
243 $ppco_proxy_https = true;
244
245 if($ppco_https_scheme !== 'https' && !$ppco_proxy_https)
246 {
247 $ppco_https_ready = false;
248 $ppco_https_reason = __('Your WordPress Site URL/Home URL is currently not configured for HTTPS.', 's2member');
249 }
250 else
251 {
252 //260216 Note: Avoid gating configuration on a server-side HTTPS "loopback" probe.
253 // Many shared hosts/WAFs return false negatives for loopback requests (e.g., HEAD blocked,
254 // ModSecurity rules, disabled loopback/outbound HTTP). Reachability is verified separately
255 // via the "Test Webhook Endpoint" link and PayPal's webhook simulator.
256 }
257
258 if(!$ppco_https_ready)
259 {
260 echo '<div class="ws-menu-page-notice ws-menu-page-notice-info">'."\n";
261 echo '<p><span class="ws-menu-page-error"><strong>'.esc_html__('PayPal Checkout requires HTTPS (SSL).', 's2member').'</strong></span></p>'."\n";
262 echo '<p><span class="ws-menu-page-error">'.esc_html__('This site does not currently have a working HTTPS endpoint for this domain. PayPal Checkout is disabled here because webhooks cannot be delivered without HTTPS.', 's2member').'</span></p>'."\n";
263 echo '<p><code>'.esc_html($ppco_https_reason).'</code></p>'."\n";
264 echo '</div>'."\n";
265 }
266
267 echo '<table class="form-table" style="margin:0;">'."\n";
268 echo '<tbody>'."\n";
269 echo '<tr>'."\n";
270
271 echo '<th style="padding-top:0;">'."\n";
272 echo '<label for="ws-plugin--s2member-paypal-checkout-enable-0">'."\n";
273 echo 'Enable PayPal Checkout (Beta)?'."\n";
274 echo '</label>'."\n";
275 echo '</th>'."\n";
276
277 echo '</tr>'."\n";
278 echo '<tr>'."\n";
279
280 echo '<td>'."\n";
281 echo '<input type="radio" name="ws_plugin__s2member_paypal_checkout_enable" id="ws-plugin--s2member-paypal-checkout-enable-0" value="0" onclick="window.s2mPpcoApplyEnvCues&&window.s2mPpcoApplyEnvCues();"'.((!$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_enable"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-paypal-checkout-enable-0">No</label> &nbsp;&nbsp;&nbsp; <input type="radio" name="ws_plugin__s2member_paypal_checkout_enable" id="ws-plugin--s2member-paypal-checkout-enable-1" value="1" onclick="window.s2mPpcoApplyEnvCues&&window.s2mPpcoApplyEnvCues();"'.((!$ppco_https_ready) ? ' disabled="disabled"' : '').(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_enable"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-paypal-checkout-enable-1">Yes, enable PayPal Checkout.</label><br />'."\n";
282 echo '<em>Only enable this after you have configured PayPal Checkout credentials below. If disabled, s2Member continues using the existing PayPal Standard and Express Checkout integrations.</em>'."\n";
283
284 if(!$ppco_https_ready)
285 echo '<br /><span class="ws-menu-page-error">'.esc_html__('Disabled: PayPal Checkout requires HTTPS (SSL) for webhooks. Fix HTTPS for this domain, then enable PayPal Checkout.', 's2member').'</span>'."\n";
286 echo '</td>'."\n";
287
288 echo '</tr>'."\n";
289 echo '<tr>'."\n";
290
291 echo '<th>'."\n";
292 echo '<label for="ws-plugin--s2member-paypal-checkout-sandbox-0">'."\n";
293 echo 'PayPal Checkout Environment'."\n";
294 echo '</label>'."\n";
295 echo '</th>'."\n";
296
297 echo '</tr>'."\n";
298 echo '<tr>'."\n";
299
300 echo '<td>'."\n";
301 echo '<input type="radio" name="ws_plugin__s2member_paypal_checkout_sandbox" id="ws-plugin--s2member-paypal-checkout-sandbox-0" value="0" onclick="window.s2mPpcoApplyEnvCues && window.s2mPpcoApplyEnvCues(\'click-live\');"'.((!$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-paypal-checkout-sandbox-0">Live</label> &nbsp;&nbsp;&nbsp; <input type="radio" name="ws_plugin__s2member_paypal_checkout_sandbox" id="ws-plugin--s2member-paypal-checkout-sandbox-1" value="1" onclick="window.s2mPpcoApplyEnvCues && window.s2mPpcoApplyEnvCues(\'click-sandbox\');"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-paypal-checkout-sandbox-1">Sandbox</label><br />'."\n";
302 echo '<em>This controls which credentials and webhook ID are used at runtime.</em>'."\n";
303 echo '</td>'."\n";
304
305 echo '</tr>'."\n";
306 echo '</tbody>'."\n";
307 echo '</table>'."\n";
308
309 echo '<div class="ws-menu-page-hr"></div>'."\n";
310
311 echo '<style type="text/css">'."\n";
312 echo '/* PayPal Checkout (Beta): environment cues (Live vs Sandbox) */'."\n";
313 echo '.ws-plugin--s2member-paypal-checkout-account-details-section .s2m-ppco-env-wrap{border-radius:8px;}'."\n";
314 echo '.ws-plugin--s2member-paypal-checkout-account-details-section .s2m-ppco-env-live-wrap{margin:0 0 18px 0;}'."\n";
315 echo '.ws-plugin--s2member-paypal-checkout-account-details-section .s2m-ppco-env-sandbox-wrap{background:#fcfeff; margin:0 -12px 18px -12px; padding:12px;}'."\n";
316 echo '.ws-plugin--s2member-paypal-checkout-account-details-section .s2m-ppco-env-inactive{opacity:0.50;}'."\n";
317 echo '/* Prevent long <code> strings from forcing horizontal overflow (e.g., webhook URL). */'."\n";
318 echo '.ws-plugin--s2member-paypal-checkout-account-details-section code{display:inline-block; max-width:100%; margin:0 !important; white-space:normal; overflow-wrap:anywhere; word-break:break-word;}'."\n";
319 echo '/* Keep inputs inside the section from overflowing the container when zoomed. */'."\n";
320 echo '.ws-plugin--s2member-paypal-checkout-account-details-section input[type="text"],'."\n";
321 echo '.ws-plugin--s2member-paypal-checkout-account-details-section input[type="password"]{max-width:100%; box-sizing:border-box;}'."\n";
322 echo '</style>'."\n";
323
324 echo '<script type="text/javascript">'."\n";
325 echo 'window.s2mPpcoApplyEnvCues=function(){'."\n";
326 echo ' var en=document.getElementById(\'ws-plugin--s2member-paypal-checkout-enable-1\');'."\n";
327 echo ' var sb=document.getElementById(\'ws-plugin--s2member-paypal-checkout-sandbox-1\');'."\n";
328 echo ' var live=document.querySelector(\'.ws-plugin--s2member-paypal-checkout-account-details-section .s2m-ppco-env-live-wrap\');'."\n";
329 echo ' var sandbox=document.querySelector(\'.ws-plugin--s2member-paypal-checkout-account-details-section .s2m-ppco-env-sandbox-wrap\');'."\n";
330 echo ' if(!live||!sandbox){return;}'."\n";
331 echo ' var enabled=!!(en&&en.checked);'."\n";
332 echo ' var isSandbox=!!(sb&&sb.checked);'."\n";
333 echo ' live.classList.toggle(\'s2m-ppco-env-inactive\', !enabled||isSandbox);'."\n";
334 echo ' sandbox.classList.toggle(\'s2m-ppco-env-inactive\', !enabled||!isSandbox);'."\n";
335 echo '};'."\n";
336 echo 'window.s2mPpcoApplyEnvCues();'."\n";
337 echo '</script>'."\n";
338
339 $ppco_post_back_url = remove_query_arg(array('s2member_ppco_webhook', 'ppco_webhook_env', 's2member_ppco_creds_test', 'ppco_creds_env', 's2member_ppco_clear_cache', 'ppco_clear_env', '_wpnonce'));
340
341 $ppco_webhook_nonce = wp_create_nonce('s2member_ppco_webhook');
342 $ppco_creds_test_nonce = wp_create_nonce('s2member_ppco_creds_test');
343 $ppco_clear_cache_nonce = wp_create_nonce('s2member_ppco_clear_cache');
344
345 echo '<script type="text/javascript">'."\n";
346 echo 'window.s2mPpcoPostBackUrl='.wp_json_encode($ppco_post_back_url).';'."\n";
347 echo 'window.s2mPpcoPostAction=function(action,env,nonce){'."\n";
348 echo ' try{'."\n";
349 echo ' var f=document.createElement("form");'."\n";
350 echo ' f.method="post";'."\n";
351 echo ' f.action=window.s2mPpcoPostBackUrl||window.location.href;'."\n";
352 echo ' var add=function(n,v){var i=document.createElement("input");i.type="hidden";i.name=n;i.value=v;f.appendChild(i);};'."\n";
353 echo ' if(action==="webhook"){add("s2member_ppco_webhook","1");add("ppco_webhook_env",env);}'."\n";
354 echo ' else if(action==="creds_test"){add("s2member_ppco_creds_test","1");add("ppco_creds_env",env);}'."\n";
355 echo ' else if(action==="clear_cache"){add("s2member_ppco_clear_cache","1");add("ppco_clear_env",env);}'."\n";
356 echo ' add("_wpnonce",nonce);'."\n";
357 echo ' document.body.appendChild(f);'."\n";
358 echo ' f.submit();'."\n";
359 echo ' }catch(e){}'."\n";
360 echo ' return false;'."\n";
361 echo '};'."\n";
362 echo '</script>'."\n";
363
364 $ppco_webhook_live_url = add_query_arg(array(
365 's2member_ppco_webhook' => '1',
366 'ppco_webhook_env' => 'live',
367 '_wpnonce' => wp_create_nonce('s2member_ppco_webhook'),
368 ), remove_query_arg(array('s2member_ppco_webhook', 'ppco_webhook_env', 's2member_ppco_creds_test', 'ppco_creds_env', 's2member_ppco_clear_cache', 'ppco_clear_env', '_wpnonce')));
369
370 $ppco_creds_test_live_url = add_query_arg(array(
371 's2member_ppco_creds_test' => '1',
372 'ppco_creds_env' => 'live',
373 '_wpnonce' => wp_create_nonce('s2member_ppco_creds_test'),
374 ), remove_query_arg(array('s2member_ppco_creds_test', 'ppco_creds_env', 's2member_ppco_webhook', 'ppco_webhook_env', 's2member_ppco_clear_cache', 'ppco_clear_env', '_wpnonce')));
375
376 $ppco_clear_cache_live_url = add_query_arg(array(
377 's2member_ppco_clear_cache' => '1',
378 'ppco_clear_env' => 'live',
379 '_wpnonce' => wp_create_nonce('s2member_ppco_clear_cache'),
380 ), remove_query_arg(array('s2member_ppco_clear_cache', 'ppco_clear_env', 's2member_ppco_webhook', 'ppco_webhook_env', 's2member_ppco_creds_test', 'ppco_creds_env', '_wpnonce')));
381
382 $ppco_webhook_test_live_url = add_query_arg(array(
383 's2member_paypal_webhook' => '1',
384 's2member_paypal_webhook_test' => '1',
385 'ppco_webhook_env' => 'live',
386 '_wpnonce' => wp_create_nonce('s2member_ppco_webhook_test'),
387 ), home_url("/", 'https'));
388
389 $ppco_webhook_sandbox_url = add_query_arg(array(
390 's2member_ppco_webhook' => '1',
391 'ppco_webhook_env' => 'sandbox',
392 '_wpnonce' => wp_create_nonce('s2member_ppco_webhook'),
393 ), remove_query_arg(array('s2member_ppco_webhook', 'ppco_webhook_env', 's2member_ppco_creds_test', 'ppco_creds_env', 's2member_ppco_clear_cache', 'ppco_clear_env', '_wpnonce')));
394
395 $ppco_creds_test_sandbox_url = add_query_arg(array(
396 's2member_ppco_creds_test' => '1',
397 'ppco_creds_env' => 'sandbox',
398 '_wpnonce' => wp_create_nonce('s2member_ppco_creds_test'),
399 ), remove_query_arg(array('s2member_ppco_creds_test', 'ppco_creds_env', 's2member_ppco_webhook', 'ppco_webhook_env', 's2member_ppco_clear_cache', 'ppco_clear_env', '_wpnonce')));
400
401 $ppco_clear_cache_sandbox_url = add_query_arg(array(
402 's2member_ppco_clear_cache' => '1',
403 'ppco_clear_env' => 'sandbox',
404 '_wpnonce' => wp_create_nonce('s2member_ppco_clear_cache'),
405 ), remove_query_arg(array('s2member_ppco_clear_cache', 'ppco_clear_env', 's2member_ppco_webhook', 'ppco_webhook_env', 's2member_ppco_creds_test', 'ppco_creds_env', '_wpnonce')));
406
407 $ppco_webhook_test_sandbox_url = add_query_arg(array(
408 's2member_paypal_webhook' => '1',
409 's2member_paypal_webhook_test' => '1',
410 'ppco_webhook_env' => 'sandbox',
411 '_wpnonce' => wp_create_nonce('s2member_ppco_webhook_test'),
412 ), home_url("/", 'https'));
413
414 $ppco_cache = !empty($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_cache"]) && is_array($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_cache"]) ? $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_cache"] : array();
415
416 $ppco_live_cred_id = c_ws_plugin__s2member_paypal_utilities::paypal_checkout_cred_id('live');
417 $ppco_sandbox_cred_id = c_ws_plugin__s2member_paypal_utilities::paypal_checkout_cred_id('sandbox');
418
419 $ppco_plan_count_live = (!empty($ppco_cache[$ppco_live_cred_id]['live']['plan_ids']) && is_array($ppco_cache[$ppco_live_cred_id]['live']['plan_ids'])) ? count($ppco_cache[$ppco_live_cred_id]['live']['plan_ids']) : 0;
420 $ppco_prod_count_live = (!empty($ppco_cache[$ppco_live_cred_id]['live']['product_ids']) && is_array($ppco_cache[$ppco_live_cred_id]['live']['product_ids'])) ? count($ppco_cache[$ppco_live_cred_id]['live']['product_ids']) : 0;
421
422 $ppco_plan_count_sandbox = (!empty($ppco_cache[$ppco_sandbox_cred_id]['sandbox']['plan_ids']) && is_array($ppco_cache[$ppco_sandbox_cred_id]['sandbox']['plan_ids'])) ? count($ppco_cache[$ppco_sandbox_cred_id]['sandbox']['plan_ids']) : 0;
423 $ppco_prod_count_sandbox = (!empty($ppco_cache[$ppco_sandbox_cred_id]['sandbox']['product_ids']) && is_array($ppco_cache[$ppco_sandbox_cred_id]['sandbox']['product_ids'])) ? count($ppco_cache[$ppco_sandbox_cred_id]['sandbox']['product_ids']) : 0;
424
425 echo '<div class="s2m-ppco-env-wrap s2m-ppco-env-live-wrap'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox"]) ? ' s2m-ppco-env-inactive' : '').'">'."\n";
426 echo '<h3 style="margin:0 0 6px 0;">Live</h3>'."\n";
427
428 echo '<table class="form-table">'."\n";
429 echo '<tbody>'."\n";
430
431 echo '<tr>'."\n";
432 echo '<th>'."\n";
433 echo '<label for="ws-plugin--s2member-paypal-checkout-client-id">'."\n";
434 echo 'PayPal Checkout Client ID (Live — REST API):'."\n";
435 echo '</label>'."\n";
436 echo '</th>'."\n";
437 echo '</tr>'."\n";
438
439 echo '<tr>'."\n";
440 echo '<td>'."\n";
441 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_paypal_checkout_client_id" id="ws-plugin--s2member-paypal-checkout-client-id" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_id"]).'" /><br />'."\n";
442 echo 'At PayPal, see: <strong><a href="https://developer.paypal.com/dashboard/applications/live" target="_blank" rel="external">My Apps &amp; Credentials (Live)</a></strong>'."\n";
443 echo '</td>'."\n";
444 echo '</tr>'."\n";
445
446 echo '<tr>'."\n";
447 echo '<th>'."\n";
448 echo '<label for="ws-plugin--s2member-paypal-checkout-client-secret">'."\n";
449 echo 'PayPal Checkout Client Secret (Live — REST API):'."\n";
450 echo '</label>'."\n";
451 echo '</th>'."\n";
452 echo '</tr>'."\n";
453
454 echo '<tr>'."\n";
455 echo '<td>'."\n";
456 echo '<input type="password" autocomplete="off" name="ws_plugin__s2member_paypal_checkout_client_secret" id="ws-plugin--s2member-paypal-checkout-client-secret" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_secret"]).'" /><br />'."\n";
457 echo 'At PayPal, see: <strong><a href="https://developer.paypal.com/dashboard/applications/live" target="_blank" rel="external">My Apps &amp; Credentials (Live)</a></strong>'."\n";
458
459 echo '<table class="form-table" style="margin:10px 0 0 0;">'."\n";
460 echo '<tbody>'."\n";
461 echo '<tr>'."\n";
462 echo '<th style="width:260px; padding:0 10px 0 0;">'."\n";
463 if($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_id"] && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_secret"])
464 echo '<a class="button" style="min-width:240px; text-align:center;" href="'.esc_attr($ppco_creds_test_live_url).'" onclick="return window.s2mPpcoPostAction(\'creds_test\',\'live\',\''.esc_attr($ppco_creds_test_nonce).'\');">'.esc_html__('Test Credentials', 's2member').'</a>'."\n";
465 else echo '<span class="button" style="min-width:240px; text-align:center; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('Save Live credentials first.', 's2member').'">'.esc_html__('Test Credentials', 's2member').'</span>'."\n";
466 echo '</th>'."\n";
467 echo '<td style="padding:0;">'."\n";
468 echo '<em>'.esc_html__('Verifies your Live Client ID/Secret by requesting an access token.', 's2member').'</em>'."\n";
469 echo '</td>'."\n";
470 echo '</tr>'."\n";
471 echo '</tbody>'."\n";
472 echo '</table>'."\n";
473
474 echo '</td>'."\n";
475 echo '</tr>'."\n";
476
477 echo '<tr>'."\n";
478 echo '<th>'."\n";
479 echo '<label for="ws-plugin--s2member-paypal-checkout-webhook-id">'."\n";
480 echo 'PayPal Checkout Webhook ID (Live):'."\n";
481 echo '</label>'."\n";
482 echo '</th>'."\n";
483 echo '</tr>'."\n";
484
485 echo '<tr>'."\n";
486 echo '<td>'."\n";
487 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_paypal_checkout_webhook_id" id="ws-plugin--s2member-paypal-checkout-webhook-id" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_webhook_id"]).'" />'."\n";
488 echo '<br /><em>'.esc_html__('From your PayPal app webhook settings (Live).', 's2member').'</em><br />'."\n";
489
490 echo '<table class="form-table" style="margin:12px 0 0 0;">'."\n";
491 echo '<tbody>'."\n";
492
493 echo '<tr>'."\n";
494 echo '<th style="width:260px; padding:0 10px 0 0;">'."\n";
495 if($ppco_https_ready && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_id"] && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_secret"])
496 echo '<a class="button button-primary" style="min-width:250px; text-align:center; color:#fff !important;" href="'.esc_attr($ppco_webhook_live_url).'" onclick="return window.s2mPpcoPostAction(\'webhook\',\'live\',\''.esc_attr($ppco_webhook_nonce).'\');">'.esc_html__('Create/Update Webhook', 's2member').'</a>'."\n";
497 else if(!$ppco_https_ready)
498 echo '<span class="button button-primary" style="min-width:250px; text-align:center; color:#fff !important; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('HTTPS required.', 's2member').'">'.esc_html__('Create/Update Webhook', 's2member').'</span>'."\n";
499 else
500 echo '<span class="button button-primary" style="min-width:250px; text-align:center; color:#fff !important; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('Save Live credentials first.', 's2member').'">'.esc_html__('Create/Update Webhook', 's2member').'</span>'."\n";
501 echo '</th>'."\n";
502 echo '<td style="padding:0;">'."\n";
503 echo '<em>'.esc_html__('Registers or updates your webhook URL and required events with PayPal (HTTPS required).', 's2member').' '.esc_html__('If your site URL changes, re-run Create/Update Webhook to update the callback URL at PayPal.', 's2member').'</em>'."\n";
504 echo '</td>'."\n";
505 echo '</tr>'."\n";
506
507 echo '<tr>'."\n";
508 echo '<th style="width:260px; padding:8px 10px 0 0;">'."\n";
509 if($ppco_https_ready && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_id"] && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_secret"] && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_webhook_id"])
510 echo '<a class="button" style="min-width:250px; text-align:center;" target="_blank" href="'.esc_attr($ppco_webhook_test_live_url).'">'.esc_html__('Test Webhook Endpoint', 's2member').'</a>'."\n";
511 else if(!$ppco_https_ready)
512 echo '<span class="button disabled" style="min-width:250px; text-align:center;" title="'.esc_attr__('HTTPS required.', 's2member').'">'.esc_html__('Test Webhook Endpoint', 's2member').'</span>'."\n";
513 else if(!$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_id"] || !$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_client_secret"])
514 echo '<span class="button disabled" style="min-width:250px; text-align:center;" title="'.esc_attr__('Save Live credentials first.', 's2member').'">'.esc_html__('Test Webhook Endpoint', 's2member').'</span>'."\n";
515 else
516 echo '<span class="button" style="min-width:250px; text-align:center; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('Set a Live Webhook ID first (or use Create/Update Webhook).', 's2member').'">'.esc_html__('Test Webhook Endpoint', 's2member').'</span>'."\n";
517 echo '</th>'."\n";
518 echo '<td style="padding:8px 0 0 0;">'."\n";
519 echo '<em>'.esc_html__('Reachability-only check. Use real webhooks for end-to-end verification.', 's2member').'</em>'."\n";
520 echo '<br /><em>'.esc_html__('Test URL: ', 's2member').'<code>'.esc_html($ppco_webhook_test_live_url).'</code></em>'."\n";
521 echo '</td>'."\n";
522 echo '</tr>'."\n";
523
524 echo '</tbody>'."\n";
525 echo '</table>'."\n";
526
527 echo '<br /><br />'."\n";
528 echo '<em>'.esc_html__('Webhook URL:', 's2member').'</em><br /><code>'.esc_html(add_query_arg("s2member_paypal_webhook", "1", home_url("/", 'https'))).'</code>'."\n";
529 echo '<br /><em class="ws-menu-page-hilite">'.esc_html__('Note: PayPal requires an HTTPS (SSL) webhook URL.', 's2member').'</em>'."\n";
530
531 $ppco_https_scheme = strtolower((string)c_ws_plugin__s2member_utils_urls::parse_url(home_url('/'), PHP_URL_SCHEME));
532
533 //260224 Reverse-proxy/Cloudflare fallback for admin-side HTTPS warning display.
534 $ppco_proxy_https = false;
535 if(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && stripos(strtolower((string)$_SERVER['HTTP_X_FORWARDED_PROTO']), 'https') !== false)
536 $ppco_proxy_https = true;
537 else if(!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && strtolower((string)$_SERVER['HTTP_X_FORWARDED_SSL']) === 'on')
538 $ppco_proxy_https = true;
539 else if(!empty($_SERVER['HTTPS']) && in_array(strtolower((string)$_SERVER['HTTPS']), array('on', '1'), true))
540 $ppco_proxy_https = true;
541 else if(!empty($_SERVER['HTTP_CF_VISITOR']) && stripos((string)$_SERVER['HTTP_CF_VISITOR'], '"scheme":"https"') !== false)
542 $ppco_proxy_https = true;
543
544 if($ppco_https_scheme !== 'https' && !$ppco_proxy_https)
545 echo '<br /><span class="ws-menu-page-error">'.esc_html__('Warning: Your WordPress Site URL/Home URL is currently not configured for HTTPS. PayPal may reject webhook creation and delivery until your site supports SSL and your Site URL uses https://.', 's2member').'</span>'."\n";
546 else
547 {
548 $ppco_https_probe = wp_remote_head(home_url("/", 'https'), array('timeout' => 8, 'redirection' => 0));
549 $ppco_https_code = !is_wp_error($ppco_https_probe) ? (int)wp_remote_retrieve_response_code($ppco_https_probe) : 0;
550 $ppco_https_loc = !is_wp_error($ppco_https_probe) ? (string)wp_remote_retrieve_header($ppco_https_probe, 'location') : '';
551
552 if(is_wp_error($ppco_https_probe))
553 echo '<br /><span class="ws-menu-page-error">'.esc_html__('Warning: Your HTTPS endpoint could not be reached from this server. This often indicates SSL/TLS or virtual-host configuration issues; PayPal webhooks may not be delivered successfully.', 's2member').'<br /><code>'.esc_html($ppco_https_probe->get_error_message()).'</code></span>'."\n";
554 else if($ppco_https_loc && stripos($ppco_https_loc, 'cgi-sys/defaultwebpage.cgi') !== false)
555 echo '<br /><span class="ws-menu-page-error">'.esc_html__('Warning: Your HTTPS endpoint appears to redirect to a hosting default page (cgi-sys/defaultwebpage.cgi). This suggests SSL/virtual-host configuration issues; PayPal webhooks may not be delivered successfully.', 's2member').'</span>'."\n";
556 else if($ppco_https_code && $ppco_https_code >= 400)
557 echo '<br /><span class="ws-menu-page-error">'.sprintf(esc_html__('Warning: Server-side probe to your HTTPS endpoint returned HTTP %1$s. This can be caused by SSL/virtual-host configuration issues, but it can also be a false negative due to security rules that block loopback requests. Use the “Test Webhook Endpoint” link and PayPal\'s webhook simulator to confirm external reachability.', 's2member'), (int)$ppco_https_code).'</span>'."\n";
558 }
559
560 //260824.1727 Include payment, dispute, and subscription lifecycle events required by Checkout processing.
561 echo '<br /><br /><em>'.esc_html__('Required Events:', 's2member').'</em><br /><code>PAYMENT.SALE.COMPLETED</code> <code>PAYMENT.CAPTURE.COMPLETED</code> <code>PAYMENT.SALE.REFUNDED</code> <code>PAYMENT.CAPTURE.REFUNDED</code> <code>PAYMENT.SALE.REVERSED</code> <code>PAYMENT.CAPTURE.REVERSED</code> <code>CUSTOMER.DISPUTE.CREATED</code> <code>BILLING.SUBSCRIPTION.CREATED</code> <code>BILLING.SUBSCRIPTION.ACTIVATED</code> <code>BILLING.SUBSCRIPTION.RE-ACTIVATED</code> <code>BILLING.SUBSCRIPTION.UPDATED</code> <code>BILLING.SUBSCRIPTION.CANCELLED</code> <code>BILLING.SUBSCRIPTION.SUSPENDED</code> <code>BILLING.SUBSCRIPTION.EXPIRED</code> <code>BILLING.SUBSCRIPTION.PAYMENT.FAILED</code>'."\n";
562
563 echo '<p style="margin:10px 0 0 0;"><em>'.esc_html__('Tip: Use PayPal\'s Webhooks Events dashboard to view and re-deliver webhook events while testing.', 's2member').' <a href="https://developer.paypal.com/api/rest/webhooks/events-dashboard/" target="_blank" rel="external">'.esc_html__('Open Events Dashboard', 's2member').'</a></em></p>'."\n";
564
565 if(!$ppco_https_ready)
566 echo '<p style="margin:10px 0 0 0;"><span class="ws-menu-page-error">'.esc_html__('Disabled: PayPal webhooks require a working HTTPS (SSL) endpoint on this domain.', 's2member').'</span></p>'."\n";
567
568 echo '<p style="margin:16px 0 10px 0;"><strong>'.esc_html__('Plan/Product Cache (Live)', 's2member').'</strong></p>'."\n";
569 echo '<table class="form-table" style="margin:0;">'."\n";
570 echo '<tbody>'."\n";
571
572 echo '<tr>'."\n";
573 echo '<th style="width:260px; padding:0 10px 0 0;">'."\n";
574 if((int)$ppco_plan_count_live || (int)$ppco_prod_count_live)
575 echo '<a class="button" style="min-width:250px; text-align:center;" href="'.esc_attr($ppco_clear_cache_live_url).'" onclick="return window.s2mPpcoPostAction(\'clear_cache\',\'live\',\''.esc_attr($ppco_clear_cache_nonce).'\');">'.esc_html__('Clear Plan/Product Cache', 's2member').'</a>'."\n";
576 else echo '<span class="button disabled" style="min-width:250px; text-align:center;" title="'.esc_attr__('Cache is already empty.', 's2member').'">'.esc_html__('Clear Plan/Product Cache', 's2member').'</span>'."\n";
577 echo '</th>'."\n";
578 echo '<td style="padding:0;">'."\n";
579 echo '<em>'.sprintf(esc_html__('Clears the local PayPal Checkout Product/Plan ID cache (used to avoid creating duplicates). Existing subscriptions are unaffected. Next checkout will create new Products/Plans as needed. Current cache: %1$s plan(s), %2$s product(s).', 's2member'), (int)$ppco_plan_count_live, (int)$ppco_prod_count_live).'</em>'."\n";
580 echo '</td>'."\n";
581 echo '</tr>'."\n";
582
583 echo '</tbody>'."\n";
584 echo '</table>'."\n";
585
586 echo '</td>'."\n";
587 echo '</tr>'."\n";
588
589 echo '</tbody>'."\n";
590 echo '</table>'."\n";
591 echo '</div>'."\n";
592
593 echo '<div class="ws-menu-page-hr"></div>'."\n";
594
595 echo '<div class="s2m-ppco-env-wrap s2m-ppco-env-sandbox-wrap'.((!$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox"]) ? ' s2m-ppco-env-inactive' : '').'">'."\n";
596 echo '<h3 style="margin:0 0 6px 0;">Sandbox</h3>'."\n";
597
598 echo '<table class="form-table">'."\n";
599 echo '<tbody>'."\n";
600
601 echo '<tr>'."\n";
602 echo '<th>'."\n";
603 echo '<label for="ws-plugin--s2member-paypal-checkout-sandbox-client-id">'."\n";
604 echo 'PayPal Checkout Client ID (Sandbox — REST API):'."\n";
605 echo '</label>'."\n";
606 echo '</th>'."\n";
607 echo '</tr>'."\n";
608
609 echo '<tr>'."\n";
610 echo '<td>'."\n";
611 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_paypal_checkout_sandbox_client_id" id="ws-plugin--s2member-paypal-checkout-sandbox-client-id" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_id"]).'" /><br />'."\n";
612 echo 'At PayPal, see: <strong><a href="https://developer.paypal.com/dashboard/applications/sandbox" target="_blank" rel="external">My Apps &amp; Credentials (Sandbox)</a></strong>'."\n";
613 echo '</td>'."\n";
614 echo '</tr>'."\n";
615
616 echo '<tr>'."\n";
617 echo '<th>'."\n";
618 echo '<label for="ws-plugin--s2member-paypal-checkout-sandbox-client-secret">'."\n";
619 echo 'PayPal Checkout Client Secret (Sandbox — REST API):'."\n";
620 echo '</label>'."\n";
621 echo '</th>'."\n";
622 echo '</tr>'."\n";
623
624 echo '<tr>'."\n";
625 echo '<td>'."\n";
626 echo '<input type="password" autocomplete="off" name="ws_plugin__s2member_paypal_checkout_sandbox_client_secret" id="ws-plugin--s2member-paypal-checkout-sandbox-client-secret" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_secret"]).'" /><br />'."\n";
627 echo 'At PayPal, see: <strong><a href="https://developer.paypal.com/dashboard/applications/sandbox" target="_blank" rel="external">My Apps &amp; Credentials (Sandbox)</a></strong>'."\n";
628
629 echo '<table class="form-table" style="margin:10px 0 0 0;">'."\n";
630 echo '<tbody>'."\n";
631 echo '<tr>'."\n";
632 echo '<th style="width:260px; padding:0 10px 0 0;">'."\n";
633 if($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_id"] && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_secret"])
634 echo '<a class="button" style="min-width:240px; text-align:center;" href="'.esc_attr($ppco_creds_test_sandbox_url).'" onclick="return window.s2mPpcoPostAction(\'creds_test\',\'sandbox\',\''.esc_attr($ppco_creds_test_nonce).'\');">'.esc_html__('Test Credentials', 's2member').'</a>'."\n";
635 else echo '<span class="button" style="min-width:240px; text-align:center; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('Save Sandbox credentials first.', 's2member').'">'.esc_html__('Test Credentials', 's2member').'</span>'."\n";
636 echo '</th>'."\n";
637 echo '<td style="padding:0;">'."\n";
638 echo '<em>'.esc_html__('Verifies your Sandbox Client ID/Secret by requesting an access token.', 's2member').'</em>'."\n";
639 echo '</td>'."\n";
640 echo '</tr>'."\n";
641 echo '</tbody>'."\n";
642 echo '</table>'."\n";
643
644 echo '</td>'."\n";
645 echo '</tr>'."\n";
646
647 echo '<tr>'."\n";
648 echo '<th>'."\n";
649 echo '<label for="ws-plugin--s2member-paypal-checkout-sandbox-webhook-id">'."\n";
650 echo 'PayPal Checkout Webhook ID (Sandbox):'."\n";
651 echo '</label>'."\n";
652 echo '</th>'."\n";
653 echo '</tr>'."\n";
654
655 echo '<tr>'."\n";
656 echo '<td>'."\n";
657 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_paypal_checkout_sandbox_webhook_id" id="ws-plugin--s2member-paypal-checkout-sandbox-webhook-id" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_webhook_id"]).'" />'."\n";
658 echo '<br /><em>'.esc_html__('From your PayPal app webhook settings (Sandbox).', 's2member').'</em><br />'."\n";
659
660 echo '<table class="form-table" style="margin:12px 0 0 0;">'."\n";
661 echo '<tbody>'."\n";
662
663 echo '<tr>'."\n";
664 echo '<th style="width:260px; padding:0 10px 0 0;">'."\n";
665 if($ppco_https_ready && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_id"] && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_secret"])
666 echo '<a class="button button-primary" style="min-width:250px; text-align:center; color:#fff !important;" href="'.esc_attr($ppco_webhook_sandbox_url).'" onclick="return window.s2mPpcoPostAction(\'webhook\',\'sandbox\',\''.esc_attr($ppco_webhook_nonce).'\');">'.esc_html__('Create/Update Webhook', 's2member').'</a>'."\n";
667 else if(!$ppco_https_ready)
668 echo '<span class="button button-primary" style="min-width:250px; text-align:center; color:#fff !important; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('HTTPS required.', 's2member').'">'.esc_html__('Create/Update Webhook', 's2member').'</span>'."\n";
669 else
670 echo '<span class="button button-primary" style="min-width:250px; text-align:center; color:#fff !important; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('Save Sandbox credentials first.', 's2member').'">'.esc_html__('Create/Update Webhook', 's2member').'</span>'."\n";
671 echo '</th>'."\n";
672 echo '<td style="padding:0;">'."\n";
673 echo '<em>'.esc_html__('Registers or updates your webhook URL and required events with PayPal (HTTPS required).', 's2member').' '.esc_html__('If your site URL changes, re-run Create/Update Webhook to update the callback URL at PayPal.', 's2member').'</em>'."\n";
674 echo '</td>'."\n";
675 echo '</tr>'."\n";
676
677 echo '<tr>'."\n";
678 echo '<th style="width:260px; padding:8px 10px 0 0;">'."\n";
679 if($ppco_https_ready && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_id"] && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_secret"] && $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_webhook_id"])
680 echo '<a class="button" style="min-width:250px; text-align:center;" target="_blank" href="'.esc_attr($ppco_webhook_test_sandbox_url).'">'.esc_html__('Test Webhook Endpoint', 's2member').'</a>'."\n";
681 else if(!$ppco_https_ready)
682 echo '<span class="button" style="min-width:250px; text-align:center; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('HTTPS required.', 's2member').'">'.esc_html__('Test Webhook Endpoint', 's2member').'</span>'."\n";
683 else if(!$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_id"] || !$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_checkout_sandbox_client_secret"])
684 echo '<span class="button" style="min-width:250px; text-align:center; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('Save Sandbox credentials first.', 's2member').'">'.esc_html__('Test Webhook Endpoint', 's2member').'</span>'."\n";
685 else
686 echo '<span class="button" style="min-width:250px; text-align:center; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('Set a Sandbox Webhook ID first (or use Create/Update Webhook).', 's2member').'">'.esc_html__('Test Webhook Endpoint', 's2member').'</span>'."\n";
687 echo '</th>'."\n";
688 echo '<td style="padding:8px 0 0 0;">'."\n";
689 echo '<em>'.esc_html__('Reachability-only check. Use real webhooks for end-to-end verification.', 's2member').'</em>'."\n";
690 echo '<br /><em>'.esc_html__('Test URL: ', 's2member').'<code>'.esc_html($ppco_webhook_test_sandbox_url).'</code></em>'."\n";
691 echo '</td>'."\n";
692 echo '</tr>'."\n";
693
694 echo '</tbody>'."\n";
695 echo '</table>'."\n";
696
697 echo '<br /><br />'."\n";
698 echo '<em>'.esc_html__('Webhook URL:', 's2member').'</em><br /><code>'.esc_html(add_query_arg("s2member_paypal_webhook", "1", home_url("/", 'https'))).'</code>'."\n";
699 echo '<br /><em class="ws-menu-page-hilite">'.esc_html__('Note: PayPal requires an HTTPS (SSL) webhook URL.', 's2member').'</em>'."\n";
700
701 //260824.1727 Include payment, dispute, and subscription lifecycle events required by Checkout processing.
702 echo '<br /><br /><em>'.esc_html__('Required Events:', 's2member').'</em><br /><code>PAYMENT.SALE.COMPLETED</code> <code>PAYMENT.CAPTURE.COMPLETED</code> <code>PAYMENT.SALE.REFUNDED</code> <code>PAYMENT.CAPTURE.REFUNDED</code> <code>PAYMENT.SALE.REVERSED</code> <code>PAYMENT.CAPTURE.REVERSED</code> <code>CUSTOMER.DISPUTE.CREATED</code> <code>BILLING.SUBSCRIPTION.CREATED</code> <code>BILLING.SUBSCRIPTION.ACTIVATED</code> <code>BILLING.SUBSCRIPTION.RE-ACTIVATED</code> <code>BILLING.SUBSCRIPTION.UPDATED</code> <code>BILLING.SUBSCRIPTION.CANCELLED</code> <code>BILLING.SUBSCRIPTION.SUSPENDED</code> <code>BILLING.SUBSCRIPTION.EXPIRED</code> <code>BILLING.SUBSCRIPTION.PAYMENT.FAILED</code>'."\n";
703
704 echo '<p style="margin:10px 0 0 0;"><em>'.esc_html__('Tip: Use PayPal\'s Webhooks Events dashboard to view and re-deliver webhook events while testing.', 's2member').' <a href="https://developer.paypal.com/api/rest/webhooks/events-dashboard/" target="_blank" rel="external">'.esc_html__('Open Events Dashboard', 's2member').'</a></em></p>'."\n";
705
706 if(!$ppco_https_ready)
707 echo '<p style="margin:10px 0 0 0;"><span class="ws-menu-page-error">'.esc_html__('Disabled: PayPal webhooks require a working HTTPS (SSL) endpoint on this domain.', 's2member').'</span></p>'."\n";
708
709 echo '<p style="margin:16px 0 10px 0;"><strong>'.esc_html__('Plan/Product Cache (Sandbox)', 's2member').'</strong></p>'."\n";
710 echo '<table class="form-table" style="margin:0;">'."\n";
711 echo '<tbody>'."\n";
712
713 echo '<tr>'."\n";
714 echo '<th style="width:260px; padding:0 10px 0 0;">'."\n";
715 if((int)$ppco_plan_count_sandbox || (int)$ppco_prod_count_sandbox)
716 echo '<a class="button" style="min-width:250px; text-align:center;" href="'.esc_attr($ppco_clear_cache_sandbox_url).'" onclick="return window.s2mPpcoPostAction(\'clear_cache\',\'sandbox\',\''.esc_attr($ppco_clear_cache_nonce).'\');">'.esc_html__('Clear Plan/Product Cache', 's2member').'</a>'."\n";
717 else echo '<span class="button" style="min-width:250px; text-align:center; opacity:0.50; cursor:default; pointer-events:none;" title="'.esc_attr__('Cache is already empty.', 's2member').'">'.esc_html__('Clear Plan/Product Cache', 's2member').'</span>'."\n";
718 echo '</th>'."\n";
719 echo '<td style="padding:0;">'."\n";
720 echo '<em>'.sprintf(esc_html__('Clears the local PayPal Checkout Product/Plan ID cache (used to avoid creating duplicates). Existing subscriptions are unaffected. Next checkout will create new Products/Plans as needed. Current cache: %1$s plan(s), %2$s product(s).', 's2member'), (int)$ppco_plan_count_sandbox, (int)$ppco_prod_count_sandbox).'</em>'."\n";
721 echo '</td>'."\n";
722 echo '</tr>'."\n";
723
724 echo '</tbody>'."\n";
725 echo '</table>'."\n";
726
727 echo '</td>'."\n";
728 echo '</tr>'."\n";
729
730 echo '</tbody>'."\n";
731 echo '</table>'."\n";
732 echo '</div>'."\n";
733
734 echo '</div>'."\n"; // .ws-menu-page-section
735
736 echo '</div>'."\n"; // .ws-menu-page-group
737
738 echo '<div class="ws-menu-page-group" title="PayPal Account Details">'."\n";
739
740 echo '<div class="ws-menu-page-section ws-plugin--s2member-paypal-account-details-section">'."\n";
741 echo '<a href="https://s2member.com/r/paypal/" target="_blank"><img src="'.esc_attr($GLOBALS["WS_PLUGIN__"]["s2member"]["c"]["dir_url"]).'/src/images/paypal-logo.png" class="ws-menu-page-right" style="width:125px; height:125px; border:0;" alt="." /></a>'."\n";
742 echo '<h3>PayPal Account Details (required, if using PayPal)</h3>'."\n";
743 echo '<p>s2Member integrates with <a href="https://s2member.com/r/paypal/" target="_blank" rel="external">PayPal Payments Standard</a>—for businesses. You do not need a PayPal Pro account. You just need to upgrade your Personal PayPal account to a Business status, which is free. A PayPal account can be <a href="http://s2member.com/r/paypal-business-upgrade/" target="_blank" rel="external">upgraded</a> from a Personal account to a Business account by clicking the "Profile" link under your "My Account" tab, selecting "Personal Business Information", and then clicking the "Upgrade Your Account" button. <strong>See also:</strong> This KB article: <a href="http://s2member.com/kb-article/supported-paypal-account-types/" target="_blank" rel="external">PayPal Compatibility (Account Types)</a>.</p>'."\n";
744 echo '<p><em><strong>PayPal API Credentials:</strong> Once you have a PayPal Business account, you\'ll need access to your <a href="http://s2member.com/r/paypal-profile-api-access/" target="_blank" rel="external">PayPal API Credentials</a>. Log into your PayPal account, and navigate to <strong>Profile → API Access (or → Request API Credentials)</strong>. From the available options, please choose "Request API Signature".</em></p>'."\n";
745 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_account_details", get_defined_vars());
746
747 echo '<table class="form-table">'."\n";
748 echo '<tbody>'."\n";
749 echo '<tr>'."\n";
750
751 echo '<th>'."\n";
752 echo '<label for="ws-plugin--s2member-paypal-merchant-id">'."\n";
753 echo 'Your PayPal Merchant ID:'."\n";
754 echo '</label>'."\n";
755 echo '</th>'."\n";
756
757 echo '</tr>'."\n";
758 echo '<tr>'."\n";
759
760 echo '<td>'."\n";
761 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_paypal_merchant_id" id="ws-plugin--s2member-paypal-merchant-id" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_merchant_id"]).'" /><br />'."\n";
762 echo 'At PayPal, see: <strong><a href="https://www.paypal.com/businessmanage/account/aboutBusiness" target="_blank" rel="external">Account Settings → Business Information → PayPal Merchant ID</a></strong>'."\n";
763 echo '</td>'."\n";
764
765 echo '</tr>'."\n";
766 echo '<tr>'."\n";
767
768 echo '<th>'."\n";
769 echo '<label for="ws-plugin--s2member-paypal-business">'."\n";
770 echo 'Your PayPal EMail Address:'."\n";
771 echo '</label>'."\n";
772 echo '</th>'."\n";
773
774 echo '</tr>'."\n";
775 echo '<tr>'."\n";
776
777 echo '<td>'."\n";
778 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_paypal_business" id="ws-plugin--s2member-paypal-business" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_business"]).'" /><br />'."\n";
779 echo 'At PayPal, see: <strong><a href="https://www.paypal.com/businessmanage/account/accountOwner" target="_blank" rel="external">Account Settings → Account Owner Information → Email Address</a></strong>'."\n";
780 echo '</td>'."\n";
781
782 echo '</tr>'."\n";
783 echo '</tbody>'."\n";
784 echo '</table>'."\n";
785
786 echo '<div class="ws-menu-page-hr"></div>'."\n";
787
788 echo '<table class="form-table">'."\n";
789 echo '<tbody>'."\n";
790 echo '<tr>'."\n";
791
792 echo '<th>'."\n";
793 echo '<label for="ws-plugin--s2member-paypal-api-username">'."\n";
794 echo 'Your PayPal API Username:'."\n";
795 echo '</label>'."\n";
796 echo '</th>'."\n";
797
798 echo '</tr>'."\n";
799 echo '<tr>'."\n";
800
801 echo '<td>'."\n";
802 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_paypal_api_username" id="ws-plugin--s2member-paypal-api-username" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_api_username"]).'" /><br />'."\n";
803 echo 'At PayPal, see: <strong><a href="https://www.paypal.com/businessprofile/mytools/apiaccess/firstparty/signature" target="_blank" rel="external">Account Settings → Website Payments → API Access → NVP/SOAP API integration (Classic) → Manage API Credentials</a></strong>'."\n";
804 echo '</td>'."\n";
805
806 echo '</tr>'."\n";
807 echo '<tr>'."\n";
808
809 echo '<th>'."\n";
810 echo '<label for="ws-plugin--s2member-paypal-api-password">'."\n";
811 echo 'Your PayPal API Password:'."\n";
812 echo '</label>'."\n";
813 echo '</th>'."\n";
814
815 echo '</tr>'."\n";
816 echo '<tr>'."\n";
817
818 echo '<td>'."\n";
819 echo '<input type="password" autocomplete="off" name="ws_plugin__s2member_paypal_api_password" id="ws-plugin--s2member-paypal-api-password" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_api_password"]).'" /><br />'."\n";
820 echo 'At PayPal, see: <strong><a href="https://www.paypal.com/businessprofile/mytools/apiaccess/firstparty/signature" target="_blank" rel="external">Account Settings → Website Payments → API Access → NVP/SOAP API integration (Classic) → Manage API Credentials</a></strong>'."\n";
821 echo '</td>'."\n";
822
823 echo '</tr>'."\n";
824 echo '<tr>'."\n";
825
826 echo '<th>'."\n";
827 echo '<label for="ws-plugin--s2member-paypal-api-signature">'."\n";
828 echo 'Your PayPal API Signature:'."\n";
829 echo '</label>'."\n";
830 echo '</th>'."\n";
831
832 echo '</tr>'."\n";
833 echo '<tr>'."\n";
834
835 echo '<td>'."\n";
836 echo '<input type="password" autocomplete="off" name="ws_plugin__s2member_paypal_api_signature" id="ws-plugin--s2member-paypal-api-signature" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_api_signature"]).'" /><br />'."\n";
837 echo 'At PayPal, see: <strong><a href="https://www.paypal.com/businessprofile/mytools/apiaccess/firstparty/signature" target="_blank" rel="external">Account Settings → Website Payments → API Access → NVP/SOAP API integration (Classic) → Manage API Credentials</a></strong>'."\n";
838 echo '</td>'."\n";
839
840 echo '</tr>'."\n";
841 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_account_detail_rows", get_defined_vars());
842
843 echo '</tbody>'."\n";
844 echo '</table>'."\n";
845
846 echo '<div class="ws-menu-page-hr"></div>'."\n";
847
848 echo '<table class="form-table" style="margin:0;">'."\n";
849 echo '<tbody>'."\n";
850 echo '<tr>'."\n";
851
852 echo '<th style="padding-top:0;">'."\n";
853 echo '<label for="ws-plugin--s2member-paypal-sandbox">'."\n";
854 echo 'Developer/Sandbox Testing?'."\n";
855 echo '</label>'."\n";
856 echo '</th>'."\n";
857
858 echo '</tr>'."\n";
859 echo '<tr>'."\n";
860
861 echo '<td>'."\n";
862 echo '<input type="radio" name="ws_plugin__s2member_paypal_sandbox" id="ws-plugin--s2member-paypal-sandbox-0" value="0"'.((!$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_sandbox"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-paypal-sandbox-0">No</label> &nbsp;&nbsp;&nbsp; <input type="radio" name="ws_plugin__s2member_paypal_sandbox" id="ws-plugin--s2member-paypal-sandbox-1" value="1"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_sandbox"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-paypal-sandbox-1">Yes, enable support for Sandbox testing.</label><br />'."\n";
863 echo '<em>Only enable this if you\'ve provided Sandbox credentials above.<br />This puts the API, IPN, PDT and Form/Button Generators all into Sandbox mode. See: <a href="http://s2member.com/r/paypal-developers/" target="_blank" rel="external">PayPal Developers</a></em><br />'."\n";
864 echo '<em><strong>Warning:</strong> The PayPal Sandbox doesn\'t always give you an accurate view of what will happen once you go live, and in fact it is sometimes buggy at best. For this reason, our strong recommendation is that instead of using Sandbox Mode to run tests, that you go live and run tests with low-dollar amounts; i.e., $0.01 transactions are possible with PayPal in live mode, and that is a better way to test your installation of s2Member.</em>'."\n";
865 echo '</td>'."\n";
866
867 echo '</tr>'."\n";
868 echo '<tr>'."\n";
869
870 echo '<th>'."\n";
871 echo '<label for="ws-plugin--s2member-paypal-btn-encryption">'."\n";
872 echo 'Button Encryption'."\n";
873 echo '</label>'."\n";
874 echo '</th>'."\n";
875
876 echo '</tr>'."\n";
877 echo '<tr>'."\n";
878
879 echo '<td>'."\n";
880 echo '<p style="margin-bottom: 0.5em;"><span class="ws-menu-page-error">⚠️ PayPal has given some trouble recently with encrypted buttons, so for the time being it\'s recommended to leave it disabled and allow non-encrypted payments. <a href="https://www.paypal.com/businessmanage/preferences/website" target="_blank" rel="external" style="font-style: italic;">PayPal Account Settings → Website Payments → Website Preferences → Encrypted Website Payments</a></span></p>'."\n"; //230807
881 echo '<input type="radio" name="ws_plugin__s2member_paypal_btn_encryption" id="ws-plugin--s2member-paypal-btn-encryption-0" value="0"'.((!$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_btn_encryption"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-paypal-btn-encryption-0">No</label> &nbsp;&nbsp;&nbsp; <input type="radio" name="ws_plugin__s2member_paypal_btn_encryption" id="ws-plugin--s2member-paypal-btn-encryption-1" value="1"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_btn_encryption"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-paypal-btn-encryption-1">Yes, enable PayPal Button encryption.</label><br />'."\n";
882 echo '<em>If enabled, all of your PayPal Button Shortcodes will produce <em>encrypted</em> PayPal Buttons. This improves security against fraudulent transactions. For extra security, you should update your PayPal account too, under: <strong><a href="https://www.paypal.com/businessmanage/preferences/website" target="_blank" rel="external">Account Settings → Website Payments → Website Preferences → Encrypted Website Payments</a></strong>. You\'ll want to block all non-encrypted payments. <strong>Note:</strong> this will NOT work until you\'ve supplied s2Member with your PayPal Email Address, and also with your API Username/Password/Signature.</em>'."\n";
883 echo '</td>'."\n";
884
885 echo '</tr>'."\n";
886
887 if(!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site())
888 {
889 echo '<tr>'."\n";
890
891 echo '<th>'."\n";
892 echo '<label for="ws-plugin--s2member-gateway-debug-logs">'."\n";
893 echo 'Enable Logging Routines?'."\n";
894 echo '</label>'."\n";
895 echo '</th>'."\n";
896
897 echo '</tr>'."\n";
898 echo '<tr>'."\n";
899
900 echo '<td>'."\n";
901 echo '<input type="radio" name="ws_plugin__s2member_gateway_debug_logs" id="ws-plugin--s2member-gateway-debug-logs-0" value="0"'.((!$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["gateway_debug_logs"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-gateway-debug-logs-0">No</label> &nbsp;&nbsp;&nbsp; <input type="radio" name="ws_plugin__s2member_gateway_debug_logs" id="ws-plugin--s2member-gateway-debug-logs-1" value="1"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["gateway_debug_logs"]) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-gateway-debug-logs-1">Yes, enable debugging, with API, IPN &amp; Return Page logging.</label><br />'."\n";
902 echo '<em>This enables API, IPN and Return Page logging. The log files are stored here: <code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($GLOBALS["WS_PLUGIN__"]["s2member"]["c"]["logs_dir"])).'</code></em><br />'."\n";
903 echo '<em class="ws-menu-page-hilite">If you have any trouble, please review your s2Member log files for problems. See: <a href="'.esc_attr(admin_url("/admin.php?page=ws-plugin--s2member-logs")).'">Log Viewer</a></em>'."\n";
904 echo '</td>'."\n";
905
906 echo '</tr>'."\n";
907 echo '<tr>'."\n";
908
909 echo '<td>'."\n";
910 echo '<div class="info" style="margin-bottom:0;">'."\n";
911 echo '<p style="margin-top:0;"><span>We highly recommend that you enable logging during your initial testing phase. Logs produce lots of useful details that can help in debugging. Logs can help you find issues in your configuration and/or problems during payment processing. See: <a href="'.esc_attr(admin_url("/admin.php?page=ws-plugin--s2member-logs")).'">Log Files (Debug)</a>.</span></p>'."\n";
912 echo '<p style="margin-bottom:0;"><span class="ws-menu-page-error">However, it is very important to disable logging once you go live. Log files may contain personally identifiable information, credit card numbers, secret API credentials, passwords and/or other sensitive information. We strongly suggest that logging be disabled on a live site (for security reasons).</span></p>'."\n";
913 echo '</div>'."\n";
914 echo '</td>'."\n";
915
916 echo '</tr>'."\n";
917 }
918 echo '</tbody>'."\n";
919 echo '</table>'."\n";
920
921 echo '<div class="ws-menu-page-hr"></div>'."\n";
922
923 echo '<p><em><strong>Sandbox Tip:</strong> If you\'re testing your site through a PayPal Sandbox account, please remember that Email Confirmations from s2Member will not be received after a test purchase. s2Member sends its Confirmation Emails to the PayPal Email Address of the Customer. Since PayPal Sandbox addresses are usually bogus (for testing), you will have to run live transactions before Email Confirmations from s2Member are received. That being said, all other s2Member functionality can be tested through a PayPal Sandbox account. Email Confirmations are the only hang-up.</em></p>'."\n";
924 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_account_details_after_sandbox_tip", get_defined_vars());
925 echo '</div>'."\n";
926
927 echo '</div>'."\n";
928
929 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_after_paypal_account_details", get_defined_vars());
930 }
931
932 if(apply_filters("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_display_paypal_payflow_account_details", c_ws_plugin__s2member_utils_conds::pro_is_installed(), get_defined_vars()))
933 {
934 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_before_paypal_payflow_account_details", get_defined_vars());
935
936 echo '<div class="ws-menu-page-group" title="PayPal Pro Payflow Edition API Credentials">'."\n";
937
938 echo '<div class="ws-menu-page-section ws-plugin--s2member-paypal-payflow-account-details-section">'."\n";
939 echo '<a href="https://s2member.com/r/paypal/" target="_blank"><img src="'.esc_attr($GLOBALS["WS_PLUGIN__"]["s2member"]["c"]["dir_url"]).'/src/images/paypal-logo.png" class="ws-menu-page-right" style="width:125px; height:125px; border:0;" alt="." /></a>'."\n";
940 echo '<h3>Payflow Account Details (required, if using Payflow)</h3>'."\n";
941 echo '<p>Newer PayPal Pro accounts (i.e., PayPal Pro w/ Payflow Edition), come with the Payflow API for Recurring Billing service. If you have a newer PayPal Pro account, and you wish to integrate PayPal\'s Recurring Billing service with s2Member Pro-Forms, you will need to fill in the details here. Providing Payflow API Credentials below, will automatically put s2Member\'s Recurring Billing integration through Pro-Forms into Payflow mode. Just fill in the details below, and you\'re ready to generate Pro-Forms that charge customers on a recurring basis. s2Member will use the Payflow Edition API instead of the older PayPal Pro DPRP service; which is being slowly phased out in favor of Payflow Edition APIs.</p>'."\n";
942 echo '<p><em><strong>Payflow API Credentials:</strong> Once you have a PayPal Pro account, you\'ll need access to your <a href="http://s2member.com/r/paypal-profile-api-access/" target="_blank" rel="external">Payflow API Credentials</a>. Log into your PayPal account, and navigate to <strong>Profile → API Access (or → Request API Credentials)</strong>. From the available options, please choose "Payflow / API Access". You will need the following credentials: Username, Password, Partner, and Vendor.</em></p>'."\n";
943 echo '<p><em><strong>Important Note:</strong> Supplying Payflow API Credentials here does not mean you can bypass other areas of s2Member\'s configuration; i.e., please supply s2Member with all of your PayPal account details. Your PayPal Pro (Payflow Edition) account is configured here, but up above you will need to configure other Account Details also.</em></p>'."\n";
944 echo '<p><strong>See also:</strong> This KB article: <a href="http://s2member.com/kb-article/supported-paypal-account-types/" target="_blank" rel="external">PayPal Account Types</a>.</p>'."\n";
945 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_payflow_account_details", get_defined_vars());
946
947 echo '<table class="form-table">'."\n";
948 echo '<tbody>'."\n";
949 echo '<tr>'."\n";
950
951 echo '<th>'."\n";
952 echo '<label for="ws-plugin--s2member-paypal-payflow-api-username">'."\n";
953 echo 'Your Payflow API Username:'."\n";
954 echo '</label>'."\n";
955 echo '</th>'."\n";
956
957 echo '</tr>'."\n";
958 echo '<tr>'."\n";
959
960 echo '<td>'."\n";
961 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_paypal_payflow_api_username" id="ws-plugin--s2member-paypal-payflow-api-username" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_payflow_api_username"]).'" /><br />'."\n";
962 echo 'At PayPal, see: <strong>Profile → API Access (or → Request API Credentials) → Payflow API Access</strong>'."\n";
963 echo '</td>'."\n";
964
965 echo '</tr>'."\n";
966 echo '<tr>'."\n";
967
968 echo '<th>'."\n";
969 echo '<label for="ws-plugin--s2member-paypal-payflow-api-password">'."\n";
970 echo 'Your Payflow API Password:'."\n";
971 echo '</label>'."\n";
972 echo '</th>'."\n";
973
974 echo '</tr>'."\n";
975 echo '<tr>'."\n";
976
977 echo '<td>'."\n";
978 echo '<input type="password" autocomplete="off" name="ws_plugin__s2member_paypal_payflow_api_password" id="ws-plugin--s2member-paypal-payflow-api-password" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_payflow_api_password"]).'" /><br />'."\n";
979 echo 'At PayPal, see: <strong>Profile → API Access (or → Request API Credentials) → Payflow API Access</strong>'."\n";
980 echo '</td>'."\n";
981
982 echo '</tr>'."\n";
983 echo '<tr>'."\n";
984
985 echo '<th>'."\n";
986 echo '<label for="ws-plugin--s2member-paypal-payflow-api-partner">'."\n";
987 echo 'Your Payflow API Partner:'."\n";
988 echo '</label>'."\n";
989 echo '</th>'."\n";
990
991 echo '</tr>'."\n";
992 echo '<tr>'."\n";
993
994 echo '<td>'."\n";
995 echo '<input type="text" name="ws_plugin__s2member_paypal_payflow_api_partner" id="ws-plugin--s2member-paypal-payflow-api-partner" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_payflow_api_partner"]).'" /><br />'."\n";
996 echo 'At PayPal, see: <strong>Profile → API Access (or → Request API Credentials) → Payflow API Access</strong>'."\n";
997 echo '</td>'."\n";
998
999 echo '</tr>'."\n";
1000 echo '<tr>'."\n";
1001
1002 echo '<th>'."\n";
1003 echo '<label for="ws-plugin--s2member-paypal-payflow-api-vendor">'."\n";
1004 echo 'Your Payflow API Vendor:'."\n";
1005 echo '</label>'."\n";
1006 echo '</th>'."\n";
1007
1008 echo '</tr>'."\n";
1009 echo '<tr>'."\n";
1010
1011 echo '<td>'."\n";
1012 echo '<input type="text" name="ws_plugin__s2member_paypal_payflow_api_vendor" id="ws-plugin--s2member-paypal-payflow-api-vendor" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_payflow_api_vendor"]).'" /><br />'."\n";
1013 echo 'At PayPal, see: <strong>Profile → API Access (or → Request API Credentials) → Payflow API Access</strong>'."\n";
1014 echo '</td>'."\n";
1015
1016 echo '</tr>'."\n";
1017 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_payflow_account_detail_rows", get_defined_vars());
1018 echo '</tbody>'."\n";
1019 echo '</table>'."\n";
1020 echo '</div>'."\n";
1021
1022 echo '</div>'."\n";
1023
1024 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_after_paypal_payflow_account_details", get_defined_vars());
1025 }
1026
1027 if(apply_filters("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_display_paypal_ipn", TRUE, get_defined_vars()))
1028 {
1029 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_before_paypal_ipn", get_defined_vars());
1030
1031 echo '<div class="ws-menu-page-group" title="PayPal IPN Integration">'."\n";
1032
1033 echo '<div class="ws-menu-page-section ws-plugin--s2member-paypal-ipn-section">'."\n";
1034 echo '<h3>PayPal IPN / Instant Payment Notifications (required, please enable)</h3>'."\n";
1035 echo '<p>Log into your PayPal account and navigate to this section:<br /><strong><a href="http://s2member.com/r/paypal-com-ipn-configuration-page/" target="_blank" rel="external">Account Settings → Website Payments → Instant Payment Notifications</a></strong></p>'."\n";
1036 echo '<p>Edit your IPN settings &amp; turn IPN Notifications: <strong><code>On</code></strong></p>'."\n";
1037 echo '<p>You\'ll need your IPN URL, which is:<br /><code>'.esc_html(home_url("/?s2member_paypal_notify=1", "https")).'</code></p>'."\n";
1038 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_ipn", get_defined_vars());
1039
1040 echo '<h4 style="margin-bottom:0;"><strong class="ws-menu-page-hilite">Note: SSL is required by PayPal</strong></h4>'."\n";
1041 echo '<p style="margin-top:0;">If you configure your PayPal.com account using the URL above, your site <strong><em>must</em> support SSL</strong> (i.e., the <code>https://</code> protocol). In other words, PayPal\'s system will refuse to accept any URL that does not begin with <code>https://</code>. The IPN URL that s2Member provides (see above) <em>does</em> start with <code>https://</code>. However, that doesn\'t necessarily mean that the URL actually works. Please be sure that your hosting account is configured with a valid SSL certificate before giving this URL to PayPal.</p>'."\n";
1042
1043 //250426 Fallback for IPN Signup Vars.
1044 echo '<div class="ws-menu-page-hr"></div>'."\n";
1045
1046 echo '<table class="form-table">'."\n";
1047 echo '<tbody>'."\n";
1048 echo '<tr>'."\n";
1049
1050 echo '<th>'."\n";
1051 echo '<label for="ws-plugin--s2member-ipn-signup-vars-fallback">'."\n";
1052 echo 'Enable Fallback for Missing User "IPN Signup Vars"? (beta)<br />'."\n";
1053 echo '<small><em class="ws-menu-page-hilite">* This setting applies to all gateways. [ <a href="#" onclick="alert(\'This configuration option may also appear under the other gateways. You can change it here, but remember that this setting is shared among all Payment Gateways integrated with s2Member.\'); return false;">?</a> ]</em></small>'."\n";
1054 echo '</label>'."\n";
1055 echo '</th>'."\n";
1056
1057 echo '</tr>'."\n";
1058 echo '<tr>'."\n";
1059
1060 echo '<td>'."\n";
1061 echo '<input type="radio" name="ws_plugin__s2member_ipn_signup_vars_fallback" id="ws-plugin--s2member-ipn-signup-vars-fallback-0" value="0"'.((!$GLOBALS['WS_PLUGIN__']['s2member']['o']['ipn_signup_vars_fallback']) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-ipn-signup-vars-fallback-0">No</label> &nbsp;&nbsp;&nbsp; <input type="radio" name="ws_plugin__s2member_ipn_signup_vars_fallback" id="ws-plugin--s2member-ipn-signup-vars-fallback-1" value="1"'.(($GLOBALS['WS_PLUGIN__']['s2member']['o']['ipn_signup_vars_fallback']) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-ipn-signup-vars-fallback-1">Yes, enable user IPN Signup Vars fallback.</label><br />'."\n";
1062 echo '<em>Enable fallback behavior when the user\'s IPN Signup Variables are missing during the IPN event processing (useful for migrated or imported subscriptions, for example).</em><br />'."\n";
1063 echo '</td>'."\n";
1064
1065 echo '</tr>'."\n";
1066 echo '</tbody>'."\n";
1067 echo '</table>'."\n";
1068
1069 //250606 Skip Custom Value Domain Validation.
1070 echo '<div class="ws-menu-page-hr"></div>'."\n";
1071
1072 echo '<table class="form-table">'."\n";
1073 echo '<tbody>'."\n";
1074 echo '<tr>'."\n";
1075
1076 echo '<th>'."\n";
1077 echo '<label for="ws-plugin--s2member-skip-ipn-domain-validation">'."\n";
1078 echo 'Skip Domain Validation of the `custom` Value? (beta)<br />'."\n";
1079 echo '<small><em class="ws-menu-page-hilite">* This setting applies to all gateways. [ <a href="#" onclick="alert(\'This configuration option may also appear under the other gateways. You can change it here, but remember that this setting is shared among all Payment Gateways integrated with s2Member.\'); return false;">?</a> ]</em></small>'."\n";
1080 echo '</label>'."\n";
1081 echo '</th>'."\n";
1082
1083 echo '</tr>'."\n";
1084 echo '<tr>'."\n";
1085
1086 echo '<td>'."\n";
1087 echo '<input type="radio" name="ws_plugin__s2member_skip_ipn_domain_validation" id="ws-plugin--s2member-skip-ipn-domain-validation-0" value="0"'.((!$GLOBALS['WS_PLUGIN__']['s2member']['o']['skip_ipn_domain_validation']) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-skip-ipn-domain-validation-0">No</label> &nbsp;&nbsp;&nbsp; <input type="radio" name="ws_plugin__s2member_skip_ipn_domain_validation" id="ws-plugin--s2member-skip-ipn-domain-validation-1" value="1"'.(($GLOBALS['WS_PLUGIN__']['s2member']['o']['skip_ipn_domain_validation']) ? ' checked="checked"' : '').' /> <label for="ws-plugin--s2member-skip-ipn-domain-validation-1">Yes, skip domain checks in the `custom` value.</label><br />'."\n";
1088 echo '<em>This allows continued processing of notifications even if the domain in the `custom` value doesn\'t match the current <code>'.esc_html($_SERVER["HTTP_HOST"]).'</code>. Useful for subscriptions originated outside of s2Member, or under a different domain.</em><br />'."\n";
1089 echo '</td>'."\n";
1090
1091 echo '</tr>'."\n";
1092 echo '</tbody>'."\n";
1093 echo '</table>'."\n";
1094
1095 echo '<div class="ws-menu-page-hr"></div>'."\n";
1096
1097 echo '<h3 style="margin:0;">More Information (<a href="#" onclick="jQuery(\'div#ws-plugin--s2member-paypal-ipn-details\').toggle(); return false;" class="ws-dotted-link">click here</a>)</h3>'."\n";
1098 echo '<div id="ws-plugin--s2member-paypal-ipn-details" style="margin-top:10px; display:none;">'."\n";
1099 echo '<p><em><strong>IPN History:</strong> Here\'s PayPal\'s <a href="https://s2member.com/r/paypal-ipn-history/" target="_blank" rel="external">Instant Payment Notification (IPN) History page</a>.</em></p>';
1100 echo '<p><em><strong>Quick Tip:</strong> In addition to the <a href="http://s2member.com/r/paypal-com-ipn-configuration-page/" target="_blank" rel="external">default IPN settings inside your PayPal account</a>, the IPN URL is also set on a per-transaction basis by the special PayPal Button Code that s2Member provides you with. In other words, if you have multiple sites operating on one PayPal account, that\'s OK. s2Member dynamically sets the IPN URL for each transaction. The result is that the IPN URL configured from within your PayPal account, becomes the default, which is then overwritten on a per-transaction basis. In fact, PayPal recently updated their system to support IPN URL preservation. One PayPal account can handle multiple sites, all using different IPN URLs.</em></p>'."\n";
1101 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_ipn_after_quick_tip", get_defined_vars());
1102 echo '<p><em><strong>IPN Communications:</strong> You\'ll be happy to know that s2Member handles cancellations, expirations, failed payments, terminations (e.g., refunds &amp; chargebacks) for you automatically. If you log into your PayPal account and cancel a Member\'s Subscription, or, if the Member logs into their PayPal account and cancels their own Subscription, s2Member will be notified of these important changes and react accordingly through the PayPal IPN service that runs silently behind-the-scene. The PayPal IPN service will notify s2Member whenever a Member\'s payments have been failing, and/or whenever a Member\'s Subscription has expired for any reason. Even refunds &amp; chargeback reversals are supported through the IPN service. If you issue a refund to an unhappy Customer through PayPal, s2Member will be notified, and the account for that Customer will either be assigned the role selected under Demote To Role, or have the configured Delete behavior applied automatically. The communication from PayPal → s2Member is seamless.</em></p>'."\n";
1103 echo '</div>'."\n";
1104
1105 echo '<div class="ws-menu-page-hr"></div>'."\n";
1106
1107 echo '<h3 style="margin:0;">IPN w/ Proxy Key (<a href="#" onclick="jQuery(\'div#ws-plugin--s2member-paypal-ipn-proxy-details\').toggle(); return false;" class="ws-dotted-link">optional, for 3rd-party integrations</a>)</h3>'."\n";
1108 echo '<div id="ws-plugin--s2member-paypal-ipn-proxy-details" style="margin-top:10px; display:none;">'."\n";
1109 echo '<p>If you\'re using a 3rd-party application that needs to POST simulated IPN transactions to your s2Member installation, you can use this alternate IPN URL, which includes a Proxy Key. This encrypted Proxy Key verifies incoming data being received by s2Member\'s IPN processor. You can change <em>[proxy-gateway]</em> to whatever you like. The <em>[proxy-gateway]</em> value is required. It will be stored by s2Member as the Customer\'s Paid Subscr. Gateway. Your [proxy-gateway] value will also be reflected in s2Member\'s IPN log.</p>'."\n";
1110 echo '<input type="text" autocomplete="off" value="'.format_to_edit(home_url("/?s2member_paypal_notify=1&s2member_paypal_proxy=[proxy-gateway]&s2member_paypal_proxy_verification=".urlencode(c_ws_plugin__s2member_paypal_utilities::paypal_proxy_key_gen()), "https")).'" style="width:99%;" />'."\n";
1111 echo '<p><em>Any 3rd-party application that is sending IPN transactions to your s2Member installation must include the <code>custom</code> POST variable, and that variable must always start with your installation domain (i.e., custom=<code>'.esc_html($_SERVER["HTTP_HOST"]).'</code>). In addition, the <code>item_number</code> variable must always match a format that s2Member looks for. Generally speaking, the <code>item_number</code> should be <code>1, 2, 3, or 4</code>, indicating a specific s2Member Level #. However, s2Member also uses some advanced formats in this field. Just to be sure, we suggest creating a PayPal Button with the s2Member Button Generator, and then taking a look at the Full Button Code to see how s2Member expects <code>item_number</code> to be formatted. Other than the aforementioned exceptions, all other POST variables should follow PayPal standards. Please see: <a href="https://s2member.com/r/paypal/-ipn-pdt-vars" target="_blank" rel="external">PayPal\'s IPN/PDT reference guide</a> for full documentation.</em></p>'."\n";
1112 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_ipn_after_proxy", get_defined_vars());
1113 echo '</div>'."\n";
1114 echo '</div>'."\n";
1115
1116 echo '</div>'."\n";
1117
1118 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_after_paypal_ipn", get_defined_vars());
1119 }
1120
1121 if(apply_filters("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_display_paypal_pdt", TRUE, get_defined_vars()))
1122 {
1123 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_before_paypal_pdt", get_defined_vars());
1124
1125 echo '<div class="ws-menu-page-group" title="PayPal PDT/Auto-Return Integration">'."\n";
1126
1127 echo '<div class="ws-menu-page-section ws-plugin--s2member-paypal-pdt-section">'."\n";
1128 echo '<h3>PayPal PDT Identity Token (required, please enable)</h3>'."\n";
1129 echo '<p>Log into your PayPal account and navigate to this section:<br /><strong><a href="https://www.paypal.com/businessmanage/preferences/website" target="_blank" rel="external">Account Settings → Website Payments → Website Preferences</a></strong></p>'."\n";
1130 echo '<p>Turn the Auto-Return feature: <strong><code>On</code></strong></p>'."\n";
1131 echo '<p>You\'ll need your <a href="'.esc_attr(home_url("/?s2member_paypal_return=1&s2member_paypal_proxy=paypal&s2member_paypal_proxy_use=x-preview")).'" target="_blank" rel="external">Auto-Return URL</a>, which is:<br /><code>'.esc_html(home_url("/?s2member_paypal_return=1")).'</code></p>'."\n";
1132 echo '<p>You must also enable PDT (Payment Data Transfer): <strong><code>On</code></strong><br /><em>You\'ll be issued an Identity Token that you can enter below.</em></p>'."\n";
1133 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_pdt", get_defined_vars());
1134
1135 echo '<table class="form-table">'."\n";
1136 echo '<tbody>'."\n";
1137 echo '<tr>'."\n";
1138
1139 echo '<th>'."\n";
1140 echo '<label for="ws-plugin--s2member-paypal-identity-token">'."\n";
1141 echo 'PayPal PDT Identity Token:'."\n";
1142 echo '</label>'."\n";
1143 echo '</th>'."\n";
1144
1145 echo '</tr>'."\n";
1146 echo '<tr>'."\n";
1147
1148 echo '<td>'."\n";
1149 echo '<input type="password" autocomplete="off" name="ws_plugin__s2member_paypal_identity_token" id="ws-plugin--s2member-paypal-identity-token" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["paypal_identity_token"]).'" /><br />'."\n";
1150 echo 'Your PDT Identity Token will appear under <strong>Account Settings → Website Payments → Website Preferences</strong> in your PayPal account.'."\n";
1151 echo '</td>'."\n";
1152
1153 echo '</tr>'."\n";
1154 echo '</tbody>'."\n";
1155 echo '</table>'."\n";
1156
1157 echo '<div class="ws-menu-page-hr"></div>'."\n";
1158
1159 echo '<h3 style="margin:0;">More Information (<a href="#" onclick="jQuery(\'div#ws-plugin--s2member-paypal-pdt-details\').toggle(); return false;" class="ws-dotted-link">click here</a>)</h3>'."\n";
1160 echo '<div id="ws-plugin--s2member-paypal-pdt-details" style="margin-top:10px; display:none;">'."\n";
1161 echo '<p><em><strong>Quick Tip:</strong> In addition to the <a href="http://s2member.com/r/paypal-pdt-setup/" target="_blank" rel="external">default Auto-Return/PDT configuration inside your PayPal account</a>, the Auto-Return URL is also set on a per-transaction basis from within the special PayPal Button Code that s2Member provides you with. In other words, if you have multiple sites operating on one PayPal account, that\'s OK. s2Member sets the Auto-Return URL (dynamically) for each transaction. The result is that the Auto-Return URL configured from within your PayPal account becomes the default, which is then overwritten on a per-transaction basis by the s2Member software.</em></p>'."\n";
1162 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_pdt_after_quick_tip", get_defined_vars());
1163 echo '</div>'."\n";
1164
1165 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_paypal_pdt_after_more_info", get_defined_vars());
1166
1167 echo '</div>'."\n";
1168
1169 echo '</div>'."\n";
1170
1171 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_after_paypal_pdt", get_defined_vars());
1172 }
1173
1174 if(apply_filters("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_display_signup_confirmation_email", TRUE, get_defined_vars()))
1175 {
1176 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_before_signup_confirmation_email", get_defined_vars());
1177
1178 echo '<div class="ws-menu-page-group" title="Signup Confirmation Email (Standard)">'."\n";
1179
1180 echo '<div class="ws-menu-page-section ws-plugin--s2member-signup-confirmation-email-section">'."\n";
1181 echo '<h3>Signup Confirmation Email (required, but the default works fine)</h3>'."\n";
1182 echo '<p>This email is sent to new Customers after they return from a successful signup at PayPal. The <strong>primary</strong> purpose of this email is to provide the Customer with instructions, along with a link to register a Username for their Membership. You may also customize this further by providing details that are specifically geared to your site.</p>'."\n";
1183 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_signup_confirmation_email", get_defined_vars());
1184
1185 echo '<table class="form-table">'."\n";
1186 echo '<tbody>'."\n";
1187 echo '<tr>'."\n";
1188
1189 echo '<th>'."\n";
1190 echo '<label for="ws-plugin--s2member-signup-email-recipients">'."\n";
1191 echo 'Signup Confirmation Recipients:'."\n";
1192 echo '</label>'."\n";
1193 echo '</th>'."\n";
1194
1195 echo '</tr>'."\n";
1196 echo '<tr>'."\n";
1197
1198 echo '<td>'."\n";
1199 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_signup_email_recipients" id="ws-plugin--s2member-signup-email-recipients" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["signup_email_recipients"]).'" /><br />'."\n";
1200 echo 'This is a semicolon ( ; ) delimited list of Recipients. Here is an example:<br />'."\n";
1201 echo '<code>"%%full_name%%" &lt;%%payer_email%%&gt;; admin@example.com; "Webmaster" &lt;webmaster@example.com&gt;</code>'."\n";
1202 echo '</td>'."\n";
1203
1204 echo '</tr>'."\n";
1205 echo '<tr>'."\n";
1206
1207 echo '<th>'."\n";
1208 echo '<label for="ws-plugin--s2member-signup-email-subject">'."\n";
1209 echo 'Signup Confirmation Email Subject:'."\n";
1210 echo '</label>'."\n";
1211 echo '</th>'."\n";
1212
1213 echo '</tr>'."\n";
1214 echo '<tr>'."\n";
1215
1216 echo '<td>'."\n";
1217 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_signup_email_subject" id="ws-plugin--s2member-signup-email-subject" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["signup_email_subject"]).'" /><br />'."\n";
1218 echo 'Subject Line used in the email sent to a Customer after a successful signup has occurred through PayPal.'."\n";
1219 echo '</td>'."\n";
1220
1221 echo '</tr>'."\n";
1222 echo '<tr>'."\n";
1223
1224 echo '<th>'."\n";
1225 echo '<label for="ws-plugin--s2member-signup-email-message">'."\n";
1226 echo 'Signup Confirmation Email Message:'."\n";
1227 echo '</label>'."\n";
1228 echo '</th>'."\n";
1229
1230 echo '</tr>'."\n";
1231 echo '<tr>'."\n";
1232
1233 echo '<td>'."\n";
1234 //250615 Visual editor for HTML emails.
1235 if (empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['html_emails_enabled'])) {
1236 echo '<textarea name="ws_plugin__s2member_signup_email_message" id="ws-plugin--s2member-signup-email-message" rows="10">'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["signup_email_message"]).'</textarea><br />'."\n";
1237 } else {
1238 c_ws_plugin__s2member_utilities::editor('signup_email_message');
1239 }
1240
1241 echo 'Message Body used in the email sent to a Customer after a successful signup has occurred through PayPal.<br /><br />'."\n";
1242 echo '<strong>You can also use these special Replacement Codes if you need them:</strong>'."\n";
1243 echo '<ul class="ws-menu-page-li-margins">'."\n";
1244 echo '<li><code>%%registration_url%%</code> = The full URL (generated by s2Member) where the Customer can get registered.</li>'."\n";
1245 echo '<li><code>%%subscr_id%%</code> = The PayPal Subscription ID, which remains constant throughout any &amp; all future payments. [ <a href="#" onclick="alert(\'There is one exception. If you are selling Lifetime or Fixed-Term (non-recurring) access, using Buy Now functionality; the %%subscr_id%% is actually set to the Transaction ID for the purchase. PayPal does not provide a specific Subscription ID for Buy Now purchases. Since Lifetime &amp; Fixed-Term Subscriptions are NOT recurring (i.e., there is only ONE payment), using the Transaction ID as the Subscription ID is a graceful way to deal with this minor conflict.\'); return false;">?</a> ]</li>'."\n";
1246 echo '<li><code>%%currency%%</code> = Three-character currency code (uppercase); e.g., <code>USD</code></li>'."\n";
1247 echo '<li><code>%%currency_symbol%%</code> = Currency code symbol; e.g., <code>$</code></li>'."\n";
1248 echo '<li><code>%%initial%%</code> = The Initial Fee charged during signup. If you offered a 100% Free Trial, this will be <code>0</code>. [ <a href="#" onclick="alert(\'This will always represent the amount of money the Customer spent, whenever they initially signed up, no matter what. If a Customer signs up, under the terms of a 100% Free Trial Period, this will be 0.\'); return false;">?</a> ]</li>'."\n";
1249 echo '<li><code>%%regular%%</code> = The Regular Amount of the Subscription. If you offer something 100% free, this will be <code>0</code>. [ <a href="#" onclick="alert(\'This is how much the Subscription costs after an Initial Period expires. If you did NOT offer an Initial Period at a different price, %%initial%% and %%regular%% will be equal to the same thing.\'); return false;">?</a> ]</li>'."\n";
1250 echo '<li><code>%%recurring%%</code> = This is the amount that will be charged on a recurring basis, or <code>0</code> if non-recurring. [ <a href="#" onclick="alert(\'If Recurring Payments have not been required, this will be equal to 0. That being said, %%regular%% &amp; %%recurring%% are usually the same value. This variable can be used in two different ways. You can use it to determine what the Regular Recurring Rate is, or to determine whether the Subscription will recur or not. If it is going to recur, %%recurring%% will be > 0.\'); return false;">?</a> ]</li>'."\n";
1251 echo '<li><code>%%first_name%%</code> = The First Name of the Customer who purchased the Membership Subscription.</li>'."\n";
1252 echo '<li><code>%%last_name%%</code> = The Last Name of the Customer who purchased the Membership Subscription.</li>'."\n";
1253 echo '<li><code>%%full_name%%</code> = The Full Name (First &amp; Last) of the Customer who purchased the Membership Subscription.</li>'."\n";
1254 echo '<li><code>%%payer_email%%</code> = The Email Address of the Customer who purchased the Membership Subscription.</li>'."\n";
1255 echo '<li><code>%%user_ip%%</code> = The Customer\'s IP Address, detected during checkout via <code>$_SERVER["REMOTE_ADDR"]</code>.</li>'."\n";
1256 echo '<li><code>%%item_number%%</code> = The Item Number (colon separated <code><em>level:custom_capabilities:fixed term</em></code>) that the Subscription is for.</li>'."\n";
1257 echo '<li><code>%%item_name%%</code> = The Item Name (as provided by the <code>desc=""</code> attribute in your Shortcode, which briefly describes the Item Number).</li>'."\n";
1258 echo '<li><code>%%initial_term%%</code> = This is the term length of the Initial Period. This will be a numeric value, followed by a space, then a single letter. [ <a href="#" onclick="alert(\'Here are some examples:\\n\\n%%initial_term%% = 1 D (this means 1 Day)\\n%%initial_term%% = 1 W (this means 1 Week)\\n%%initial_term%% = 1 M (this means 1 Month)\\n%%initial_term%% = 1 Y (this means 1 Year)\\n\\nThe Initial Period never recurs, so this only lasts for the term length specified, then it is over.\'); return false;">?</a> ]</li>'."\n";
1259 echo '<li><code>%%initial_cycle%%</code> = This is the <code>%%initial_term%%</code> from above, converted to a cycle representation of: <code><em>X days/weeks/months/years</em></code>.</li>'."\n";
1260 echo '<li><code>%%regular_term%%</code> = This is the term length of the Regular Period. This will be a numeric value, followed by a space, then a single letter. [ <a href="#" onclick="alert(\'Here are some examples:\\n\\n%%regular_term%% = 1 D (this means 1 Day)\\n%%regular_term%% = 1 W (this means 1 Week)\\n%%regular_term%% = 1 M (this means 1 Month)\\n%%regular_term%% = 1 Y (this means 1 Year)\\n%%regular_term%% = 1 L (this means 1 Lifetime)\\n\\nThe Regular Term is usually recurring. So the Regular Term value represents the period (or duration) of each recurring period. If %%recurring%% = 0, then the Regular Term only applies once, because it is not recurring. So if it is not recurring, the value of %%regular_term%% simply represents how long their Membership privileges are going to last after the %%initial_term%% has expired, if there was an Initial Term. The value of this variable ( %%regular_term%% ) will never be empty, it will always be at least: 1 D, meaning 1 day. No exceptions.\'); return false;">?</a> ]</li>'."\n";
1261 echo '<li><code>%%regular_cycle%%</code> = This is the <code>%%regular_term%%</code> from above, converted to a cycle representation of: <code><em>[every] X days/weeks/months/years—OR daily, weekly, bi-weekly, monthly, bi-monthly, quarterly, yearly, or lifetime</em></code>. This is a very useful Replacment Code. Its value is dynamic; depending on term length, recurring status, and period/term lengths configured.</li>'."\n";
1262 echo '<li><code>%%recurring/regular_cycle%%</code> = Example (<code>14.95 / Monthly</code>), or ... (<code>0 / non-recurring</code>); depending on the value of <code>%%recurring%%</code>.</li>'."\n";
1263 echo '</ul>'."\n";
1264
1265 echo '<strong>Custom Replacement Codes can also be inserted using these instructions:</strong>'."\n";
1266 echo '<ul class="ws-menu-page-li-margins">'."\n";
1267 echo '<li><code>%%cv0%%</code> = The domain of your site, which is passed through the `custom` attribute in your Shortcode.</li>'."\n";
1268 echo '<li><code>%%cv1%%</code> = If you need to track additional custom variables, you can pipe delimit them into the `custom` attribute; inside your Shortcode, like this: <code>custom="'.esc_html($_SERVER["HTTP_HOST"]).'|cv1|cv2|cv3"</code>. You can have an unlimited number of custom variables. Obviously, this is for advanced webmasters; but the functionality has been made available for those who need it.</li>'."\n";
1269 echo '</ul>'."\n";
1270 echo '<strong>This example uses cv1 to record a special marketing campaign:</strong><br />'."\n";
1271 echo '<em>(The campaign (i.e., christmas-promo) could be referenced using <code>%%cv1%%</code>)</em><br />'."\n";
1272 echo '<code>custom="'.esc_html($_SERVER["HTTP_HOST"]).'|christmas-promo"</code>'."\n";
1273
1274 echo (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()) ?
1275 '<div class="ws-menu-page-hr"></div>'."\n".
1276 '<p style="margin:0;"><strong>Advanced Customization:</strong> Further customization is possible with the <code>ws_plugin__s2member_signup_email_sbj</code> and <code>ws_plugin__s2member_signup_email_msg</code> filters from a plugin or theme.</p>'."\n"
1277 : '';
1278 echo '</td>'."\n";
1279
1280 echo '</tr>'."\n";
1281 echo '</tbody>'."\n";
1282 echo '</table>'."\n";
1283 echo '</div>'."\n";
1284
1285 echo '</div>'."\n";
1286
1287 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_after_signup_confirmation_email", get_defined_vars());
1288 }
1289
1290 if(apply_filters("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_display_modification_confirmation_email", c_ws_plugin__s2member_utils_conds::pro_is_installed(), get_defined_vars()))
1291 {
1292 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_before_modification_confirmation_email", get_defined_vars());
1293
1294 echo '<div class="ws-menu-page-group" title="Modification Confirmation Email '.((c_ws_plugin__s2member_utils_conds::pro_is_installed()) ? '(Standard/Pro-Form)' : '(Standard)').'">'."\n";
1295
1296 echo '<div class="ws-menu-page-section ws-plugin--s2member-modification-confirmation-email-section">'."\n";
1297 echo '<h3>Modification Confirmation Email (required, but the default works fine)</h3>'."\n";
1298 echo '<p>This email is sent to existing Users after they complete an upgrade/downgrade (if and when you make this possible). For instance, if a Free Subscriber upgrades to a paid Membership Level, s2Member considers this a Modification (NOT a Signup; a Signup is associated only with someone completely new). The <strong>primary</strong> purpose of this email is to provide the Customer with a confirmation that their account was updated. You may also customize this further by providing details that are specifically geared to your site.</p>'."\n";
1299 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_modification_confirmation_email", get_defined_vars());
1300
1301 echo '<table class="form-table">'."\n";
1302 echo '<tbody>'."\n";
1303 echo '<tr>'."\n";
1304
1305 echo '<th>'."\n";
1306 echo '<label for="ws-plugin--s2member-modification-email-recipients">'."\n";
1307 echo 'Modification Confirmation Recipients:'."\n";
1308 echo '</label>'."\n";
1309 echo '</th>'."\n";
1310
1311 echo '</tr>'."\n";
1312 echo '<tr>'."\n";
1313
1314 echo '<td>'."\n";
1315 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_modification_email_recipients" id="ws-plugin--s2member-modification-email-recipients" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["modification_email_recipients"]).'" /><br />'."\n";
1316 echo 'This is a semicolon ( ; ) delimited list of Recipients. Here is an example:<br />'."\n";
1317 echo '<code>"%%full_name%%" &lt;%%payer_email%%&gt;; admin@example.com; "Webmaster" &lt;webmaster@example.com&gt;</code>'."\n";
1318 echo '</td>'."\n";
1319
1320 echo '</tr>'."\n";
1321 echo '<tr>'."\n";
1322
1323 echo '<th>'."\n";
1324 echo '<label for="ws-plugin--s2member-modification-email-subject">'."\n";
1325 echo 'Modification Confirmation Email Subject:'."\n";
1326 echo '</label>'."\n";
1327 echo '</th>'."\n";
1328
1329 echo '</tr>'."\n";
1330 echo '<tr>'."\n";
1331
1332 echo '<td>'."\n";
1333 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_modification_email_subject" id="ws-plugin--s2member-modification-email-subject" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["modification_email_subject"]).'" /><br />'."\n";
1334 echo 'Subject Line used in the email sent to a Customer after a successful modification has occurred through PayPal.'."\n";
1335 echo '</td>'."\n";
1336
1337 echo '</tr>'."\n";
1338 echo '<tr>'."\n";
1339
1340 echo '<th>'."\n";
1341 echo '<label for="ws-plugin--s2member-modification-email-message">'."\n";
1342 echo 'Modification Confirmation Email Message:'."\n";
1343 echo '</label>'."\n";
1344 echo '</th>'."\n";
1345
1346 echo '</tr>'."\n";
1347 echo '<tr>'."\n";
1348
1349 echo '<td>'."\n";
1350 //250615 Visual editor for HTML emails.
1351 if (empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['html_emails_enabled'])) {
1352 echo '<textarea name="ws_plugin__s2member_modification_email_message" id="ws-plugin--s2member-modification-email-message" rows="10">'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["modification_email_message"]).'</textarea><br />'."\n";
1353 } else {
1354 c_ws_plugin__s2member_utilities::editor('modification_email_message');
1355 }
1356
1357 echo 'Message Body used in the email sent to a Customer after a successful modification has occurred through PayPal.<br /><br />'."\n";
1358 echo '<strong>You can also use these special Replacement Codes if you need them:</strong>'."\n";
1359 echo '<ul class="ws-menu-page-li-margins">'."\n";
1360 echo '<li><code>%%subscr_id%%</code> = The PayPal Subscription ID, which remains constant throughout any &amp; all future payments. [ <a href="#" onclick="alert(\'There is one exception. If you are selling Lifetime or Fixed-Term (non-recurring) access, using Buy Now functionality; the %%subscr_id%% is actually set to the Transaction ID for the purchase. PayPal does not provide a specific Subscription ID for Buy Now purchases. Since Lifetime &amp; Fixed-Term Subscriptions are NOT recurring (i.e., there is only ONE payment), using the Transaction ID as the Subscription ID is a graceful way to deal with this minor conflict.\'); return false;">?</a> ]</li>'."\n";
1361 echo '<li><code>%%subscr_baid%%</code> = Applicable only with PayPal Pro (Payflow Edition); and only for Express Checkout transactions that require a Billing Agreement. This is the Subscription\'s Billing Agreement ID, which remains constant throughout any &amp; all future payments. [ <a href="#" onclick="alert(\'Applicable only with PayPal Pro (Payflow Edition); and only for Express Checkout transactions that require a Billing Agreement. In all other cases, the %%subscr_baid%% is simply set to the %%subscr_id%% value; i.e., it is a duplicate of %%subscr_id%% in most cases.\'); return false;">?</a> ]</li>'."\n";
1362 echo '<li><code>%%currency%%</code> = Three-character currency code (uppercase); e.g., <code>USD</code></li>'."\n";
1363 echo '<li><code>%%currency_symbol%%</code> = Currency code symbol; e.g., <code>$</code></li>'."\n";
1364 echo '<li><code>%%initial%%</code> = The Initial Fee. If you offered a 100% Free Trial, this will be <code>0</code>. [ <a href="#" onclick="alert(\'This will always represent the amount of money the Customer spent when they completed checkout, no matter what. Even if that amount is 0. If a Customer upgrades/downgrades under the terms of a 100% Free Trial Period, this will be 0.\'); return false;">?</a> ]</li>'."\n";
1365 echo '<li><code>%%regular%%</code> = The Regular Amount of the Subscription. If you offer something 100% free, this will be <code>0</code>. [ <a href="#" onclick="alert(\'This is how much the Subscription costs after an Initial Period expires. If you did NOT offer an Initial Period at a different price, %%initial%% and %%regular%% will be equal to the same thing.\'); return false;">?</a> ]</li>'."\n";
1366 echo '<li><code>%%recurring%%</code> = This is the amount that will be charged on a recurring basis, or <code>0</code> if non-recurring. [ <a href="#" onclick="alert(\'If Recurring Payments have not been required, this will be equal to 0. That being said, %%regular%% &amp; %%recurring%% are usually the same value. This variable can be used in two different ways. You can use it to determine what the Regular Recurring Rate is, or to determine whether the Subscription will recur or not. If it is going to recur, %%recurring%% will be > 0.\'); return false;">?</a> ]</li>'."\n";
1367 echo '<li><code>%%first_name%%</code> = The First Name of the Customer who purchased the Membership Subscription.</li>'."\n";
1368 echo '<li><code>%%last_name%%</code> = The Last Name of the Customer who purchased the Membership Subscription.</li>'."\n";
1369 echo '<li><code>%%full_name%%</code> = The Full Name (First &amp; Last) of the Customer who purchased the Membership Subscription.</li>'."\n";
1370 echo '<li><code>%%payer_email%%</code> = The Email Address of the Customer who purchased the Membership Subscription.</li>'."\n";
1371 echo '<li><code>%%item_number%%</code> = The Item Number (colon separated <code><em>level:custom_capabilities:fixed term</em></code>) that the Subscription is for.</li>'."\n";
1372 echo '<li><code>%%item_name%%</code> = The Item Name (as provided by the <code>desc=""</code> attribute in your Shortcode, which briefly describes the Item Number).</li>'."\n";
1373 echo '<li><code>%%initial_term%%</code> = This is the term length of the Initial Period. This will be a numeric value, followed by a space, then a single letter. [ <a href="#" onclick="alert(\'Here are some examples:\\n\\n%%initial_term%% = 1 D (this means 1 Day)\\n%%initial_term%% = 1 W (this means 1 Week)\\n%%initial_term%% = 1 M (this means 1 Month)\\n%%initial_term%% = 1 Y (this means 1 Year)\\n\\nThe Initial Period never recurs, so this only lasts for the term length specified, then it is over.\'); return false;">?</a> ]</li>'."\n";
1374 echo '<li><code>%%initial_cycle%%</code> = This is the <code>%%initial_term%%</code> from above, converted to a cycle representation of: <code><em>X days/weeks/months/years</em></code>.</li>'."\n";
1375 echo '<li><code>%%regular_term%%</code> = This is the term length of the Regular Period. This will be a numeric value, followed by a space, then a single letter. [ <a href="#" onclick="alert(\'Here are some examples:\\n\\n%%regular_term%% = 1 D (this means 1 Day)\\n%%regular_term%% = 1 W (this means 1 Week)\\n%%regular_term%% = 1 M (this means 1 Month)\\n%%regular_term%% = 1 Y (this means 1 Year)\\n%%regular_term%% = 1 L (this means 1 Lifetime)\\n\\nThe Regular Term is usually recurring. So the Regular Term value represents the period (or duration) of each recurring period. If %%recurring%% = 0, then the Regular Term only applies once, because it is not recurring. So if it is not recurring, the value of %%regular_term%% simply represents how long their Membership privileges are going to last after the %%initial_term%% has expired, if there was an Initial Term. The value of this variable ( %%regular_term%% ) will never be empty, it will always be at least: 1 D, meaning 1 day. No exceptions.\'); return false;">?</a> ]</li>'."\n";
1376 echo '<li><code>%%regular_cycle%%</code> = This is the <code>%%regular_term%%</code> from above, converted to a cycle representation of: <code><em>[every] X days/weeks/months/years—OR daily, weekly, bi-weekly, monthly, bi-monthly, quarterly, yearly, or lifetime</em></code>. This is a very useful Replacment Code. Its value is dynamic; depending on term length, recurring status, and period/term lengths configured.</li>'."\n";
1377 echo '<li><code>%%recurring/regular_cycle%%</code> = Example (<code>14.95 / Monthly</code>), or ... (<code>0 / non-recurring</code>); depending on the value of <code>%%recurring%%</code>.</li>'."\n";
1378 echo '<li><code>%%user_first_name%%</code> = The First Name listed on their User account. This might be different than what is on file with PayPal.</li>'."\n";
1379 echo '<li><code>%%user_last_name%%</code> = The Last Name listed on their User account. This might be different than what is on file with PayPal.</li>'."\n";
1380 echo '<li><code>%%user_full_name%%</code> = The Full Name listed on their User account. This might be different than what is on file with PayPal.</li>'."\n";
1381 echo '<li><code>%%user_email%%</code> = The Email Address associated with their User account. This might be different than what is on file with PayPal.</li>'."\n";
1382 echo '<li><code>%%user_login%%</code> = The Username associated with their account. The Customer created this during registration.</li>'."\n";
1383 echo '<li><code>%%user_ip%%</code> = The Customer\'s original IP Address, during checkout/registration via <code>$_SERVER["REMOTE_ADDR"]</code>.</li>'."\n";
1384 echo '<li><code>%%user_id%%</code> = A unique WordPress User ID that references this account in the WordPress database.</li>'."\n";
1385 echo '</ul>'."\n";
1386
1387 if(c_ws_plugin__s2member_utils_conds::pro_is_installed())
1388 {
1389 echo '<strong>Coupon Replacement Codes (applicable only w/ s2Member Pro-Forms):</strong>'."\n";
1390 echo '<ul class="ws-menu-page-li-margins">'."\n";
1391 echo '<li><code>%%full_coupon_code%%</code> = A full Coupon Code—if one is accepted by your configuration of s2Member. This may indicate an Affiliate Coupon Code, which will include your Affiliate Suffix Chars too (i.e., the full Coupon Code).</li>'."\n";
1392 echo '<li><code>%%coupon_code%%</code> = A Coupon Code—if one is accepted by your configuration of s2Member. This will NOT include any Affiliate Suffix Chars. This indicates the actual Coupon Code accepted by your configuration of s2Member (excluding any Affiliate ID).</li>'."\n";
1393 echo '<li><code>%%coupon_affiliate_id%%</code> = This is the end of an Affiliate Coupon Code <em>(i.e., the referring affiliate\'s ID)</em>. This is only applicable if an Affiliate Coupon Code is accepted by your configuration of s2Member.</li>'."\n";
1394 echo '</ul>'."\n";
1395 }
1396 echo '<strong>Custom Registration/Profile Fields are also supported in this email:</strong>'."\n";
1397 echo '<ul class="ws-menu-page-li-margins">'."\n";
1398 echo '<li><code>%%date_of_birth%%</code> would be valid; if you have a Custom Registration/Profile Field with the ID <code>date_of_birth</code>.</li>'."\n";
1399 echo '<li><code>%%street_address%%</code> would be valid; if you have a Custom Registration/Profile Field with the ID <code>street_address</code>.</li>'."\n";
1400 echo '<li><code>%%country%%</code> would be valid; if you have a Custom Registration/Profile Field with the ID <code>country</code>.</li>'."\n";
1401 echo '<li><em><code>%%etc, etc...%%</code> <strong>see:</strong> s2Member → General Options → Registration/Profile Fields</em>.</li>'."\n";
1402 echo '</ul>'."\n";
1403
1404 echo '<strong>Custom Replacement Codes can also be inserted using these instructions:</strong>'."\n";
1405 echo '<ul class="ws-menu-page-li-margins">'."\n";
1406 echo '<li><code>%%cv0%%</code> = The domain of your site, which is passed through the `custom` attribute in your Shortcode.</li>'."\n";
1407 echo '<li><code>%%cv1%%</code> = If you need to track additional custom variables, you can pipe delimit them into the `custom` attribute; inside your Shortcode, like this: <code>custom="'.esc_html($_SERVER["HTTP_HOST"]).'|cv1|cv2|cv3"</code>. You can have an unlimited number of custom variables. Obviously, this is for advanced webmasters; but the functionality has been made available for those who need it.</li>'."\n";
1408 echo '</ul>'."\n";
1409 echo '<strong>This example uses cv1 to record a special marketing campaign:</strong><br />'."\n";
1410 echo '<em>(The campaign (i.e., christmas-promo) could be referenced using <code>%%cv1%%</code>)</em><br />'."\n";
1411 echo '<code>custom="'.esc_html($_SERVER["HTTP_HOST"]).'|christmas-promo"</code>'."\n";
1412
1413 echo (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()) ?
1414 '<div class="ws-menu-page-hr"></div>'."\n".
1415 '<p style="margin:0;"><strong>Advanced Customization:</strong> Further customization is possible with the <code>ws_plugin__s2member_modification_email_sbj</code> and <code>ws_plugin__s2member_modification_email_msg</code> filters from a plugin or theme.</p>'."\n"
1416 : '';
1417 echo '</td>'."\n";
1418
1419 echo '</tr>'."\n";
1420 echo '</tbody>'."\n";
1421 echo '</table>'."\n";
1422 echo '</div>'."\n";
1423
1424 echo '</div>'."\n";
1425
1426 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_after_modification_confirmation_email", get_defined_vars());
1427 }
1428
1429 if(apply_filters("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_display_ccap_confirmation_email", c_ws_plugin__s2member_utils_conds::pro_is_installed(), get_defined_vars()))
1430 {
1431 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_before_ccap_confirmation_email", get_defined_vars());
1432
1433 echo '<div class="ws-menu-page-group" title="Capability Confirmation Email '.((c_ws_plugin__s2member_utils_conds::pro_is_installed()) ? '(Standard/Pro-Form)' : '(Standard)').'">'."\n";
1434
1435 echo '<div class="ws-menu-page-section ws-plugin--s2member-ccap-confirmation-email-section">'."\n";
1436 echo '<h3>Capability Confirmation Email (required, but the default works fine)</h3>'."\n";
1437 echo '<p>This email is sent to existing Users after they complete a Buy Now purchase for one or more Custom Capabilities (if and when you make this possible); see: <strong>Dashboard → s2Member → PayPal Buttons/Forms → Capability (Buy Now)</strong>. The <strong>primary</strong> purpose of this email is to provide the Customer with a confirmation that their account was updated. You may also customize this further by providing details that are specifically geared to your site.</p>'."\n";
1438 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_ccap_confirmation_email", get_defined_vars());
1439
1440 echo '<table class="form-table">'."\n";
1441 echo '<tbody>'."\n";
1442 echo '<tr>'."\n";
1443
1444 echo '<th>'."\n";
1445 echo '<label for="ws-plugin--s2member-ccap-email-recipients">'."\n";
1446 echo 'Capability Confirmation Recipients:'."\n";
1447 echo '</label>'."\n";
1448 echo '</th>'."\n";
1449
1450 echo '</tr>'."\n";
1451 echo '<tr>'."\n";
1452
1453 echo '<td>'."\n";
1454 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_ccap_email_recipients" id="ws-plugin--s2member-ccap-email-recipients" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["ccap_email_recipients"]).'" /><br />'."\n";
1455 echo 'This is a semicolon ( ; ) delimited list of Recipients. Here is an example:<br />'."\n";
1456 echo '<code>"%%full_name%%" &lt;%%payer_email%%&gt;; admin@example.com; "Webmaster" &lt;webmaster@example.com&gt;</code>'."\n";
1457 echo '</td>'."\n";
1458
1459 echo '</tr>'."\n";
1460 echo '<tr>'."\n";
1461
1462 echo '<th>'."\n";
1463 echo '<label for="ws-plugin--s2member-ccap-email-subject">'."\n";
1464 echo 'Capability Confirmation Email Subject:'."\n";
1465 echo '</label>'."\n";
1466 echo '</th>'."\n";
1467
1468 echo '</tr>'."\n";
1469 echo '<tr>'."\n";
1470
1471 echo '<td>'."\n";
1472 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_ccap_email_subject" id="ws-plugin--s2member-ccap-email-subject" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["ccap_email_subject"]).'" /><br />'."\n";
1473 echo 'Subject Line used in the email sent to a Customer after a purchase is completed through PayPal.'."\n";
1474 echo '</td>'."\n";
1475
1476 echo '</tr>'."\n";
1477 echo '<tr>'."\n";
1478
1479 echo '<th>'."\n";
1480 echo '<label for="ws-plugin--s2member-ccap-email-message">'."\n";
1481 echo 'Capability Confirmation Email Message:'."\n";
1482 echo '</label>'."\n";
1483 echo '</th>'."\n";
1484
1485 echo '</tr>'."\n";
1486 echo '<tr>'."\n";
1487
1488 echo '<td>'."\n";
1489 //250615 Visual editor for HTML emails.
1490 if (empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['html_emails_enabled'])) {
1491 echo '<textarea name="ws_plugin__s2member_ccap_email_message" id="ws-plugin--s2member-ccap-email-message" rows="10">'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["ccap_email_message"]).'</textarea><br />'."\n";
1492 } else {
1493 c_ws_plugin__s2member_utilities::editor('ccap_email_message');
1494 }
1495
1496 echo 'Message Body used in the email sent to a Customer after a purchase is completed through PayPal.<br /><br />'."\n";
1497 echo '<strong>You can also use these special Replacement Codes if you need them:</strong>'."\n";
1498 echo '<ul class="ws-menu-page-li-margins">'."\n";
1499 echo '<li><code>%%txn_id%%</code> = The PayPal Transaction ID. PayPal assigns a unique identifier for every purchase.</li>'."\n";
1500 echo '<li><code>%%currency%%</code> = Three-character currency code (uppercase); e.g., <code>USD</code></li>'."\n";
1501 echo '<li><code>%%currency_symbol%%</code> = Currency code symbol; e.g., <code>$</code></li>'."\n";
1502 echo '<li><code>%%amount%%</code> = The full Amount that you charged for Custom Capability access.</li>'."\n";
1503 echo '<li><code>%%first_name%%</code> = The First Name of the Customer who completed the purchase.</li>'."\n";
1504 echo '<li><code>%%last_name%%</code> = The Last Name of the Customer who completed the purchase.</li>'."\n";
1505 echo '<li><code>%%full_name%%</code> = The Full Name (First &amp; Last) of the Customer who completed the purchase.</li>'."\n";
1506 echo '<li><code>%%payer_email%%</code> = The Email Address of the Customer who completed the purchase.</li>'."\n";
1507 echo '<li><code>%%item_number%%</code> = The Item Number (colon separated <code><em>*:custom_capabilities</em></code>); where <code>custom_capabilities</code> is a comma-delimited list of the Custom Capabilities they purchased.</li>'."\n";
1508 echo '<li><code>%%item_name%%</code> = The Item Name (as provided by the <code>desc=""</code> attribute in your Shortcode, which briefly describes the Item Number).</li>'."\n";
1509 echo '<li><code>%%user_first_name%%</code> = The First Name listed on their User account. This might be different than what is on file with PayPal.</li>'."\n";
1510 echo '<li><code>%%user_last_name%%</code> = The Last Name listed on their User account. This might be different than what is on file with PayPal.</li>'."\n";
1511 echo '<li><code>%%user_full_name%%</code> = The Full Name listed on their User account. This might be different than what is on file with PayPal.</li>'."\n";
1512 echo '<li><code>%%user_email%%</code> = The Email Address associated with their User account. This might be different than what is on file with PayPal.</li>'."\n";
1513 echo '<li><code>%%user_login%%</code> = The Username associated with their account. The Customer created this during registration.</li>'."\n";
1514 echo '<li><code>%%user_ip%%</code> = The Customer\'s original IP Address, during checkout/registration via <code>$_SERVER["REMOTE_ADDR"]</code>.</li>'."\n";
1515 echo '<li><code>%%user_id%%</code> = A unique WordPress User ID that references this account in the WordPress database.</li>'."\n";
1516 echo '</ul>'."\n";
1517
1518 if(c_ws_plugin__s2member_utils_conds::pro_is_installed())
1519 {
1520 echo '<strong>Coupon Replacement Codes (applicable only w/ s2Member Pro-Forms):</strong>'."\n";
1521 echo '<ul class="ws-menu-page-li-margins">'."\n";
1522 echo '<li><code>%%full_coupon_code%%</code> = A full Coupon Code—if one is accepted by your configuration of s2Member. This may indicate an Affiliate Coupon Code, which will include your Affiliate Suffix Chars too (i.e., the full Coupon Code).</li>'."\n";
1523 echo '<li><code>%%coupon_code%%</code> = A Coupon Code—if one is accepted by your configuration of s2Member. This will NOT include any Affiliate Suffix Chars. This indicates the actual Coupon Code accepted by your configuration of s2Member (excluding any Affiliate ID).</li>'."\n";
1524 echo '<li><code>%%coupon_affiliate_id%%</code> = This is the end of an Affiliate Coupon Code <em>(i.e., the referring affiliate\'s ID)</em>. This is only applicable if an Affiliate Coupon Code is accepted by your configuration of s2Member.</li>'."\n";
1525 echo '</ul>'."\n";
1526 }
1527 echo '<strong>Custom Registration/Profile Fields are also supported in this email:</strong>'."\n";
1528 echo '<ul class="ws-menu-page-li-margins">'."\n";
1529 echo '<li><code>%%date_of_birth%%</code> would be valid; if you have a Custom Registration/Profile Field with the ID <code>date_of_birth</code>.</li>'."\n";
1530 echo '<li><code>%%street_address%%</code> would be valid; if you have a Custom Registration/Profile Field with the ID <code>street_address</code>.</li>'."\n";
1531 echo '<li><code>%%country%%</code> would be valid; if you have a Custom Registration/Profile Field with the ID <code>country</code>.</li>'."\n";
1532 echo '<li><em><code>%%etc, etc...%%</code> <strong>see:</strong> s2Member → General Options → Registration/Profile Fields</em>.</li>'."\n";
1533 echo '</ul>'."\n";
1534
1535 echo '<strong>Custom Replacement Codes can also be inserted using these instructions:</strong>'."\n";
1536 echo '<ul class="ws-menu-page-li-margins">'."\n";
1537 echo '<li><code>%%cv0%%</code> = The domain of your site, which is passed through the `custom` attribute in your Shortcode.</li>'."\n";
1538 echo '<li><code>%%cv1%%</code> = If you need to track additional custom variables, you can pipe delimit them into the `custom` attribute; inside your Shortcode, like this: <code>custom="'.esc_html($_SERVER["HTTP_HOST"]).'|cv1|cv2|cv3"</code>. You can have an unlimited number of custom variables. Obviously, this is for advanced webmasters; but the functionality has been made available for those who need it.</li>'."\n";
1539 echo '</ul>'."\n";
1540 echo '<strong>This example uses cv1 to record a special marketing campaign:</strong><br />'."\n";
1541 echo '<em>(The campaign (i.e., christmas-promo) could be referenced using <code>%%cv1%%</code>)</em><br />'."\n";
1542 echo '<code>custom="'.esc_html($_SERVER["HTTP_HOST"]).'|christmas-promo"</code>'."\n";
1543
1544 echo (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()) ?
1545 '<div class="ws-menu-page-hr"></div>'."\n".
1546 '<p style="margin:0;"><strong>Advanced Customization:</strong> Further customization is possible with the <code>ws_plugin__s2member_capabilities_email_sbj</code> and <code>ws_plugin__s2member_capabilities_email_msg</code> filters from a plugin or theme.</p>'."\n"
1547 : '';
1548 echo '</td>'."\n";
1549
1550 echo '</tr>'."\n";
1551 echo '</tbody>'."\n";
1552 echo '</table>'."\n";
1553 echo '</div>'."\n";
1554
1555 echo '</div>'."\n";
1556
1557 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_after_ccap_confirmation_email", get_defined_vars());
1558 }
1559
1560 if(apply_filters("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_display_sp_confirmation_email", TRUE, get_defined_vars()))
1561 {
1562 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_before_sp_confirmation_email", get_defined_vars());
1563
1564 echo '<div class="ws-menu-page-group" title="Specific Post/Page Confirmation Email (Standard)">'."\n";
1565
1566 echo '<div class="ws-menu-page-section ws-plugin--s2member-sp-confirmation-email-section">'."\n";
1567 echo '<h3>Specific Post/Page Confirmation Email (required, but the default works fine)</h3>'."\n";
1568 echo '<p>This email is sent to new Customers after they return from a successful purchase at PayPal, for Specific Post/Page Access. (see: <strong>s2Member → Restriction Options → Specific Post/Page Access</strong>). This is NOT used for Membership sales, only for Specific Post/Page Access. The <strong>primary</strong> purpose of this email is to provide the Customer with instructions, along with a link to access the Specific Post/Page they\'ve purchased access to. If you\'ve created a Specific Post/Page Package (with multiple Posts/Pages bundled together into one transaction), this ONE link (<code>%%sp_access_url%%</code>) will automatically authenticate them for access to ALL of the Posts/Pages included in their transaction. You may customize this email further by providing details that are specifically geared to your site.</p>'."\n";
1569 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_sp_confirmation_email", get_defined_vars());
1570
1571 echo '<table class="form-table">'."\n";
1572 echo '<tbody>'."\n";
1573 echo '<tr>'."\n";
1574
1575 echo '<th>'."\n";
1576 echo '<label for="ws-plugin--s2member-sp-email-recipients">'."\n";
1577 echo 'Specific Post/Page Confirmation Recipients:'."\n";
1578 echo '</label>'."\n";
1579 echo '</th>'."\n";
1580
1581 echo '</tr>'."\n";
1582 echo '<tr>'."\n";
1583
1584 echo '<td>'."\n";
1585 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_sp_email_recipients" id="ws-plugin--s2member-sp-email-recipients" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["sp_email_recipients"]).'" /><br />'."\n";
1586 echo 'This is a semicolon ( ; ) delimited list of Recipients. Here is an example:<br />'."\n";
1587 echo '<code>"%%full_name%%" &lt;%%payer_email%%&gt;; admin@example.com; "Webmaster" &lt;webmaster@example.com&gt;</code>'."\n";
1588 echo '</td>'."\n";
1589
1590 echo '</tr>'."\n";
1591 echo '<tr>'."\n";
1592
1593 echo '<th>'."\n";
1594 echo '<label for="ws-plugin--s2member-sp-email-subject">'."\n";
1595 echo 'Specific Post/Page Confirmation Email Subject:'."\n";
1596 echo '</label>'."\n";
1597 echo '</th>'."\n";
1598
1599 echo '</tr>'."\n";
1600 echo '<tr>'."\n";
1601
1602 echo '<td>'."\n";
1603 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_sp_email_subject" id="ws-plugin--s2member-sp-email-subject" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["sp_email_subject"]).'" /><br />'."\n";
1604 echo 'Subject Line used in the email sent to a Customer after a successful purchase has occurred through PayPal, for Specific Post/Page Access.'."\n";
1605 echo '</td>'."\n";
1606
1607 echo '</tr>'."\n";
1608 echo '<tr>'."\n";
1609
1610 echo '<th>'."\n";
1611 echo '<label for="ws-plugin--s2member-sp-email-message">'."\n";
1612 echo 'Specific Post/Page Confirmation Email Message:'."\n";
1613 echo '</label>'."\n";
1614 echo '</th>'."\n";
1615
1616 echo '</tr>'."\n";
1617 echo '<tr>'."\n";
1618
1619 echo '<td>'."\n";
1620 //250615 Visual editor for HTML emails.
1621 if (empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['html_emails_enabled'])) {
1622 echo '<textarea name="ws_plugin__s2member_sp_email_message" id="ws-plugin--s2member-sp-email-message" rows="10">'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["sp_email_message"]).'</textarea><br />'."\n";
1623 } else {
1624 c_ws_plugin__s2member_utilities::editor('sp_email_message');
1625 }
1626
1627 echo 'Message Body used in the email sent to a Customer after a successful purchase has occurred through PayPal, for Specific Post/Page Access.<br /><br />'."\n";
1628 echo '<strong>You can also use these special Replacement Codes if you need them:</strong>'."\n";
1629 echo '<ul class="ws-menu-page-li-margins">'."\n";
1630 echo '<li><code>%%sp_access_url%%</code> = The full URL (generated by s2Member) where the Customer can gain access.</li>'."\n";
1631 echo '<li><code>%%sp_access_exp%%</code> = Human readable expiration for <code>%%sp_access_url%%</code>. Ex: <em>(link expires in <code>%%sp_access_exp%%</code>)</em>.</li>'."\n";
1632 echo '<li><code>%%txn_id%%</code> = The PayPal Transaction ID. PayPal assigns a unique identifier for every purchase.</li>'."\n";
1633 echo '<li><code>%%currency%%</code> = Three-character currency code (uppercase); e.g., <code>USD</code></li>'."\n";
1634 echo '<li><code>%%currency_symbol%%</code> = Currency code symbol; e.g., <code>$</code></li>'."\n";
1635 echo '<li><code>%%amount%%</code> = The full Amount that you charged for Specific Post/Page Access.</li>'."\n";
1636 echo '<li><code>%%first_name%%</code> = The First Name of the Customer who purchased Specific Post/Page Access.</li>'."\n";
1637 echo '<li><code>%%last_name%%</code> = The Last Name of the Customer who purchased Specific Post/Page Access.</li>'."\n";
1638 echo '<li><code>%%full_name%%</code> = The Full Name (First &amp; Last) of the Customer who purchased Specific Post/Page Access.</li>'."\n";
1639 echo '<li><code>%%payer_email%%</code> = The Email Address of the Customer who purchased Specific Post/Page Access.</li>'."\n";
1640 echo '<li><code>%%user_ip%%</code> = The Customer\'s IP Address, detected during checkout via <code>$_SERVER["REMOTE_ADDR"]</code>.</li>'."\n";
1641 echo '<li><code>%%item_number%%</code> = The Item Number. Ex: <code><em>sp:13,24,36:72</em></code> (translates to: <code><em>sp:comma-delimited IDs:expiration hours</em></code>).</li>'."\n";
1642 echo '<li><code>%%item_name%%</code> = The Item Name (as provided by the <code>desc=""</code> attribute in your Shortcode, which briefly describes the Item Number).</li>'."\n";
1643 echo '</ul>'."\n";
1644
1645 echo '<strong>Custom Replacement Codes can also be inserted using these instructions:</strong>'."\n";
1646 echo '<ul class="ws-menu-page-li-margins">'."\n";
1647 echo '<li><code>%%cv0%%</code> = The domain of your site, which is passed through the `custom` attribute in your Shortcode.</li>'."\n";
1648 echo '<li><code>%%cv1%%</code> = If you need to track additional custom variables, you can pipe delimit them into the `custom` attribute; inside your Shortcode, like this: <code>custom="'.esc_html($_SERVER["HTTP_HOST"]).'|cv1|cv2|cv3"</code>. You can have an unlimited number of custom variables. Obviously, this is for advanced webmasters; but the functionality has been made available for those who need it.</li>'."\n";
1649 echo '</ul>'."\n";
1650 echo '<strong>This example uses cv1 to record a special marketing campaign:</strong><br />'."\n";
1651 echo '<em>(The campaign (i.e., christmas-promo) could be referenced using <code>%%cv1%%</code>)</em><br />'."\n";
1652 echo '<code>custom="'.esc_html($_SERVER["HTTP_HOST"]).'|christmas-promo"</code>'."\n";
1653
1654 echo (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()) ?
1655 '<div class="ws-menu-page-hr"></div>'."\n".
1656 '<p style="margin:0;"><strong>Advanced Customization:</strong> Further customization is possible with the <code>ws_plugin__s2member_sp_email_sbj</code> and <code>ws_plugin__s2member_sp_email_msg</code> filters from a plugin or theme.</p>'."\n"
1657 : '';
1658 echo '</td>'."\n";
1659
1660 echo '</tr>'."\n";
1661 echo '</tbody>'."\n";
1662 echo '</table>'."\n";
1663 echo '</div>'."\n";
1664
1665 echo '</div>'."\n";
1666
1667 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_after_sp_confirmation_email", get_defined_vars());
1668 }
1669
1670 if(apply_filters("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_display_eot_behavior", TRUE, get_defined_vars()))
1671 {
1672 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_before_eot_behavior", get_defined_vars());
1673
1674 //260908.2031 Allow diagnostic/admin-notice links to open this collapsed panel before the shared menu-page JavaScript scrolls to a setting inside it.
1675 echo '<div class="ws-menu-page-group" title="Automatic End-of-Term Behavior"'.((!empty($_GET['s2member-open-panel']) && $_GET['s2member-open-panel'] === 'auto-eot') ? ' default-state="open"' : '').'>'."\n";
1676
1677 echo '<div class="ws-menu-page-section ws-plugin--s2member-eot-behavior-section">'."\n";
1678 echo '<h3>PayPal End-of-Term Behavior (required, please choose)</h3>'."\n";
1679 echo '<p>EOT = End Of Term. By default, s2Member will demote a paid Member to the role selected under Demote To Role whenever their Subscription term has ended (i.e., expired), been cancelled, refunded, charged back to you, etc. s2Member assigns the role selected under Demote To Role; resulting access depends on that role and on any unrelated WordPress roles you choose to preserve. However, in some cases, you may prefer to have Customer accounts moved to Pending Deletion for administrator review, instead of just being demoted. This is where you choose which method works best for your site. If you don\'t want s2Member to take ANY action at all, you can disable s2Member\'s EOT System temporarily, or even completely. There are also a few other configurable options here, so please read carefully. These options are all very important.</p>'."\n";
1680 echo '<p><strong>PayPal IPNs:</strong> The PayPal IPN service will notify s2Member whenever a Member\'s payments have been failing, and/or whenever a Member\'s Subscription has expired for any reason. Even refunds &amp; chargeback reversals are supported through the IPN service. For example, if you issue a refund to an unhappy Customer through PayPal, s2Member will eventually be notified, and the account for that Customer will either be assigned the role selected under Demote To Role, or have the configured Delete behavior applied automatically. The communication from PayPal → s2Member is seamless.</p>'."\n";
1681 echo '<p><em><strong>Some Hairy Details:</strong> There might be times whenever you notice that a Member\'s Subscription has been cancelled through PayPal... but, s2Member continues allowing the User access to your site as a paid Member. Please don\'t be confused by this... in 99.9% of these cases, the reason for this is legitimate. s2Member will only process the membership EOT when an EOT (End Of Term) is reached, a refund occurs, a chargeback occurs, or when a cancellation later results in a delayed Auto-EOT by s2Member.</em></p>'."\n";
1682 echo '<p><em>s2Member will not process an EOT until the User has completely used up the time they paid for. In other words, if a User signs up for a monthly Subscription on Jan 1st, and then cancels their Subscription on Jan 15th; technically, they should still be allowed to access the site for another 15 days, and then on Feb 1st, the time they paid for has completely elapsed. At that time, s2Member will process their membership EOT by either assigning the role selected under Demote To Role or applying the configured Delete behavior. s2Member also calculates one extra day (24 hours) into its equation, just to make sure access is not removed sooner than a Customer might expect.</em></p>'."\n";
1683 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_during_eot_behavior", get_defined_vars());
1684
1685 //260820.0306 Show current processing health where Auto-EOT is configured, so pending/stalled work is visible before it becomes a support issue.
1686 $auto_eot_health = c_ws_plugin__s2member_auto_eots::auto_eot_system_health(TRUE);
1687 $auto_eot_legacy_cap = c_ws_plugin__s2member_auto_eots::auto_eot_system_legacy_cap_info();
1688 $auto_eot_mode = (string)$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["auto_eot_system_enabled"];
1689 $auto_eot_runtime_target = c_ws_plugin__s2member_auto_eots::auto_eot_system_runtime_budget($auto_eot_mode === '2');
1690 $php_execution_limit = (int)ini_get('max_execution_time');
1691 $auto_eot_status_labels = array('healthy' => 'Healthy', 'processing' => 'Processing', 'catching_up' => 'Catching up', 'attention' => 'Attention', 'error' => 'Needs attention', 'disabled' => 'Disabled');
1692 $auto_eot_status_label = isset($auto_eot_status_labels[$auto_eot_health['status']]) ? $auto_eot_status_labels[$auto_eot_health['status']] : ucfirst($auto_eot_health['status']);
1693
1694 $auto_eot_next_run = 0;
1695 if($auto_eot_mode === '1')
1696 {
1697 $_scheduled = array_filter(array((int)$auto_eot_health['recurring_at'], (int)$auto_eot_health['continuation_at']));
1698 $auto_eot_next_run = $_scheduled ? min($_scheduled) : 0;
1699 unset($_scheduled);
1700 }
1701
1702 echo '<div class="ws-menu-page-hr"></div>'."\n";
1703 echo '<h3>Automatic Behavior Status: <span class="ws-plugin--s2member-status-light ws-plugin--s2member-status-light-'.esc_attr($auto_eot_health['status']).'" aria-hidden="true"></span>'.esc_html($auto_eot_status_label).'</h3>'."\n";
1704 echo '<table class="ws-plugin--s2member-status-table"><tbody>'."\n";
1705 echo '<tr><th scope="row">Pending overdue EOTs:</th><td>'.number_format_i18n((int)$auto_eot_health['pending_count']).'</td></tr>'."\n";
1706 echo '<tr><th scope="row">Oldest overdue EOT:</th><td>'.($auto_eot_health['oldest_due_at'] ? esc_html(human_time_diff((int)$auto_eot_health['oldest_due_at'], time()).' ago') : 'None').'</td></tr>'."\n";
1707 echo '<tr><th scope="row">Last completed run:</th><td>'.($auto_eot_health['last_completed_at'] ? esc_html(human_time_diff((int)$auto_eot_health['last_completed_at'], time()).' ago') : 'Not recorded yet').'</td></tr>'."\n";
1708 echo '<tr><th scope="row">Last run:</th><td>'.($auto_eot_health['last_completed_at'] ? number_format_i18n((int)$auto_eot_health['last_processed']).' EOT(s) in '.esc_html(number_format_i18n((float)$auto_eot_health['last_runtime'], 2)).' seconds' : 'Not recorded yet').'</td></tr>'."\n";
1709 echo '<tr><th scope="row">Next run:</th><td>'.($auto_eot_mode === '1' ? ($auto_eot_next_run ? esc_html(($auto_eot_next_run <= time() ? human_time_diff($auto_eot_next_run, time()).' overdue' : 'in '.human_time_diff(time(), $auto_eot_next_run))) : 'Not scheduled') : ($auto_eot_mode === '2' ? 'Controlled by your external cron service' : 'Disabled')).'</td></tr>'."\n";
1710 echo '<tr><th scope="row">Current runtime target:</th><td>About '.esc_html(number_format_i18n($auto_eot_runtime_target, 0)).' seconds'.($php_execution_limit > 0 ? ' (PHP limit: '.esc_html($php_execution_limit).')' : ' (no set PHP limit)').'</td></tr>'."\n";
1711 //260828.0231 Keep current EOT work and previous EOT history separate while retaining WordPress' native Users table, filters, bulk actions, and Screen Options.
1712 echo '<tr><th scope="row">End-of-Term users:</th><td><a href="'.esc_url(admin_url('/users.php?s2member_view=eot_current')).'">Current</a> | <a href="'.esc_url(admin_url('/users.php?s2member_view=eot_previous')).'">Previous</a></td></tr>'."\n";
1713 echo '</tbody></table>'."\n";
1714 echo '<div class="ws-menu-page-hr"></div>'."\n";
1715
1716 //260820.0306 Legacy filters remain authoritative for compatibility, but explain when an inherited fixed-batch limit can now reduce adaptive throughput.
1717 if(!empty($auto_eot_legacy_cap['detected']))
1718 {
1719 echo '<div class="ws-menu-page-notice ws-menu-page-notice-info">'."\n";
1720 echo '<p><strong>Legacy processing limit detected.</strong> A developer customization is using <code>ws_plugin__s2member_auto_eot_system_per_process</code>. This filter was useful with s2Member\'s older fixed-batch processor, but the new runtime-adaptive engine can usually process more efficiently without it. Unless it exists for a specific site reason, this legacy limit is probably unhelpful now.</p>'."\n";
1721
1722 if($auto_eot_legacy_cap['last_hard_cap_source'] === 'filter' && $auto_eot_legacy_cap['last_hard_cap'] !== NULL)
1723 echo '<p>Last effective legacy limit: <strong>'.number_format_i18n((int)$auto_eot_legacy_cap['last_hard_cap']).' EOT(s) per pass</strong>.</p>'."\n";
1724
1725 if($auto_eot_legacy_cap['last_hard_cap_source'] === 'filter' && $auto_eot_legacy_cap['last_stop_reason'] === 'legacy_item_cap' && $auto_eot_legacy_cap['estimated_additional'] > 0)
1726 echo '<p>On the last constrained run, s2Member estimates that approximately <strong>'.number_format_i18n((int)$auto_eot_legacy_cap['estimated_additional']).' additional EOT(s)</strong> could have fit within the remaining safe runtime without this hard limit.</p>'."\n";
1727
1728 if(!empty($auto_eot_legacy_cap['sources']))
1729 {
1730 echo '<p><strong>Detected source'.(count($auto_eot_legacy_cap['sources']) === 1 ? '' : 's').':</strong><br />'."\n";
1731 foreach($auto_eot_legacy_cap['sources'] as $_source)
1732 {
1733 $_source_location = !empty($_source['file']) ? $_source['file'].(!empty($_source['line']) ? ':'.(int)$_source['line'] : '') : (!empty($_source['callback']) ? $_source['callback'] : 'Unknown callback');
1734 echo '<code>'.esc_html($_source_location).'</code><br />'."\n";
1735 }
1736 echo '</p>'."\n";
1737 unset($_source, $_source_location);
1738 }
1739 echo '</div>'."\n";
1740 }
1741
1742 echo '<p id="ws-plugin--s2member-auto-eot-system-enabled-via-cron"'.(($auto_eot_mode === '2') ? '' : ' style="display:none;"').'>If you\'d like to run s2Member\'s Auto-EOT System through a traditional Cron Job instead of WP-Cron, configure an HTTP Cron Job through your hosting/server control panel. Running it every <code>1 to 10 minutes</code> works well; about every <code>5 minutes</code> is a good default. Load this URL:<br /><code>'.esc_html(home_url("/?s2member_auto_eot_system_via_cron=1")).'</code></p>'."\n";
1743
1744 echo '<table class="form-table">'."\n";
1745 echo '<tbody>'."\n";
1746 echo '<tr>'."\n";
1747
1748 echo '<th>'."\n";
1749 echo '<label for="ws-plugin--s2member-auto-eot-system-enabled">'."\n";
1750 echo 'Enable s2Member\'s Auto-EOT System?'."\n";
1751 echo '</label>'."\n";
1752 echo '</th>'."\n";
1753
1754 echo '</tr>'."\n";
1755 echo '<tr>'."\n";
1756
1757 echo '<td>'."\n";
1758 echo '<select name="ws_plugin__s2member_auto_eot_system_enabled" id="ws-plugin--s2member-auto-eot-system-enabled">'."\n";
1759 //260820.0306 Health warnings now report scheduler problems explicitly, so always show the saved mode instead of blanking this field when cron is unhealthy.
1760 echo '<option value="1"'.(($auto_eot_mode === '1') ? ' selected="selected"' : '').'>Yes (enable the Auto-EOT System through WP-Cron)</option>'."\n";
1761 echo (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()) ? '<option value="2"'.(($auto_eot_mode === '2') ? ' selected="selected"' : '').'>Yes (but, I\'ll run it with my own Cron Job)</option>'."\n" : '';
1762 echo '<option value="0"'.(($auto_eot_mode === '0') ? ' selected="selected"' : '').'>No (disable the Auto-EOT System)</option>'."\n";
1763 echo '</select><br />'."\n";
1764 echo 'Recommended setting: (<code>Yes / enable via WP-Cron</code>)'."\n";
1765 echo '<br /><em>When disabled, automatic End-of-Term processing is paused. s2Member can still record End-of-Term dates, and s2Member Pro reminders based on stored End-of-Term dates can continue independently. Overdue End-of-Term actions remain pending and will be processed if the Auto-EOT System is enabled again.</em>'."\n";
1766 echo '</td>'."\n";
1767
1768 echo '</tr>'."\n";
1769 echo '</tbody>'."\n";
1770 echo '</table>'."\n";
1771
1772 echo '<div class="ws-menu-page-hr"></div>'."\n";
1773
1774 echo '<table class="form-table">'."\n";
1775 echo '<tbody>'."\n";
1776 echo '<tr>'."\n";
1777 echo '<th><label for="ws-plugin--s2member-auto-eot-system-runtime-mode">Auto-EOT Processing Runtime</label></th>'."\n";
1778 echo '</tr>'."\n";
1779 echo '<tr>'."\n";
1780 echo '<td>'."\n";
1781 echo '<select name="ws_plugin__s2member_auto_eot_system_runtime_mode" id="ws-plugin--s2member-auto-eot-system-runtime-mode">'."\n";
1782 echo '<option value="auto"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["auto_eot_system_runtime_mode"] === 'auto') ? ' selected="selected"' : '').'>Automatic (recommended)</option>'."\n";
1783 echo '<option value="custom"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["auto_eot_system_runtime_mode"] === 'custom') ? ' selected="selected"' : '').'>Custom maximum runtime</option>'."\n";
1784 echo '</select><br />'."\n";
1785 echo '<em>Automatic mode adapts the number of EOTs processed to the wall-clock time available in each run. The current target for this configuration is approximately <strong>'.esc_html(number_format_i18n($auto_eot_runtime_target, 1)).' seconds</strong>; it automatically leaves additional headroom below a finite PHP execution limit.</em>'."\n";
1786 echo '</td>'."\n";
1787 echo '</tr>'."\n";
1788 echo '<tr>'."\n";
1789 echo '<th><label for="ws-plugin--s2member-auto-eot-system-runtime-custom">Custom Maximum Runtime (seconds)</label></th>'."\n";
1790 echo '</tr>'."\n";
1791 echo '<tr>'."\n";
1792 echo '<td>'."\n";
1793 echo '<input type="number" min="1" max="3600" step="1" name="ws_plugin__s2member_auto_eot_system_runtime_custom" id="ws-plugin--s2member-auto-eot-system-runtime-custom" value="'.esc_attr($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["auto_eot_system_runtime_custom"]).'" /><br />'."\n";
1794 echo '<em>Used only in Custom mode. If PHP reports a finite execution limit, s2Member uses the lower of this value and 90% of PHP\'s limit. For example, if you enter 100 seconds and PHP\'s limit is 30 seconds, s2Member uses 27 seconds. Developers can still override the final runtime with <code>ws_plugin__s2member_auto_eot_system_runtime</code>.</em>'."\n";
1795 echo '</td>'."\n";
1796 echo '</tr>'."\n";
1797 echo '</tbody>'."\n";
1798 echo '</table>'."\n";
1799
1800 echo '<div class="ws-menu-page-hr"></div>'."\n";
1801
1802 echo '<table class="form-table">'."\n";
1803 echo '<tbody>'."\n";
1804 echo '<tr>'."\n";
1805
1806 echo '<th>'."\n";
1807 echo '<label for="ws-plugin--s2member-membership-eot-behavior">'."\n";
1808 echo 'Membership End-of-Term Behavior (Demote or Delete)?'."\n";
1809 echo '</label>'."\n";
1810 echo '</th>'."\n";
1811
1812 echo '</tr>'."\n";
1813 echo '<tr>'."\n";
1814
1815 echo '<td>'."\n";
1816 echo '<select name="ws_plugin__s2member_membership_eot_behavior" id="ws-plugin--s2member-membership-eot-behavior">'."\n";
1817 echo '<option value="demote"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["membership_eot_behavior"] === "demote") ? ' selected="selected"' : '').'>Demote (assign the role selected under Demote To Role)</option>'."\n";
1818 echo '<option value="delete"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["membership_eot_behavior"] === "delete") ? ' selected="selected"' : '').'>Delete (move to Pending Deletion for review)</option>'."\n";
1819 echo '</select><br />'."\n";
1820 //260822.0614 Keep the stored `delete` value for compatibility; irreversible deletion is an explicit developer opt-in rather than the product default.
1821 echo '<em>Delete removes s2Member membership access and moves the account to the <strong>Pending Deletion</strong> role for administrator review. To restore historical automatic irreversible deletion, a developer must explicitly allow it with the <code>ws_plugin__s2member_allow_eot_user_deletion</code> filter.</em>'."\n";
1822 echo '</td>'."\n";
1823
1824 echo '</tr>'."\n";
1825
1826 //260916.1840 Keep the global EOT role destination/scope controls identical across every gateway page.
1827 c_ws_plugin__s2member_menu_pages::eot_demotion_options();
1828
1829 echo '<tr>'."\n";
1830
1831 echo '<th>'."\n";
1832 echo '<label for="ws-plugin--s2member-eots-remove-ccaps">'."\n";
1833 echo 'Membership EOTs also Remove all Custom Capabilities?'."\n";
1834 echo '</label>'."\n";
1835 echo '</th>'."\n";
1836
1837 echo '</tr>'."\n";
1838 echo '<tr>'."\n";
1839
1840 echo '<td>'."\n";
1841 echo '<select name="ws_plugin__s2member_eots_remove_ccaps" id="ws-plugin--s2member-eots-remove-ccaps">'."\n";
1842 echo '<option value="1"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["eots_remove_ccaps"]) ? ' selected="selected"' : '').'>Yes (an EOT also results in the loss of any Custom Capabilities a User/Member may have)</option>'."\n";
1843 echo '<option value="0"'.((!$GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["eots_remove_ccaps"]) ? ' selected="selected"' : '').'>No (an EOT has no impact on any Custom Capabilities a User/Member may have)</option>'."\n";
1844 echo '</select><br />'."\n";
1845 echo '<em>NOTE: If Refunds/Reversals trigger an Immediate EOT (see setting below); Custom Capabilities will always be removed when/if a Refund or Reversal occurs. In other words, this setting is ignored for Refunds/Reversals (IF they trigger an Immediate EOT—based on your configuration below). If you prefer to review all Refunds/Reversals for yourself, please choose that option below.</em>'."\n";
1846 echo '</td>'."\n";
1847
1848 echo '</tr>'."\n";
1849 echo '</tbody>'."\n";
1850 echo '</table>'."\n";
1851
1852 echo '<div class="ws-menu-page-hr"></div>'."\n";
1853
1854 echo '<table class="form-table">'."\n";
1855 echo '<tbody>'."\n";
1856 echo '<tr>'."\n";
1857
1858 echo '<th>'."\n";
1859 echo '<label for="ws-plugin--s2member-eot-grace-time">'."\n";
1860 echo 'EOT Grace Time (in seconds):'."\n";
1861 echo '</label>'."\n";
1862 echo '</th>'."\n";
1863
1864 echo '</tr>'."\n";
1865 echo '<tr>'."\n";
1866
1867 echo '<td>'."\n";
1868 echo '<input type="text" autocomplete="off" name="ws_plugin__s2member_eot_grace_time" id="ws-plugin--s2member-eot-grace-time" value="'.format_to_edit($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["eot_grace_time"]).'" /><br />'."\n";
1869 echo '<em>This is represented in seconds. For example, a value of: <code>86400</code> = 1 day. Your EOT Grace Time; is the amount of time you will offer as a grace period (if any). Most site owners will give customers an additional 24 hours of access; just to help avoid any negativity that may result from a customer losing access sooner than they might expect. You can disable EOT Grace Time by setting this to: <code>0</code>. Note: there is NO Grace Time applied when/if a Refund or Reversal occurs. If Refunds/Reversals trigger an Immediate EOT (see setting below); there is never any Grace Time applied in that scenario.</em>'."\n";
1870 echo '</td>'."\n";
1871
1872 echo '</tr>'."\n";
1873 echo '</tbody>'."\n";
1874 echo '</table>'."\n";
1875
1876 echo '<div class="ws-menu-page-hr"></div>'."\n";
1877
1878 echo '<table class="form-table">'."\n";
1879 echo '<tbody>'."\n";
1880 echo '<tr>'."\n";
1881
1882 echo '<th>'."\n";
1883 //260824.1957 Clarify that disputes/chargebacks are normalized into the existing Reversals policy without changing stored option values.
1884 echo '<label for="ws-plugin--s2member-triggers-immediate-eot">'."\n";
1885 echo 'Refunds/Partial Refunds/Reversals/Disputes (trigger Immediate EOT)?'."\n";
1886 echo '</label>'."\n";
1887 echo '</th>'."\n";
1888
1889 echo '</tr>'."\n";
1890 echo '<tr>'."\n";
1891
1892 echo '<td>'."\n";
1893 echo '<select name="ws_plugin__s2member_triggers_immediate_eot" id="ws-plugin--s2member-triggers-immediate-eot">'."\n";
1894 echo '<option value="none"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["triggers_immediate_eot"] === "none") ? ' selected="selected"' : '').'>None (I\'ll review these events manually)</option>'."\n";
1895 echo '<option value="refunds"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["triggers_immediate_eot"] === "refunds") ? ' selected="selected"' : '').'>Full Refunds (full refunds only; ALWAYS trigger an Immediate EOT action)</option>'."\n";
1896 echo '<option value="reversals"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["triggers_immediate_eot"] === "reversals") ? ' selected="selected"' : '').'>Reversals/Disputes (including chargebacks; ALWAYS trigger an Immediate EOT action)</option>'."\n";
1897 echo '<option value="refunds,reversals"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["triggers_immediate_eot"] === "refunds,reversals") ? ' selected="selected"' : '').'>Full Refunds, Reversals/Disputes (these ALWAYS trigger an Immediate EOT action)</option>'."\n";
1898 echo '<option value="refunds,partial_refunds,reversals"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["triggers_immediate_eot"] === "refunds,partial_refunds,reversals") ? ' selected="selected"' : '').'>Full Refunds, Partial Refunds, Reversals/Disputes (these ALWAYS trigger an Immediate EOT action)</option>'."\n";
1899 echo '</select><br />'."\n";
1900 echo '<em><strong>Disputes/Chargebacks:</strong> When a supported gateway reports a dispute or chargeback, s2Member treats it as a Reversal for this setting. <strong>Note:</strong> s2Member is not equipped to detect partial refunds against multi-payment Subscriptions reliably. Therefore, all refunds processed against Subscriptions (of any kind) are considered <strong>Partial</strong> Refunds. Full refunds (in the eyes of s2Member) occur only against Buy Now transactions where it is easy for s2Member to see that the refund amount is &gt;= the original Buy Now purchase price (i.e., a Full Refund). <strong>Also Note:</strong> This setting (no matter what you choose) will NOT impact s2Member\'s internal API Notifications for Refund/Reversal events. <a href="#" onclick="alert(\'A Full or Partial Refund; and/or a Reversal Notification will ALWAYS be processed internally by s2Member, even if no action is taken by s2Member in accordance with your configuration here.\\n\\nIn this way, you\\\'ll have the full ability to listen for these events on your own (via API Notifications); if you prefer (optional). For more information, check your Dashboard under: `s2Member → API Notifications → Refunds/Reversals`.\'); return false;">Click here for details</a>.</em>'."\n";
1901 echo '</td>'."\n";
1902
1903 echo '</tr>'."\n";
1904 echo '</tbody>'."\n";
1905 echo '</table>'."\n";
1906
1907 echo '<div class="ws-menu-page-hr"></div>'."\n";
1908
1909 echo '<table class="form-table">'."\n";
1910 echo '<tbody>'."\n";
1911 echo '<tr>'."\n";
1912
1913 echo '<th>'."\n";
1914 echo '<label for="ws-plugin--s2member-eot-time-ext-behavior">'."\n";
1915 echo 'Fixed-Term Extensions (Auto-Extend)?'."\n";
1916 echo '</label>'."\n";
1917 echo '</th>'."\n";
1918
1919 echo '</tr>'."\n";
1920 echo '<tr>'."\n";
1921
1922 echo '<td>'."\n";
1923 echo '<select name="ws_plugin__s2member_eot_time_ext_behavior" id="ws-plugin--s2member-eot-time-ext-behavior">'."\n";
1924 echo '<option value="extend"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["eot_time_ext_behavior"] === "extend") ? ' selected="selected"' : '').'>Yes (default, automatically extend any existing EOT Time)</option>'."\n";
1925 echo '<option value="reset"'.(($GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["eot_time_ext_behavior"] === "reset") ? ' selected="selected"' : '').'>No (do NOT extend; s2Member should reset EOT Time completely)</option>'."\n";
1926 echo '</select><br />'."\n";
1927 echo '<em>This setting will only affect Buy Now transactions for fixed-term lengths. By default, s2Member will automatically extend any existing EOT Time that a Customer may have. For example, if I buy one year of access, and then I buy another year of access (before my first year is totally used up); I end up with everything I paid you for (now over 1 year of access) if this is set to <code>Yes</code>. If this was set to <code>No</code>, the EOT Time would be reset when I make the second purchase; leaving me with only 1 year of access, starting the date of my second purchase.</em>'."\n";
1928 echo '</td>'."\n";
1929
1930 echo '</tr>'."\n";
1931 echo '</tbody>'."\n";
1932 echo '</table>'."\n";
1933 echo '</div>'."\n";
1934
1935 echo '</div>'."\n";
1936
1937 do_action("ws_plugin__s2member_during_paypal_ops_page_during_left_sections_after_eot_behavior", get_defined_vars());
1938 }
1939
1940 do_action("ws_plugin__s2member_during_paypal_ops_page_after_left_sections", get_defined_vars());
1941
1942 echo '<p class="submit"><input type="submit" value="Save All Changes" /></p>'."\n";
1943
1944 echo '</form>'."\n";
1945
1946 echo '</td>'."\n";
1947
1948 echo '<td class="ws-menu-page-table-r">'."\n";
1949 c_ws_plugin__s2member_menu_pages_rs::display();
1950 echo '</td>'."\n";
1951
1952 echo '</tr>'."\n";
1953 echo '</tbody>'."\n";
1954 echo '</table>'."\n";
1955
1956 echo '</div>'."\n";
1957 }
1958 }
1959 }
1960
1961 new c_ws_plugin__s2member_menu_page_paypal_ops();
1962