← All changes
|
src/includes/classes/sc-paypal-button-in.inc.php
+96
-21
260913
→
261001
View file →
| @@ -74,8 +74,12 @@ | ||
| 74 | 74 | |
| 75 | 75 | $force_notify_url_scheme = apply_filters("ws_plugin__s2member_during_sc_paypal_button_force_notify_url_scheme", null, get_defined_vars ()); |
| 76 | 76 | $force_return_url_scheme = apply_filters("ws_plugin__s2member_during_sc_paypal_button_force_return_url_scheme", null, get_defined_vars ()); |
| 77 | 77 | |
| 78 | + //260918.2104 TO-DO PayPal Checkout cache hardening: do not embed the one-hour, visitor-specific transaction token in rendered page HTML. | |
| 79 | + // Render a cache-safe encrypted checkout definition instead, then mint the short-lived invoice/IP/user-context transaction token server-side | |
| 80 | + // when checkout actually starts (output="button", "anchor", or "url"), preserving validation, idempotency, subscription context, and return/cancel behavior. | |
| 81 | + | |
| 78 | 82 | // PayPal Checkout SDK memoization (per request; shared across all button variants). |
| 79 | 83 | static $ppco_sdks = array(); |
| 80 | 84 | |
| 81 | 85 | foreach(array_keys(get_defined_vars())as$__v)$__refs[$__v]=&$$__v; |
| @@ -195,8 +199,12 @@ | ||
| 195 | 199 | |
| 196 | 200 | $ppco_btn_id = 's2member_ppco_cancel_'.md5($user_id.$subscr_id); |
| 197 | 201 | $ppco_msg_id = 's2member_ppco_cancel_msg_'.md5($user_id.$subscr_id); |
| 198 | 202 | |
| 203 | + //260913.1946 Validate Pro's optional success URL before exposing it to cancellation JavaScript; shortcode attributes may be authored by Editors. | |
| 204 | + $ppco_success_url = (c_ws_plugin__s2member_utils_conds::pro_is_installed() && !empty($attr["success"]) && is_string($attr["success"])) ? wp_validate_redirect($attr["success"], '') : ''; | |
| 205 | + $ppco_success_json = wp_json_encode($ppco_success_url, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); | |
| 206 | + | |
| 199 | 207 | $code = '<style type="text/css">#'.esc_attr($ppco_btn_id).'{display:inline-flex;align-items:center;justify-content:center;width:150px;height:40px;padding:10px 0;border-radius:4px;border:1px solid rgba(0,0,0,0.06);background:#ffc439;color:#003087;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:600;cursor:pointer;box-sizing:border-box;white-space:nowrap;}#'.esc_attr($ppco_btn_id).':hover{filter:brightness(0.98);}#'.esc_attr($ppco_btn_id).':disabled{opacity:0.65;cursor:not-allowed;}</style>'."\n"; |
| 200 | 208 | $code .= '<button type="button" id="'.esc_attr($ppco_btn_id).'">'.esc_html(_x('Unsubscribe', 'paypal cancellation button label', 's2member')).'</button>'."\n"; //260218 |
| 201 | 209 | $code .= '<div id="'.esc_attr($ppco_msg_id).'" style="display:none; margin-top:8px;"></div>'."\n"; |
| 202 | 210 | $code .= '<script type="text/javascript">'."\n"; |
| @@ -203,8 +211,9 @@ | ||
| 203 | 211 | $code .= '(function(){'."\n"; |
| 204 | 212 | $code .= 'var b=document.getElementById("'.esc_js($ppco_btn_id).'");'."\n"; |
| 205 | 213 | $code .= 'var m=document.getElementById("'.esc_js($ppco_msg_id).'");'."\n"; |
| 206 | 214 | $code .= 'var u="'.esc_js($pp_manage_url).'";'."\n"; |
| 215 | + $code .= 'var s='.$ppco_success_json.';'."\n"; | |
| 207 | 216 | $code .= 'function goManage(){try{var w=window.open(u,"_blank","noopener");if(!w){window.location.href=u;}}catch(e){window.location.href=u;}}'."\n"; |
| 208 | 217 | $code .= 'function show(msg){try{if(m){m.style.display="block";m.innerHTML=msg;}}catch(e){}}'."\n"; |
| 209 | 218 | $code .= 'function enc(o){var s=[];for(var k in o){if(!o.hasOwnProperty(k))continue;s.push(encodeURIComponent(k)+"="+encodeURIComponent(o[k]));}return s.join("&");}'."\n"; |
| 210 | 219 | $code .= 'if(!b){return;}'."\n"; |
| @@ -214,9 +223,9 @@ | ||
| 214 | 223 | $code .= 'if(!window.confirm("'.esc_js(__('Cancel your subscription now?', 's2member')).'")){return;}'."\n"; //260218 |
| 215 | 224 | $code .= 'b.disabled=true;'."\n"; |
| 216 | 225 | $code .= 'fetch("'.esc_js($ppco_endpoint).'",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"cancel_subscription",s2member_paypal_checkout_t:"'.esc_js($ppco_token).'",s2member_paypal_checkout_nonce:"'.esc_js($ppco_nonce).'"} )})'."\n"; |
| 217 | 226 | $code .= '.then(function(r){return r.json();})'."\n"; |
| 218 | - $code .= '.then(function(res){if(res&&res.ok){show("'.esc_js(__('Subscription cancelled.', 's2member')).'");}else{goManage(); b.disabled=false;}})'."\n"; //260218 | |
| 227 | + $code .= '.then(function(res){if(res&&res.ok){if(s){window.location.assign(s);}else{show("'.esc_js(__('Subscription cancelled.', 's2member')).'");}}else{goManage(); b.disabled=false;}})'."\n"; //260913.1946 Honor Pro's validated success URL after a confirmed on-site cancellation. | |
| 219 | 228 | $code .= '.catch(function(){goManage(); b.disabled=false;});'."\n"; |
| 220 | 229 | $code .= '});'."\n"; |
| 221 | 230 | $code .= '})();'."\n"; |
| 222 | 231 | $code .= '</script>'."\n"; |
| @@ -287,8 +296,16 @@ | ||
| 287 | 296 | $paypal_os1_input_value = /* Current User's IP Address for tracking purposes. */ c_ws_plugin__s2member_utils_ip::current(); |
| 288 | 297 | |
| 289 | 298 | $paypal_invoice_input_value = /* s2Member's Unique Code~IP combo. */ uniqid () . "~" . c_ws_plugin__s2member_utils_ip::current(); |
| 290 | 299 | |
| 300 | + //260928.1515 Give each rendered PayPal Checkout button a signed provisional identity; persist its Gateway Checkout only when the buyer starts, never on page render. | |
| 301 | + $ppco_gateway_checkout_identity = FALSE; | |
| 302 | + if(c_ws_plugin__s2member_paypal_utilities::paypal_checkout_is_enabled()) | |
| 303 | + { | |
| 304 | + $ppco_gateway_checkout_identity = c_ws_plugin__s2member_gateway_checkouts::browser_identity(); | |
| 305 | + $paypal_invoice_input_value = 's2mb-'.(string)$ppco_gateway_checkout_identity['id']; | |
| 306 | + } | |
| 307 | + | |
| 291 | 308 | $attr["sp_ids_exp"] = /* Combined "sp:ids:expiration hours". */ "sp:" . $attr["ids"] . ":" . $attr["exp"]; |
| 292 | 309 | |
| 293 | 310 | $success_return_url = /* s2Member handles this all by itself. However, it can be Filtered. */ home_url ("/?s2member_paypal_return=1", $force_return_url_scheme); |
| 294 | 311 | $success_return_url = apply_filters("ws_plugin__s2member_during_sc_paypal_button_success_return_url", $success_return_url, get_defined_vars ()); |
| @@ -346,8 +363,15 @@ | ||
| 346 | 363 | |
| 347 | 364 | 'checksum' => md5($paypal_invoice_input_value.c_ws_plugin__s2member_utils_ip::current().$attr["sp_ids_exp"]), |
| 348 | 365 | ); |
| 349 | 366 | |
| 367 | + //260928.1515 Bind the PayPal invoice and encrypted purchase terms to the same provisional Gateway Checkout identity, including in anchor/url mode. | |
| 368 | + if($ppco_gateway_checkout_identity) | |
| 369 | + { | |
| 370 | + $ppco_token['gateway_checkout_id'] = (string)$ppco_gateway_checkout_identity['id']; | |
| 371 | + $ppco_token['gateway_checkout_token'] = (string)$ppco_gateway_checkout_identity['token']; | |
| 372 | + } | |
| 373 | + | |
| 350 | 374 | $ppco_token = urlencode(c_ws_plugin__s2member_utils_encryption::encrypt(serialize($ppco_token))); |
| 351 | 375 | |
| 352 | 376 | // output="anchor|url" support (no JS SDK; redirects through s2Member, then to PayPal approval URL). |
| 353 | 377 | if($attr["output"] === "anchor" || $attr["output"] === "url") |
| @@ -411,22 +435,26 @@ | ||
| 411 | 435 | $code .= 'var d="'.esc_js($ppco_div_id).'";'."\n"; |
| 412 | 436 | $code .= 'var e="'.esc_js($ppco_err_id).'";'."\n"; |
| 413 | 437 | $code .= 'var t="'.esc_js($ppco_token).'";'."\n"; |
| 414 | 438 | $code .= 'var u="'.esc_js($ppco_endpoint).'";'."\n"; |
| 439 | + //260928.1739 Bind to the SDK script actually emitted above and encode its URL as JavaScript, not HTML entities. | |
| 415 | 440 | $code .= 'var ns="'.esc_js($ppco_sdk_ns).'";'."\n"; |
| 416 | - $code .= 'var s="'.esc_js($ppco_sdk_src).'";'."\n"; | |
| 441 | + $code .= 'var sdkId="'.esc_js($ppco_sdk_id).'";'."\n"; | |
| 442 | + $code .= 'var s='.wp_json_encode($ppco_sdk_src, JSON_HEX_AMP | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT).';'."\n"; | |
| 417 | 443 | $code .= 'var cid="'.esc_js($paypal_invoice_input_value).'";'."\n"; |
| 418 | 444 | //260819.0042 Keep standalone Checkout feedback consistent with Pro-Forms while distinguishing cancellation from errors. |
| 419 | 445 | $code .= 'function showMsg(m,t){try{var el=document.getElementById(e);if(el){el.style.display="block";el.innerHTML="<span class=\"ws-plugin--s2member-ppco-"+(t==="info"?"info":"error")+"\">"+m+"</span>";}}catch(x){}}function showErr(m){showMsg(m,"error");}function showInfo(m){showMsg(m,"info");}'."\n"; |
| 420 | 446 | $code .= 'function postTo(url, data){var f=document.createElement("form");f.method="post";f.acceptCharset="UTF-8";f.action=url;for(var k in data){if(!data.hasOwnProperty(k))continue;var i=document.createElement("input");i.type="hidden";i.name=k;i.value=data[k];f.appendChild(i);}document.body.appendChild(f);f.submit();}'."\n"; //260817 Keep signed PayPal Checkout returns in UTF-8. |
| 421 | 447 | $code .= 'function enc(o){var a=[];for(var k in o){if(!o.hasOwnProperty(k))continue;a.push(encodeURIComponent(k)+"="+encodeURIComponent(o[k]));}return a.join("&");}'."\n"; |
| 422 | - $code .= 'function loadSdk(cb){var P=window[ns];if(P&&P.Buttons){cb(P);return;}var id=ns+"_sdk",tag=document.getElementById(id),done=false;function finish(){if(done)return;done=true;cb(window[ns]&&window[ns].Buttons?window[ns]:null);}function ok(){finish();}function fail(){finish();}if(tag){if(window[ns]&&window[ns].Buttons){finish();return;}if(tag.getAttribute("src")!==s){tag.setAttribute("src",s);}if(tag.readyState==="complete"||tag.readyState==="loaded"){setTimeout(finish,0);return;}tag.addEventListener("load",ok);tag.addEventListener("error",fail);setTimeout(finish,3500);return;}tag=document.createElement("script");tag.id=id;tag.setAttribute("data-namespace",ns);tag.src=s;tag.async=true;tag.onload=ok;tag.onerror=fail;(document.head||document.body||document.documentElement).appendChild(tag);setTimeout(finish,3500);}'."\n"; | |
| 448 | + $code .= 'function loadSdk(cb){var P=window[ns];if(P&&P.Buttons){cb(P);return;}var id=sdkId,tag=document.getElementById(id),done=false;function finish(){if(done)return;done=true;cb(window[ns]&&window[ns].Buttons?window[ns]:null);}function ok(){finish();}function fail(){finish();}if(tag){if(window[ns]&&window[ns].Buttons){finish();return;}if(tag.getAttribute("src")!==s){tag.setAttribute("src",s);}if(tag.readyState==="complete"||tag.readyState==="loaded"){setTimeout(finish,0);return;}tag.addEventListener("load",ok);tag.addEventListener("error",fail);setTimeout(finish,3500);return;}tag=document.createElement("script");tag.id=id;tag.setAttribute("data-namespace",ns);tag.src=s;tag.async=true;tag.onload=ok;tag.onerror=fail;(document.head||document.body||document.documentElement).appendChild(tag);setTimeout(finish,3500);}'."\n"; | |
| 423 | 449 | if($ppco_intent === 'subscription') |
| 424 | 450 | { |
| 425 | - $code .= 'function getPlanId(){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"get_plan_id",s2member_paypal_checkout_t:t})}).then(function(r){return r.json();}).then(function(res){if(res&&res.plan_id)return res.plan_id;throw(res&&res.error?res.error:"plan_get_failed");});}'."\n"; | |
| 451 | + //260928.1739 WordPress rendered this inline JS with && inside throw(res&&res.error), breaking the entire PayPal Button script. Use a ternary and || instead. | |
| 452 | + $code .= 'function getPlanId(){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"get_plan_id",s2member_paypal_checkout_t:t})}).then(function(r){return r.json();}).then(function(res){if(res&&res.plan_id)return res.plan_id;throw(res ? (res.error || "plan_get_failed") : "plan_get_failed");});}'."\n"; | |
| 426 | 453 | $code .= 'var planId=null;'."\n"; |
| 427 | 454 | $code .= 'function createSubscription(data,actions){if(planId)return actions.subscription.create({plan_id:planId,custom_id:cid,application_context:{shipping_preference:"NO_SHIPPING"}});return getPlanId().then(function(pid){planId=pid;return actions.subscription.create({plan_id:planId,custom_id:cid,application_context:{shipping_preference:"NO_SHIPPING"}});});}'."\n"; |
| 428 | - $code .= 'function onApprove(data){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"confirm_subscription",s2member_paypal_checkout_t:t,subscription_id:(data&&data.subscriptionID?data.subscriptionID:"")})}).then(function(r){return r.json();}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}throw(res&&res.error?res.error:"subscription_confirm_failed");}).catch(function(e){showErr("Subscription could not be completed. Please try again.");});}'."\n"; | |
| 455 | + //260928.1739 WordPress rendered this inline JS with && inside throw(res&&res.error), breaking the entire PayPal Button script. Use a ternary and || instead. | |
| 456 | + $code .= 'function onApprove(data){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"confirm_subscription",s2member_paypal_checkout_t:t,subscription_id:(data&&data.subscriptionID?data.subscriptionID:"")})}).then(function(r){return r.json();}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}throw(res ? (res.error || "subscription_confirm_failed") : "subscription_confirm_failed");}).catch(function(e){showErr("Subscription could not be completed. Please try again.");});}'."\n"; | |
| 429 | 457 | $code .= 'function onCancel(){showInfo("Subscription cancelled.");}'."\n"; |
| 430 | 458 | $code .= 'function onError(err){var m="PayPal error. Please try again.";try{if(err){if(typeof err==="string")m="PayPal error: "+err;else if(err.message)m="PayPal error: "+err.message;}}catch(x){}showErr(m);}'."\n"; |
| 431 | 459 | $code .= 'function init(){loadSdk(function(P){var el=document.getElementById(d);if(!el){return;}if(el.getAttribute("data-s2m-ppco-rendered")==="1"){return;}if(!P||!P.Buttons){showErr("PayPal SDK failed to load.");return;}el.setAttribute("data-s2m-ppco-rendered","1");try{P.Buttons({fundingSource:P.FUNDING.PAYPAL,style:{layout:"vertical",tagline:false,height:40},createSubscription:createSubscription,onApprove:onApprove,onCancel:onCancel,onError:onError}).render("#"+d);}catch(x){el.removeAttribute("data-s2m-ppco-rendered");showErr("PayPal render failed.");}});}'."\n"; |
| 432 | 460 | } |
| @@ -431,10 +459,13 @@ | ||
| 431 | 459 | $code .= 'function init(){loadSdk(function(P){var el=document.getElementById(d);if(!el){return;}if(el.getAttribute("data-s2m-ppco-rendered")==="1"){return;}if(!P||!P.Buttons){showErr("PayPal SDK failed to load.");return;}el.setAttribute("data-s2m-ppco-rendered","1");try{P.Buttons({fundingSource:P.FUNDING.PAYPAL,style:{layout:"vertical",tagline:false,height:40},createSubscription:createSubscription,onApprove:onApprove,onCancel:onCancel,onError:onError}).render("#"+d);}catch(x){el.removeAttribute("data-s2m-ppco-rendered");showErr("PayPal render failed.");}});}'."\n"; |
| 432 | 460 | } |
| 433 | 461 | else |
| 434 | 462 | { |
| 435 | - $code .= 'function createOrder(){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"create_order",s2member_paypal_checkout_t:t})}).then(function(r){return r.json();}).then(function(res){if(res&&res.order_id)return res.order_id;throw(res&&res.error?res.error:"order_create_failed");});}'."\n"; | |
| 436 | - $code .= 'function onApprove(data){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"capture_order",s2member_paypal_checkout_t:t,order_id:(data&&data.orderID?data.orderID:"")})}).then(function(r){return r.json();}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}throw(res&&res.error?res.error:"order_capture_failed");}).catch(function(e){showErr("Payment could not be completed. Please try again.");});}'."\n"; | |
| 463 | + $code .= 'function request(op,vars){var body={s2member_paypal_checkout_op:op,s2member_paypal_checkout_t:t};for(var k in (vars||{})){if(vars.hasOwnProperty(k))body[k]=vars[k];}return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc(body)}).then(function(r){return r.json();});}'."\n"; | |
| 464 | + $code .= 'function createOrder(){return request("create_order").then(function(res){if(res&&res.order_id)return res.order_id;throw(res ? (res.error || "order_create_failed") : "order_create_failed");});}'."\n"; | |
| 465 | + //260928.1703 A completed PayPal capture can reach the verified webhook even when the browser loses its response. Poll only our durable state and submit its saved signed handoff; never issue a second capture on uncertainty. | |
| 466 | + $code .= 'function recoverOrder(n){return request("get_order_status").then(function(res){if(res&&res.fulfilled&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}if(n>=20)throw "order_confirmation_pending";return new Promise(function(resolve){setTimeout(resolve,1200);}).then(function(){return recoverOrder(n+1);});});}'."\n"; | |
| 467 | + $code .= 'function onApprove(data){var oid=data&&data.orderID?data.orderID:"";return request("capture_order",{order_id:oid}).catch(function(){return {recoverable:true};}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}if(res&&res.recoverable)return recoverOrder(0).catch(function(){showInfo("Payment confirmation is pending. Please do not pay again; check your email or contact support.");});showErr("Payment could not be confirmed. Please contact support before trying again.");});}'."\n"; | |
| 437 | 468 | $code .= 'function onCancel(){showInfo("Payment cancelled.");}'."\n"; |
| 438 | 469 | $code .= 'function onError(err){var m="PayPal error. Please try again.";try{if(err){if(typeof err==="string")m="PayPal error: "+err;else if(err.message)m="PayPal error: "+err.message;}}catch(x){}showErr(m);}'."\n"; |
| 439 | 470 | $code .= 'function init(){loadSdk(function(P){var el=document.getElementById(d);if(!el){return;}if(el.getAttribute("data-s2m-ppco-rendered")==="1"){return;}if(!P||!P.Buttons){showErr("PayPal SDK failed to load.");return;}el.setAttribute("data-s2m-ppco-rendered","1");try{P.Buttons({fundingSource:P.FUNDING.PAYPAL,style:{layout:"vertical",tagline:false,height:40},createOrder:createOrder,onApprove:onApprove,onCancel:onCancel,onError:onError}).render("#"+d);}catch(x){el.removeAttribute("data-s2m-ppco-rendered");showErr("PayPal render failed.");}});}'."\n"; |
| 440 | 471 | } |
| @@ -500,8 +531,16 @@ | ||
| 500 | 531 | $paypal_os1_input_value = /* Current User's IP Address for tracking purposes. */ c_ws_plugin__s2member_utils_ip::current(); |
| 501 | 532 | |
| 502 | 533 | $paypal_invoice_input_value = /* s2Member's Unique Code~IP combo. */ uniqid () . "~" . c_ws_plugin__s2member_utils_ip::current(); |
| 503 | 534 | |
| 535 | + //260928.1515 Give each rendered PayPal Checkout button a signed provisional identity; persist its Gateway Checkout only when the buyer starts, never on page render. | |
| 536 | + $ppco_gateway_checkout_identity = FALSE; | |
| 537 | + if(c_ws_plugin__s2member_paypal_utilities::paypal_checkout_is_enabled()) | |
| 538 | + { | |
| 539 | + $ppco_gateway_checkout_identity = c_ws_plugin__s2member_gateway_checkouts::browser_identity(); | |
| 540 | + $paypal_invoice_input_value = 's2mb-'.(string)$ppco_gateway_checkout_identity['id']; | |
| 541 | + } | |
| 542 | + | |
| 504 | 543 | $attr["level_ccaps_eotper"] = ($attr["rr"] === "BN" && $attr["rt"] !== "L") ? $attr["level"] . ":" . $attr["ccaps"] . ":" . $attr["rp"] . " " . $attr["rt"] : $attr["level"] . ":" . $attr["ccaps"]; |
| 505 | 544 | $attr["level_ccaps_eotper"] = /* Clean any trailing separators from this string. */ rtrim ($attr["level_ccaps_eotper"], ":"); |
| 506 | 545 | |
| 507 | 546 | $success_return_url = /* s2Member handles this all by itself. However, it can be Filtered. */ home_url ("/?s2member_paypal_return=1", $force_return_url_scheme); |
| @@ -561,8 +600,15 @@ | ||
| 561 | 600 | |
| 562 | 601 | 'checksum' => md5($paypal_invoice_input_value.c_ws_plugin__s2member_utils_ip::current().$attr["level_ccaps_eotper"]), |
| 563 | 602 | ); |
| 564 | 603 | |
| 604 | + //260928.1515 Bind the PayPal invoice and encrypted purchase terms to the same provisional Gateway Checkout identity, including in anchor/url mode. | |
| 605 | + if($ppco_gateway_checkout_identity) | |
| 606 | + { | |
| 607 | + $ppco_token['gateway_checkout_id'] = (string)$ppco_gateway_checkout_identity['id']; | |
| 608 | + $ppco_token['gateway_checkout_token'] = (string)$ppco_gateway_checkout_identity['token']; | |
| 609 | + } | |
| 610 | + | |
| 565 | 611 | $ppco_token = urlencode(c_ws_plugin__s2member_utils_encryption::encrypt(serialize($ppco_token))); |
| 566 | 612 | |
| 567 | 613 | // output="anchor|url" support (no JS SDK; redirects through s2Member, then to PayPal approval URL). |
| 568 | 614 | if($attr["output"] === "anchor" || $attr["output"] === "url") |
| @@ -626,22 +672,25 @@ | ||
| 626 | 672 | $code .= 'var d="'.esc_js($ppco_div_id).'";'."\n"; |
| 627 | 673 | $code .= 'var e="'.esc_js($ppco_err_id).'";'."\n"; |
| 628 | 674 | $code .= 'var t="'.esc_js($ppco_token).'";'."\n"; |
| 629 | 675 | $code .= 'var u="'.esc_js($ppco_endpoint).'";'."\n"; |
| 676 | + //260928.1739 Bind to the SDK script actually emitted above and encode its URL as JavaScript, not HTML entities. | |
| 630 | 677 | $code .= 'var ns="'.esc_js($ppco_sdk_ns).'";'."\n"; |
| 631 | - $code .= 'var s="'.esc_js($ppco_sdk_src).'";'."\n"; | |
| 678 | + $code .= 'var sdkId="'.esc_js($ppco_sdk_id).'";'."\n"; | |
| 679 | + $code .= 'var s='.wp_json_encode($ppco_sdk_src, JSON_HEX_AMP | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT).';'."\n"; | |
| 632 | 680 | $code .= 'var cid="'.esc_js($paypal_invoice_input_value).'";'."\n"; |
| 633 | 681 | //260819.0042 Keep standalone Checkout feedback consistent with Pro-Forms while distinguishing cancellation from errors. |
| 634 | 682 | $code .= 'function showMsg(m,t){try{var el=document.getElementById(e);if(el){el.style.display="block";el.innerHTML="<span class=\"ws-plugin--s2member-ppco-"+(t==="info"?"info":"error")+"\">"+m+"</span>";}}catch(x){}}function showErr(m){showMsg(m,"error");}function showInfo(m){showMsg(m,"info");}'."\n"; |
| 635 | 683 | $code .= 'function postTo(url, data){var f=document.createElement("form");f.method="post";f.acceptCharset="UTF-8";f.action=url;for(var k in data){if(!data.hasOwnProperty(k))continue;var i=document.createElement("input");i.type="hidden";i.name=k;i.value=data[k];f.appendChild(i);}document.body.appendChild(f);f.submit();}'."\n"; //260817 Keep signed PayPal Checkout returns in UTF-8. |
| 636 | 684 | $code .= 'function enc(o){var a=[];for(var k in o){if(!o.hasOwnProperty(k))continue;a.push(encodeURIComponent(k)+"="+encodeURIComponent(o[k]));}return a.join("&");}'."\n"; |
| 637 | - $code .= 'function loadSdk(cb){var P=window[ns];if(P&&P.Buttons){cb(P);return;}var id=ns+"_sdk",tag=document.getElementById(id),done=false;function finish(){if(done)return;done=true;cb(window[ns]&&window[ns].Buttons?window[ns]:null);}function ok(){finish();}function fail(){finish();}if(tag){if(window[ns]&&window[ns].Buttons){finish();return;}if(tag.getAttribute("src")!==s){tag.setAttribute("src",s);}if(tag.readyState==="complete"||tag.readyState==="loaded"){setTimeout(finish,0);return;}tag.addEventListener("load",ok);tag.addEventListener("error",fail);setTimeout(finish,3500);return;}tag=document.createElement("script");tag.id=id;tag.setAttribute("data-namespace",ns);tag.src=s;tag.async=true;tag.onload=ok;tag.onerror=fail;(document.head||document.body||document.documentElement).appendChild(tag);setTimeout(finish,3500);}'."\n"; | |
| 685 | + $code .= 'function loadSdk(cb){var P=window[ns];if(P&&P.Buttons){cb(P);return;}var id=sdkId,tag=document.getElementById(id),done=false;function finish(){if(done)return;done=true;cb(window[ns]&&window[ns].Buttons?window[ns]:null);}function ok(){finish();}function fail(){finish();}if(tag){if(window[ns]&&window[ns].Buttons){finish();return;}if(tag.getAttribute("src")!==s){tag.setAttribute("src",s);}if(tag.readyState==="complete"||tag.readyState==="loaded"){setTimeout(finish,0);return;}tag.addEventListener("load",ok);tag.addEventListener("error",fail);setTimeout(finish,3500);return;}tag=document.createElement("script");tag.id=id;tag.setAttribute("data-namespace",ns);tag.src=s;tag.async=true;tag.onload=ok;tag.onerror=fail;(document.head||document.body||document.documentElement).appendChild(tag);setTimeout(finish,3500);}'."\n"; | |
| 638 | 686 | if($ppco_intent === 'subscription') |
| 639 | 687 | { |
| 640 | - $code .= 'function getPlanId(){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"get_plan_id",s2member_paypal_checkout_t:t})}).then(function(r){return r.json();}).then(function(res){if(res&&res.plan_id)return res.plan_id;throw(res&&res.error?res.error:"plan_get_failed");});}'."\n"; | |
| 688 | + $code .= 'function getPlanId(){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"get_plan_id",s2member_paypal_checkout_t:t})}).then(function(r){return r.json();}).then(function(res){if(res&&res.plan_id)return res.plan_id;throw(res ? (res.error || "plan_get_failed") : "plan_get_failed");});}'."\n"; | |
| 641 | 689 | $code .= 'var planId=null;'."\n"; |
| 642 | 690 | $code .= 'function createSubscription(data,actions){if(planId)return actions.subscription.create({plan_id:planId,custom_id:cid,application_context:{shipping_preference:"NO_SHIPPING"}});return getPlanId().then(function(pid){planId=pid;return actions.subscription.create({plan_id:planId,custom_id:cid,application_context:{shipping_preference:"NO_SHIPPING"}});});}'."\n"; |
| 643 | - $code .= 'function onApprove(data){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"confirm_subscription",s2member_paypal_checkout_t:t,subscription_id:(data&&data.subscriptionID?data.subscriptionID:"")})}).then(function(r){return r.json();}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}throw(res&&res.error?res.error:"subscription_confirm_failed");}).catch(function(e){showErr("Subscription could not be completed. Please try again.");});}'."\n"; | |
| 691 | + //260928.1739 WordPress rendered this inline JS with && inside throw(res&&res.error), breaking the entire PayPal Button script. Use a ternary and || instead. | |
| 692 | + $code .= 'function onApprove(data){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"confirm_subscription",s2member_paypal_checkout_t:t,subscription_id:(data&&data.subscriptionID?data.subscriptionID:"")})}).then(function(r){return r.json();}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}throw(res ? (res.error || "subscription_confirm_failed") : "subscription_confirm_failed");}).catch(function(e){showErr("Subscription could not be completed. Please try again.");});}'."\n"; | |
| 644 | 693 | $code .= 'function onCancel(){showInfo("Subscription cancelled.");}'."\n"; |
| 645 | 694 | $code .= 'function onError(err){var m="PayPal error. Please try again.";try{if(err){if(typeof err==="string")m="PayPal error: "+err;else if(err.message)m="PayPal error: "+err.message;}}catch(x){}showErr(m);}'."\n"; |
| 646 | 695 | $code .= 'function init(){loadSdk(function(P){var el=document.getElementById(d);if(!el){return;}if(el.getAttribute("data-s2m-ppco-rendered")==="1"){return;}if(!P||!P.Buttons){showErr("PayPal SDK failed to load.");return;}el.setAttribute("data-s2m-ppco-rendered","1");try{P.Buttons({fundingSource:P.FUNDING.PAYPAL,style:{layout:"vertical",tagline:false,height:40},createSubscription:createSubscription,onApprove:onApprove,onCancel:onCancel,onError:onError}).render("#"+d);}catch(x){el.removeAttribute("data-s2m-ppco-rendered");showErr("PayPal render failed.");}});}'."\n"; |
| 647 | 696 | } |
| @@ -646,10 +695,13 @@ | ||
| 646 | 695 | $code .= 'function init(){loadSdk(function(P){var el=document.getElementById(d);if(!el){return;}if(el.getAttribute("data-s2m-ppco-rendered")==="1"){return;}if(!P||!P.Buttons){showErr("PayPal SDK failed to load.");return;}el.setAttribute("data-s2m-ppco-rendered","1");try{P.Buttons({fundingSource:P.FUNDING.PAYPAL,style:{layout:"vertical",tagline:false,height:40},createSubscription:createSubscription,onApprove:onApprove,onCancel:onCancel,onError:onError}).render("#"+d);}catch(x){el.removeAttribute("data-s2m-ppco-rendered");showErr("PayPal render failed.");}});}'."\n"; |
| 647 | 696 | } |
| 648 | 697 | else |
| 649 | 698 | { |
| 650 | - $code .= 'function createOrder(){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"create_order",s2member_paypal_checkout_t:t})}).then(function(r){return r.json();}).then(function(res){if(res&&res.order_id)return res.order_id;throw(res&&res.error?res.error:"order_create_failed");});}'."\n"; | |
| 651 | - $code .= 'function onApprove(data){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"capture_order",s2member_paypal_checkout_t:t,order_id:(data&&data.orderID?data.orderID:"")})}).then(function(r){return r.json();}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}throw(res&&res.error?res.error:"order_capture_failed");}).catch(function(e){showErr("Payment could not be completed. Please try again.");});}'."\n"; | |
| 699 | + $code .= 'function request(op,vars){var body={s2member_paypal_checkout_op:op,s2member_paypal_checkout_t:t};for(var k in (vars||{})){if(vars.hasOwnProperty(k))body[k]=vars[k];}return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc(body)}).then(function(r){return r.json();});}'."\n"; | |
| 700 | + $code .= 'function createOrder(){return request("create_order").then(function(res){if(res&&res.order_id)return res.order_id;throw(res ? (res.error || "order_create_failed") : "order_create_failed");});}'."\n"; | |
| 701 | + //260928.1703 A completed PayPal capture can reach the verified webhook even when the browser loses its response. Poll only our durable state and submit its saved signed handoff; never issue a second capture on uncertainty. | |
| 702 | + $code .= 'function recoverOrder(n){return request("get_order_status").then(function(res){if(res&&res.fulfilled&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}if(n>=20)throw "order_confirmation_pending";return new Promise(function(resolve){setTimeout(resolve,1200);}).then(function(){return recoverOrder(n+1);});});}'."\n"; | |
| 703 | + $code .= 'function onApprove(data){var oid=data&&data.orderID?data.orderID:"";return request("capture_order",{order_id:oid}).catch(function(){return {recoverable:true};}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}if(res&&res.recoverable)return recoverOrder(0).catch(function(){showInfo("Payment confirmation is pending. Please do not pay again; check your email or contact support.");});showErr("Payment could not be confirmed. Please contact support before trying again.");});}'."\n"; | |
| 652 | 704 | $code .= 'function onCancel(){showInfo("Payment cancelled.");}'."\n"; |
| 653 | 705 | $code .= 'function onError(err){var m="PayPal error. Please try again.";try{if(err){if(typeof err==="string")m="PayPal error: "+err;else if(err.message)m="PayPal error: "+err.message;}}catch(x){}showErr(m);}'."\n"; |
| 654 | 706 | $code .= 'function init(){loadSdk(function(P){var el=document.getElementById(d);if(!el){return;}if(el.getAttribute("data-s2m-ppco-rendered")==="1"){return;}if(!P||!P.Buttons){showErr("PayPal SDK failed to load.");return;}el.setAttribute("data-s2m-ppco-rendered","1");try{P.Buttons({fundingSource:P.FUNDING.PAYPAL,style:{layout:"vertical",tagline:false,height:40},createOrder:createOrder,onApprove:onApprove,onCancel:onCancel,onError:onError}).render("#"+d);}catch(x){el.removeAttribute("data-s2m-ppco-rendered");showErr("PayPal render failed.");}});}'."\n"; |
| 655 | 707 | } |
| @@ -715,8 +767,16 @@ | ||
| 715 | 767 | $paypal_os1_input_value = /* Current User's IP Address for tracking purposes. */ c_ws_plugin__s2member_utils_ip::current(); |
| 716 | 768 | |
| 717 | 769 | $paypal_invoice_input_value = /* s2Member's Unique Code~IP combo. */ uniqid () . "~" . c_ws_plugin__s2member_utils_ip::current(); |
| 718 | 770 | |
| 771 | + //260928.1515 Give each rendered PayPal Checkout button a signed provisional identity; persist its Gateway Checkout only when the buyer starts, never on page render. | |
| 772 | + $ppco_gateway_checkout_identity = FALSE; | |
| 773 | + if(c_ws_plugin__s2member_paypal_utilities::paypal_checkout_is_enabled()) | |
| 774 | + { | |
| 775 | + $ppco_gateway_checkout_identity = c_ws_plugin__s2member_gateway_checkouts::browser_identity(); | |
| 776 | + $paypal_invoice_input_value = 's2mb-'.(string)$ppco_gateway_checkout_identity['id']; | |
| 777 | + } | |
| 778 | + | |
| 719 | 779 | $attr["desc"] = (!$attr["desc"]) ? $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["level" . $attr["level"] . "_label"] : $attr["desc"]; |
| 720 | 780 | |
| 721 | 781 | // PayPal Checkout: rr=0 with no trial/initial should behave like Buy Now (one-time), |
| 722 | 782 | // so s2Member’s standard Buy Now/EOT routines run (mirrors other gateways). |
| @@ -805,8 +865,15 @@ | ||
| 805 | 865 | |
| 806 | 866 | 'checksum' => md5($paypal_invoice_input_value.c_ws_plugin__s2member_utils_ip::current().$attr["level_ccaps_eotper"]), |
| 807 | 867 | ); |
| 808 | 868 | |
| 869 | + //260928.1515 Bind the PayPal invoice and encrypted purchase terms to the same provisional Gateway Checkout identity, including in anchor/url mode. | |
| 870 | + if($ppco_gateway_checkout_identity) | |
| 871 | + { | |
| 872 | + $ppco_token['gateway_checkout_id'] = (string)$ppco_gateway_checkout_identity['id']; | |
| 873 | + $ppco_token['gateway_checkout_token'] = (string)$ppco_gateway_checkout_identity['token']; | |
| 874 | + } | |
| 875 | + | |
| 809 | 876 | $ppco_token = urlencode(c_ws_plugin__s2member_utils_encryption::encrypt(serialize($ppco_token))); |
| 810 | 877 | |
| 811 | 878 | // output="anchor|url" support (no JS SDK; redirects through s2Member, then to PayPal approval URL). |
| 812 | 879 | if($attr["output"] === "anchor" || $attr["output"] === "url") |
| @@ -869,22 +936,27 @@ | ||
| 869 | 936 | $code .= 'var d="'.esc_js($ppco_div_id).'";'."\n"; |
| 870 | 937 | $code .= 'var e="'.esc_js($ppco_err_id).'";'."\n"; |
| 871 | 938 | $code .= 'var t="'.esc_js($ppco_token).'";'."\n"; |
| 872 | 939 | $code .= 'var u="'.esc_js($ppco_endpoint).'";'."\n"; |
| 940 | + //260928.1739 Bind to the SDK script actually emitted above and encode its URL as JavaScript, not HTML entities. | |
| 873 | 941 | $code .= 'var ns="'.esc_js($ppco_sdk_ns).'";'."\n"; |
| 874 | - $code .= 'var s="'.esc_js($ppco_sdk_src).'";'."\n"; | |
| 942 | + $code .= 'var sdkId="'.esc_js($ppco_sdk_id).'";'."\n"; | |
| 943 | + $code .= 'var s='.wp_json_encode($ppco_sdk_src, JSON_HEX_AMP | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT).';'."\n"; | |
| 875 | 944 | $code .= 'var cid="'.esc_js($paypal_invoice_input_value).'";'."\n"; |
| 876 | 945 | //260819.0042 Keep standalone Checkout feedback consistent with Pro-Forms while distinguishing cancellation from errors. |
| 877 | 946 | $code .= 'function showMsg(m,t){try{var el=document.getElementById(e);if(el){el.style.display="block";el.innerHTML="<span class=\"ws-plugin--s2member-ppco-"+(t==="info"?"info":"error")+"\">"+m+"</span>";}}catch(x){}}function showErr(m){showMsg(m,"error");}function showInfo(m){showMsg(m,"info");}'."\n"; |
| 878 | 947 | $code .= 'function postTo(url, data){var f=document.createElement("form");f.method="post";f.acceptCharset="UTF-8";f.action=url;for(var k in data){if(!data.hasOwnProperty(k))continue;var i=document.createElement("input");i.type="hidden";i.name=k;i.value=data[k];f.appendChild(i);}document.body.appendChild(f);f.submit();}'."\n"; //260817 Keep signed PayPal Checkout returns in UTF-8. |
| 879 | 948 | $code .= 'function enc(o){var a=[];for(var k in o){if(!o.hasOwnProperty(k))continue;a.push(encodeURIComponent(k)+"="+encodeURIComponent(o[k]));}return a.join("&");}'."\n"; |
| 880 | - $code .= 'function loadSdk(cb){var P=window[ns];if(P&&P.Buttons){cb(P);return;}var id=ns+"_sdk",tag=document.getElementById(id),done=false;function finish(){if(done)return;done=true;cb(window[ns]&&window[ns].Buttons?window[ns]:null);}function ok(){finish();}function fail(){finish();}if(tag){if(window[ns]&&window[ns].Buttons){finish();return;}if(tag.getAttribute("src")!==s){tag.setAttribute("src",s);}if(tag.readyState==="complete"||tag.readyState==="loaded"){setTimeout(finish,0);return;}tag.addEventListener("load",ok);tag.addEventListener("error",fail);setTimeout(finish,3500);return;}tag=document.createElement("script");tag.id=id;tag.setAttribute("data-namespace",ns);tag.src=s;tag.async=true;tag.onload=ok;tag.onerror=fail;(document.head||document.body||document.documentElement).appendChild(tag);setTimeout(finish,3500);}'."\n"; | |
| 949 | + $code .= 'function loadSdk(cb){var P=window[ns];if(P&&P.Buttons){cb(P);return;}var id=sdkId,tag=document.getElementById(id),done=false;function finish(){if(done)return;done=true;cb(window[ns]&&window[ns].Buttons?window[ns]:null);}function ok(){finish();}function fail(){finish();}if(tag){if(window[ns]&&window[ns].Buttons){finish();return;}if(tag.getAttribute("src")!==s){tag.setAttribute("src",s);}if(tag.readyState==="complete"||tag.readyState==="loaded"){setTimeout(finish,0);return;}tag.addEventListener("load",ok);tag.addEventListener("error",fail);setTimeout(finish,3500);return;}tag=document.createElement("script");tag.id=id;tag.setAttribute("data-namespace",ns);tag.src=s;tag.async=true;tag.onload=ok;tag.onerror=fail;(document.head||document.body||document.documentElement).appendChild(tag);setTimeout(finish,3500);}'."\n"; | |
| 881 | 950 | if($ppco_intent === 'subscription') |
| 882 | 951 | { |
| 883 | - $code .= 'function getPlanId(){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"get_plan_id",s2member_paypal_checkout_t:t})}).then(function(r){return r.json();}).then(function(res){if(res&&res.plan_id)return res.plan_id;throw(res&&res.error?res.error:"plan_get_failed");});}'."\n"; | |
| 884 | - $code .= 'var planId=null;'."\n"; | |
| 885 | - $code .= 'function createSubscription(data,actions){if(planId)return actions.subscription.create({plan_id:planId,custom_id:cid,application_context:{shipping_preference:"NO_SHIPPING"}});return getPlanId().then(function(pid){planId=pid;return actions.subscription.create({plan_id:planId,custom_id:cid,application_context:{shipping_preference:"NO_SHIPPING"}});});}'."\n"; | |
| 886 | - $code .= 'function onApprove(data){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"confirm_subscription",s2member_paypal_checkout_t:t,subscription_id:(data&&data.subscriptionID?data.subscriptionID:"")})}).then(function(r){return r.json();}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}throw(res&&res.error?res.error:"subscription_confirm_failed");}).catch(function(e){showErr("Subscription could not be completed. Please try again.");});}'."\n"; | |
| 952 | + //260928.1540 Move Framework subscription creation onto the same server-side provider/idempotency path Pro-Forms use. Only the persisted ID reaches the PayPal SDK for buyer approval. | |
| 953 | + $code .= 'function request(op,vars){var body={s2member_paypal_checkout_op:op,s2member_paypal_checkout_t:t};for(var k in (vars||{})){if(vars.hasOwnProperty(k))body[k]=vars[k];}return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc(body)}).then(function(r){return r.json();});}' . "\n"; | |
| 954 | + $code .= 'function waitForSubscription(n){return request("get_subscription_id").then(function(res){if(res&&res.subscription_id)return res.subscription_id;if(n>=20)throw "subscription_create_unresolved";return new Promise(function(resolve){setTimeout(resolve,1000);}).then(function(){return waitForSubscription(n+1);});});}' . "\n"; | |
| 955 | + $code .= 'function createSubscription(){return request("create_subscription").then(function(res){if(res&&res.subscription_id)return res.subscription_id;if(res&&res.recoverable)return waitForSubscription(0);throw(res ? (res.error || "subscription_create_failed") : "subscription_create_failed");});}' . "\n"; | |
| 956 | + //260928.1540 PayPal can report APPROVED before subscription ACTIVATED; poll the same backend until entitlement is confirmed or the activation webhook fulfills off-session. | |
| 957 | + //260928.1739 WordPress rendered this inline JS with && inside throw(res&&res.error), breaking the entire PayPal Button script. Use a ternary and || instead. | |
| 958 | + $code .= 'function onApprove(data){var sid=data&&data.subscriptionID?data.subscriptionID:"";function finish(n){return request("confirm_subscription",{subscription_id:sid}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}if(res&&res.pending_activation&&n<25){return new Promise(function(resolve){setTimeout(resolve,1200);}).then(function(){return finish(n+1);});}throw(res ? (res.error || "subscription_confirm_failed") : "subscription_confirm_failed");});}return finish(0).catch(function(){showErr("Subscription could not be completed. Please try again.");});}' . "\n"; | |
| 887 | 959 | $code .= 'function onCancel(){showInfo("Subscription cancelled.");}'."\n"; |
| 888 | 960 | $code .= 'function onError(err){var m="PayPal error. Please try again.";try{if(err){if(typeof err==="string")m="PayPal error: "+err;else if(err.message)m="PayPal error: "+err.message;}}catch(x){}showErr(m);}'."\n"; |
| 889 | 961 | $code .= 'function init(){loadSdk(function(P){var el=document.getElementById(d);if(!el){return;}if(el.getAttribute("data-s2m-ppco-rendered")==="1"){return;}if(!P||!P.Buttons){showErr("PayPal SDK failed to load.");return;}el.setAttribute("data-s2m-ppco-rendered","1");try{P.Buttons({fundingSource:P.FUNDING.PAYPAL,style:{layout:"vertical",tagline:false,height:40},createSubscription:createSubscription,onApprove:onApprove,onCancel:onCancel,onError:onError}).render("#"+d);}catch(x){el.removeAttribute("data-s2m-ppco-rendered");showErr("PayPal render failed.");}});}'."\n"; |
| 890 | 962 | } |
| @@ -889,10 +961,13 @@ | ||
| 889 | 961 | $code .= 'function init(){loadSdk(function(P){var el=document.getElementById(d);if(!el){return;}if(el.getAttribute("data-s2m-ppco-rendered")==="1"){return;}if(!P||!P.Buttons){showErr("PayPal SDK failed to load.");return;}el.setAttribute("data-s2m-ppco-rendered","1");try{P.Buttons({fundingSource:P.FUNDING.PAYPAL,style:{layout:"vertical",tagline:false,height:40},createSubscription:createSubscription,onApprove:onApprove,onCancel:onCancel,onError:onError}).render("#"+d);}catch(x){el.removeAttribute("data-s2m-ppco-rendered");showErr("PayPal render failed.");}});}'."\n"; |
| 890 | 962 | } |
| 891 | 963 | else |
| 892 | 964 | { |
| 893 | - $code .= 'function createOrder(){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"create_order",s2member_paypal_checkout_t:t})}).then(function(r){return r.json();}).then(function(res){if(res&&res.order_id)return res.order_id;throw(res&&res.error?res.error:"order_create_failed");});}'."\n"; | |
| 894 | - $code .= 'function onApprove(data){return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc({s2member_paypal_checkout_op:"capture_order",s2member_paypal_checkout_t:t,order_id:(data&&data.orderID?data.orderID:"")})}).then(function(r){return r.json();}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}throw(res&&res.error?res.error:"order_capture_failed");}).catch(function(e){showErr("Payment could not be completed. Please try again.");});}'."\n"; | |
| 965 | + $code .= 'function request(op,vars){var body={s2member_paypal_checkout_op:op,s2member_paypal_checkout_t:t};for(var k in (vars||{})){if(vars.hasOwnProperty(k))body[k]=vars[k];}return fetch(u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:enc(body)}).then(function(r){return r.json();});}'."\n"; | |
| 966 | + $code .= 'function createOrder(){return request("create_order").then(function(res){if(res&&res.order_id)return res.order_id;throw(res ? (res.error || "order_create_failed") : "order_create_failed");});}'."\n"; | |
| 967 | + //260928.1703 A completed PayPal capture can reach the verified webhook even when the browser loses its response. Poll only our durable state and submit its saved signed handoff; never issue a second capture on uncertainty. | |
| 968 | + $code .= 'function recoverOrder(n){return request("get_order_status").then(function(res){if(res&&res.fulfilled&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}if(n>=20)throw "order_confirmation_pending";return new Promise(function(resolve){setTimeout(resolve,1200);}).then(function(){return recoverOrder(n+1);});});}'."\n"; | |
| 969 | + $code .= 'function onApprove(data){var oid=data&&data.orderID?data.orderID:"";return request("capture_order",{order_id:oid}).catch(function(){return {recoverable:true};}).then(function(res){if(res&&res.rtn_url&&res.rtn_post){postTo(res.rtn_url,res.rtn_post);return;}if(res&&res.recoverable)return recoverOrder(0).catch(function(){showInfo("Payment confirmation is pending. Please do not pay again; check your email or contact support.");});showErr("Payment could not be confirmed. Please contact support before trying again.");});}'."\n"; | |
| 895 | 970 | $code .= 'function onCancel(){showInfo("Payment cancelled.");}'."\n"; |
| 896 | 971 | $code .= 'function onError(err){var m="PayPal error. Please try again.";try{if(err){if(typeof err==="string")m="PayPal error: "+err;else if(err.message)m="PayPal error: "+err.message;}}catch(x){}showErr(m);}'."\n"; |
| 897 | 972 | $code .= 'function init(){loadSdk(function(P){var el=document.getElementById(d);if(!el){return;}if(el.getAttribute("data-s2m-ppco-rendered")==="1"){return;}if(!P||!P.Buttons){showErr("PayPal SDK failed to load.");return;}el.setAttribute("data-s2m-ppco-rendered","1");try{P.Buttons({fundingSource:P.FUNDING.PAYPAL,style:{layout:"vertical",tagline:false,height:40},createOrder:createOrder,onApprove:onApprove,onCancel:onCancel,onError:onError}).render("#"+d);}catch(x){el.removeAttribute("data-s2m-ppco-rendered");showErr("PayPal render failed.");}});}'."\n"; |
| 898 | 973 | } |