| @@ -189,8 +189,9 @@ | ||
| 189 | 189 | // Do not guess term values in fallback vars (risk of incorrect EOT decisions). |
| 190 | 190 | if (!empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['ipn_signup_vars_fallback'])) { |
| 191 | 191 | $userdata = get_userdata((int)$user_id); |
| 192 | 192 | $ipn_signup_vars = array( |
| 193 | + 's2member_ipn_signup_vars_fallback' => '1', //260829.1709 Mark generated fallback vars explicitly so gateway-aware EOT logic can distinguish missing local signup metadata from malformed stored signup data. | |
| 193 | 194 | 'subscr_cid' => !empty($_subscr_cid) ? $_subscr_cid : '', |
| 194 | 195 | 'subscr_id' => $_subscr_id, |
| 195 | 196 | 'custom' => esc_html($_SERVER["HTTP_HOST"]), |
| 196 | 197 | 'period1' => '', |
| @@ -465,21 +466,28 @@ | ||
| 465 | 466 | return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', |
| 466 | 467 | 'debug' => 'This is a fixed EOT time recorded by s2Member. It can be altered in the WordPress Dashboard for this user.'); |
| 467 | 468 | |
| 468 | 469 | if(!$subscr_gateway && !$subscr_id && !$subscr_cid && $last_auto_eot_time // EOTd? |
| 469 | - && (!user_can($user->ID, 'access_s2member_level1') || c_ws_plugin__s2member_user_access::user_access_role($user) === $demotion_role) | |
| 470 | + //260916.1840 A preserved unrelated role may sort before the destination role, so inspect all assigned roles instead of only s2Member's historical first-role result. | |
| 471 | + && (!user_can($user->ID, 'access_s2member_level1') || in_array($demotion_role, (array)$user->roles, TRUE)) | |
| 470 | 472 | && !c_ws_plugin__s2member_user_access::user_access_ccaps($user) // And no CCAPs either? |
| 471 | 473 | ) return array('type' => 'fixed', 'time' => $last_auto_eot_time, 'tense' => $last_auto_eot_time <= $now ? 'past' : 'future', |
| 472 | - 'debug' => 'This is an archived/fixed EOT time recorded by s2Member; i.e., the date this customer\'s access expired.'); | |
| 474 | + 'debug' => 'This is an archived/fixed EOT time recorded by s2Member; i.e., the date this customer\'s membership EOT was processed.'); | |
| 473 | 475 | |
| 474 | 476 | if(!$subscr_gateway || !$subscr_id || !is_array($ipn_signup_vars) || !$ipn_signup_vars) |
| 475 | 477 | return array_merge($empty_response, array('debug' => 'This user has no subscription; i.e., missing `subscr_id`, `subscr_gateway` or `ipn_signup_vars`.')); |
| 476 | 478 | |
| 477 | - if(empty($ipn_signup_vars['txn_type']) || $ipn_signup_vars['txn_type'] !== 'subscr_signup') | |
| 479 | + $ipn_signup_vars_fallback = !empty($ipn_signup_vars['s2member_ipn_signup_vars_fallback']); | |
| 480 | + //260829.1709 A PayPal fallback still has a bound local subscription ID, so allow the configured PayPal APIs to recover authoritative timing before rejecting it for missing legacy signup metadata. | |
| 481 | + $can_recover_paypal_fallback = ($check_gateway && $subscr_gateway === 'paypal' && $ipn_signup_vars_fallback); | |
| 482 | + | |
| 483 | + if((empty($ipn_signup_vars['txn_type']) || $ipn_signup_vars['txn_type'] !== 'subscr_signup') && !$can_recover_paypal_fallback) | |
| 478 | 484 | return array_merge($empty_response, array('debug' => 'This user has no subscription; i.e., `txn_type` != `subscr_signup`.')); |
| 479 | 485 | |
| 480 | - $auto_eot_time // Update this now; i.e., build a new EOT time based on IPN signup vars. | |
| 481 | - = c_ws_plugin__s2member_utils_time::auto_eot_time($user->ID, $ipn_signup_vars['period1'], $ipn_signup_vars['period3']); | |
| 486 | + //260829.1709 Never turn deliberately blank fallback billing terms into an estimated EOT of "now"; PayPal API recovery below must succeed or the existing empty/fallback behavior remains. | |
| 487 | + $auto_eot_time = ($ipn_signup_vars_fallback && empty($ipn_signup_vars['period3'])) | |
| 488 | + ? 0 | |
| 489 | + : c_ws_plugin__s2member_utils_time::auto_eot_time($user->ID, $ipn_signup_vars['period1'], $ipn_signup_vars['period3']); | |
| 482 | 490 | |
| 483 | 491 | //260828.0710 !!! TO-DO: Track a gateway-independent `s2member_access_through_time`, meaning the end of the latest confirmed entitlement period, before EOT grace. |
| 484 | 492 | // 1. On signup/received payment, save the provider's then-current next billing/period-end time as access-through. Failed payments never advance it. 2. Cancellation/EOT should use this value when available, even if already past. |
| 485 | 493 | // 3. For older users without it, estimate conservatively from provider billing schedule + signup vars/last successful payment when reliable; otherwise keep the existing estimated EOT fallback. |
| @@ -523,11 +531,16 @@ | ||
| 523 | 531 | $status = !empty($subscription['status']) ? strtoupper((string)$subscription['status']) : ''; |
| 524 | 532 | $next = !empty($subscription['billing_info']['next_billing_time']) ? (string)$subscription['billing_info']['next_billing_time'] : ''; |
| 525 | 533 | |
| 526 | 534 | if($status && $status !== 'ACTIVE') |
| 527 | - return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 528 | - 'debug' => 'This is the estimated EOT time. PayPal Checkout says this subscription is no longer active, and thus, access should be terminated at this time.'); | |
| 535 | + { | |
| 536 | + if($auto_eot_time) | |
| 537 | + return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 538 | + 'debug' => 'This is the estimated EOT time. PayPal Checkout says this subscription is no longer active, and thus, access should be terminated at this time.'); | |
| 529 | 539 | |
| 540 | + return array_merge($empty_response, array('debug' => 'PayPal Checkout says this subscription is no longer active, but local signup billing terms are unavailable; refusing to guess an EOT.')); | |
| 541 | + } | |
| 542 | + | |
| 530 | 543 | if($next && ($time = strtotime($next)) > $now) |
| 531 | 544 | return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future', |
| 532 | 545 | 'debug' => 'PayPal Checkout says this is the next payment time.'); |
| 533 | 546 | |
| @@ -545,15 +558,25 @@ | ||
| 545 | 558 | if(preg_match('/^(?:Pending|PendingProfile)$/i', $api_response['STATUS'])) |
| 546 | 559 | return array_merge($empty_response, array('debug' => 'No fixed EOT, and the PayPal Pro API says the subscription for this user is currently pending changes. Unable to determine at this moment. Please try again in 15 minutes.')); |
| 547 | 560 | |
| 548 | 561 | if(!preg_match('/^(?:Active|ActiveProfile)$/i', $api_response['STATUS'])) |
| 549 | - return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 550 | - 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription is no longer active, and thus, access should be terminated at this time.'); | |
| 562 | + { | |
| 563 | + if($auto_eot_time) | |
| 564 | + return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 565 | + 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription is no longer active, and thus, access should be terminated at this time.'); | |
| 551 | 566 | |
| 567 | + return array_merge($empty_response, array('debug' => 'The PayPal Pro API says this subscription is no longer active, but local signup billing terms are unavailable; refusing to guess an EOT.')); | |
| 568 | + } | |
| 569 | + | |
| 552 | 570 | if($api_response['TERM'] > 0 && $api_response['PAYMENTSLEFT'] <= 0) |
| 553 | - return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 554 | - 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription has reached its last payment, and thus, access should be terminated at this time.'); | |
| 571 | + { | |
| 572 | + if($auto_eot_time) | |
| 573 | + return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 574 | + 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription has reached its last payment, and thus, access should be terminated at this time.'); | |
| 555 | 575 | |
| 576 | + return array_merge($empty_response, array('debug' => 'The PayPal Pro API says this subscription has reached its last payment, but local signup billing terms are unavailable; refusing to guess an EOT.')); | |
| 577 | + } | |
| 578 | + | |
| 556 | 579 | if($api_response['TERM'] <= 0 || $api_response['PAYMENTSLEFT'] > 0) |
| 557 | 580 | if($api_response['NEXTPAYMENT'] && strlen($api_response['NEXTPAYMENT']) === 8) // MMDDYYYY format is not `strtotime()` compatible. |
| 558 | 581 | if(($time = strtotime(substr($api_response['NEXTPAYMENT'], -4).'-'.substr($api_response['NEXTPAYMENT'], 0, 2).'-'.substr($api_response['NEXTPAYMENT'], 2, 2))) > $now) |
| 559 | 582 | return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future', |
| @@ -575,14 +598,24 @@ | ||
| 575 | 598 | if(preg_match('/^(?:Pending|PendingProfile)$/i', $api_response['STATUS'])) |
| 576 | 599 | return array_merge($empty_response, array('debug' => 'No fixed EOT, and the PayPal Pro API says the subscription for this user is currently pending changes. Unable to determine at this moment. Please try again in 15 minutes.')); |
| 577 | 600 | |
| 578 | 601 | if(!preg_match('/^(?:Active|ActiveProfile)$/i', $api_response['STATUS'])) |
| 579 | - return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 580 | - 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription is no longer active, and thus, access should be terminated at this time.'); | |
| 602 | + { | |
| 603 | + if($auto_eot_time) | |
| 604 | + return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 605 | + 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription is no longer active, and thus, access should be terminated at this time.'); | |
| 581 | 606 | |
| 607 | + return array_merge($empty_response, array('debug' => 'The PayPal Pro API says this subscription is no longer active, but local signup billing terms are unavailable; refusing to guess an EOT.')); | |
| 608 | + } | |
| 609 | + | |
| 582 | 610 | if($api_response['TOTALBILLINGCYCLES'] > 0 && $api_response['NUMCYCLESREMAINING'] <= 0) |
| 583 | - return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 584 | - 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription has reached its last payment, and thus, access should be terminated at this time.'); | |
| 611 | + { | |
| 612 | + if($auto_eot_time) | |
| 613 | + return array('type' => 'fixed', 'time' => $auto_eot_time, 'tense' => $auto_eot_time <= $now ? 'past' : 'future', | |
| 614 | + 'debug' => 'This is the estimated EOT time. The PayPal Pro API says this subscription has reached its last payment, and thus, access should be terminated at this time.'); | |
| 615 | + | |
| 616 | + return array_merge($empty_response, array('debug' => 'The PayPal Pro API says this subscription has reached its last payment, but local signup billing terms are unavailable; refusing to guess an EOT.')); | |
| 617 | + } | |
| 585 | 618 | |
| 586 | 619 | if($api_response['TOTALBILLINGCYCLES'] <= 0 || $api_response['NUMCYCLESREMAINING'] > 0) |
| 587 | 620 | if($api_response['NEXTBILLINGDATE'] && ($time = strtotime($api_response['NEXTBILLINGDATE'])) > $now) |
| 588 | 621 | return array('type' => 'next', 'time' => $time, 'tense' => $time <= $now ? 'past' : 'future', |