← All changes
|
src/includes/classes/sc-paypal-button-in.inc.php
+90
-20
260917
→
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; |
| @@ -292,8 +296,16 @@ | ||
| 292 | 296 | $paypal_os1_input_value = /* Current User's IP Address for tracking purposes. */ c_ws_plugin__s2member_utils_ip::current(); |
| 293 | 297 | |
| 294 | 298 | $paypal_invoice_input_value = /* s2Member's Unique Code~IP combo. */ uniqid () . "~" . c_ws_plugin__s2member_utils_ip::current(); |
| 295 | 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 | + | |
| 296 | 308 | $attr["sp_ids_exp"] = /* Combined "sp:ids:expiration hours". */ "sp:" . $attr["ids"] . ":" . $attr["exp"]; |
| 297 | 309 | |
| 298 | 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); |
| 299 | 311 | $success_return_url = apply_filters("ws_plugin__s2member_during_sc_paypal_button_success_return_url", $success_return_url, get_defined_vars ()); |
| @@ -351,8 +363,15 @@ | ||
| 351 | 363 | |
| 352 | 364 | 'checksum' => md5($paypal_invoice_input_value.c_ws_plugin__s2member_utils_ip::current().$attr["sp_ids_exp"]), |
| 353 | 365 | ); |
| 354 | 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 | + | |
| 355 | 374 | $ppco_token = urlencode(c_ws_plugin__s2member_utils_encryption::encrypt(serialize($ppco_token))); |
| 356 | 375 | |
| 357 | 376 | // output="anchor|url" support (no JS SDK; redirects through s2Member, then to PayPal approval URL). |
| 358 | 377 | if($attr["output"] === "anchor" || $attr["output"] === "url") |
| @@ -416,22 +435,26 @@ | ||
| 416 | 435 | $code .= 'var d="'.esc_js($ppco_div_id).'";'."\n"; |
| 417 | 436 | $code .= 'var e="'.esc_js($ppco_err_id).'";'."\n"; |
| 418 | 437 | $code .= 'var t="'.esc_js($ppco_token).'";'."\n"; |
| 419 | 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. | |
| 420 | 440 | $code .= 'var ns="'.esc_js($ppco_sdk_ns).'";'."\n"; |
| 421 | - $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"; | |
| 422 | 443 | $code .= 'var cid="'.esc_js($paypal_invoice_input_value).'";'."\n"; |
| 423 | 444 | //260819.0042 Keep standalone Checkout feedback consistent with Pro-Forms while distinguishing cancellation from errors. |
| 424 | 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"; |
| 425 | 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. |
| 426 | 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"; |
| 427 | - $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"; | |
| 428 | 449 | if($ppco_intent === 'subscription') |
| 429 | 450 | { |
| 430 | - $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"; | |
| 431 | 453 | $code .= 'var planId=null;'."\n"; |
| 432 | 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"; |
| 433 | - $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"; | |
| 434 | 457 | $code .= 'function onCancel(){showInfo("Subscription cancelled.");}'."\n"; |
| 435 | 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"; |
| 436 | 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"; |
| 437 | 460 | } |
| @@ -436,10 +459,13 @@ | ||
| 436 | 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"; |
| 437 | 460 | } |
| 438 | 461 | else |
| 439 | 462 | { |
| 440 | - $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"; | |
| 441 | - $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"; | |
| 442 | 468 | $code .= 'function onCancel(){showInfo("Payment cancelled.");}'."\n"; |
| 443 | 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"; |
| 444 | 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"; |
| 445 | 471 | } |
| @@ -505,8 +531,16 @@ | ||
| 505 | 531 | $paypal_os1_input_value = /* Current User's IP Address for tracking purposes. */ c_ws_plugin__s2member_utils_ip::current(); |
| 506 | 532 | |
| 507 | 533 | $paypal_invoice_input_value = /* s2Member's Unique Code~IP combo. */ uniqid () . "~" . c_ws_plugin__s2member_utils_ip::current(); |
| 508 | 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 | + | |
| 509 | 543 | $attr["level_ccaps_eotper"] = ($attr["rr"] === "BN" && $attr["rt"] !== "L") ? $attr["level"] . ":" . $attr["ccaps"] . ":" . $attr["rp"] . " " . $attr["rt"] : $attr["level"] . ":" . $attr["ccaps"]; |
| 510 | 544 | $attr["level_ccaps_eotper"] = /* Clean any trailing separators from this string. */ rtrim ($attr["level_ccaps_eotper"], ":"); |
| 511 | 545 | |
| 512 | 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); |
| @@ -566,8 +600,15 @@ | ||
| 566 | 600 | |
| 567 | 601 | 'checksum' => md5($paypal_invoice_input_value.c_ws_plugin__s2member_utils_ip::current().$attr["level_ccaps_eotper"]), |
| 568 | 602 | ); |
| 569 | 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 | + | |
| 570 | 611 | $ppco_token = urlencode(c_ws_plugin__s2member_utils_encryption::encrypt(serialize($ppco_token))); |
| 571 | 612 | |
| 572 | 613 | // output="anchor|url" support (no JS SDK; redirects through s2Member, then to PayPal approval URL). |
| 573 | 614 | if($attr["output"] === "anchor" || $attr["output"] === "url") |
| @@ -631,22 +672,25 @@ | ||
| 631 | 672 | $code .= 'var d="'.esc_js($ppco_div_id).'";'."\n"; |
| 632 | 673 | $code .= 'var e="'.esc_js($ppco_err_id).'";'."\n"; |
| 633 | 674 | $code .= 'var t="'.esc_js($ppco_token).'";'."\n"; |
| 634 | 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. | |
| 635 | 677 | $code .= 'var ns="'.esc_js($ppco_sdk_ns).'";'."\n"; |
| 636 | - $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"; | |
| 637 | 680 | $code .= 'var cid="'.esc_js($paypal_invoice_input_value).'";'."\n"; |
| 638 | 681 | //260819.0042 Keep standalone Checkout feedback consistent with Pro-Forms while distinguishing cancellation from errors. |
| 639 | 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"; |
| 640 | 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. |
| 641 | 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"; |
| 642 | - $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"; | |
| 643 | 686 | if($ppco_intent === 'subscription') |
| 644 | 687 | { |
| 645 | - $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"; | |
| 646 | 689 | $code .= 'var planId=null;'."\n"; |
| 647 | 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"; |
| 648 | - $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"; | |
| 649 | 693 | $code .= 'function onCancel(){showInfo("Subscription cancelled.");}'."\n"; |
| 650 | 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"; |
| 651 | 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"; |
| 652 | 696 | } |
| @@ -651,10 +695,13 @@ | ||
| 651 | 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"; |
| 652 | 696 | } |
| 653 | 697 | else |
| 654 | 698 | { |
| 655 | - $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"; | |
| 656 | - $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"; | |
| 657 | 704 | $code .= 'function onCancel(){showInfo("Payment cancelled.");}'."\n"; |
| 658 | 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"; |
| 659 | 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"; |
| 660 | 707 | } |
| @@ -720,8 +767,16 @@ | ||
| 720 | 767 | $paypal_os1_input_value = /* Current User's IP Address for tracking purposes. */ c_ws_plugin__s2member_utils_ip::current(); |
| 721 | 768 | |
| 722 | 769 | $paypal_invoice_input_value = /* s2Member's Unique Code~IP combo. */ uniqid () . "~" . c_ws_plugin__s2member_utils_ip::current(); |
| 723 | 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 | + | |
| 724 | 779 | $attr["desc"] = (!$attr["desc"]) ? $GLOBALS["WS_PLUGIN__"]["s2member"]["o"]["level" . $attr["level"] . "_label"] : $attr["desc"]; |
| 725 | 780 | |
| 726 | 781 | // PayPal Checkout: rr=0 with no trial/initial should behave like Buy Now (one-time), |
| 727 | 782 | // so s2Member’s standard Buy Now/EOT routines run (mirrors other gateways). |
| @@ -810,8 +865,15 @@ | ||
| 810 | 865 | |
| 811 | 866 | 'checksum' => md5($paypal_invoice_input_value.c_ws_plugin__s2member_utils_ip::current().$attr["level_ccaps_eotper"]), |
| 812 | 867 | ); |
| 813 | 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 | + | |
| 814 | 876 | $ppco_token = urlencode(c_ws_plugin__s2member_utils_encryption::encrypt(serialize($ppco_token))); |
| 815 | 877 | |
| 816 | 878 | // output="anchor|url" support (no JS SDK; redirects through s2Member, then to PayPal approval URL). |
| 817 | 879 | if($attr["output"] === "anchor" || $attr["output"] === "url") |
| @@ -874,22 +936,27 @@ | ||
| 874 | 936 | $code .= 'var d="'.esc_js($ppco_div_id).'";'."\n"; |
| 875 | 937 | $code .= 'var e="'.esc_js($ppco_err_id).'";'."\n"; |
| 876 | 938 | $code .= 'var t="'.esc_js($ppco_token).'";'."\n"; |
| 877 | 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. | |
| 878 | 941 | $code .= 'var ns="'.esc_js($ppco_sdk_ns).'";'."\n"; |
| 879 | - $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"; | |
| 880 | 944 | $code .= 'var cid="'.esc_js($paypal_invoice_input_value).'";'."\n"; |
| 881 | 945 | //260819.0042 Keep standalone Checkout feedback consistent with Pro-Forms while distinguishing cancellation from errors. |
| 882 | 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"; |
| 883 | 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. |
| 884 | 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"; |
| 885 | - $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"; | |
| 886 | 950 | if($ppco_intent === 'subscription') |
| 887 | 951 | { |
| 888 | - $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"; | |
| 889 | - $code .= 'var planId=null;'."\n"; | |
| 890 | - $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"; | |
| 891 | - $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"; | |
| 892 | 959 | $code .= 'function onCancel(){showInfo("Subscription cancelled.");}'."\n"; |
| 893 | 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"; |
| 894 | 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"; |
| 895 | 962 | } |
| @@ -894,10 +961,13 @@ | ||
| 894 | 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"; |
| 895 | 962 | } |
| 896 | 963 | else |
| 897 | 964 | { |
| 898 | - $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"; | |
| 899 | - $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"; | |
| 900 | 970 | $code .= 'function onCancel(){showInfo("Payment cancelled.");}'."\n"; |
| 901 | 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"; |
| 902 | 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"; |
| 903 | 973 | } |