PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.6.1
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.6.1
1.6.1 1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
← All changes | inc/admin/notices.php +358 -43 1.2.0 → 1.6.1 View file →
@@ -5,16 +5,20 @@
5 5 * Registers SureDonation's engagement admin notices:
6 6 *
7 7 * - Review notice (donation) : shown once the site has at least one live
8 8 * donation, after the 3-day install grace.
9 - * - Review notice (gateway) : shown when a payment gateway is connected but
10 - * no live donation exists yet, after the 3-day
11 - * install grace.
9 + * - Test mode notice : shown when a payment gateway is connected but
10 + * the site is still in test mode, nudging the
11 + * admin to switch to live mode.
12 + * - Review notice (gateway) : shown when a payment gateway is connected in
13 + * live mode but no live donation exists yet,
14 + * after the 3-day install grace.
12 15 * - Setup gateway notice : shown instantly (no grace) when no payment
13 16 * gateway is connected at all.
14 17 *
15 - * The three notices are mutually exclusive by construction. Priority is:
16 - * live donation > gateway configured > no gateway.
18 + * The four notices are mutually exclusive by construction. Priority is:
19 + * live donation > gateway configured (test mode) > gateway configured (live
20 + * mode) > no gateway.
17 21 *
18 22 * @package SureDonation
19 23 */
20 24
@@ -21,8 +25,9 @@
21 25 namespace SureDonation\Inc\Admin;
22 26
23 27 use SureDonation\Inc\Database\Tables\Donations;
24 28 use SureDonation\Inc\Helper;
29 +use SureDonation\Inc\Payments\Payment_Helper;
25 30 use SureDonation\Inc\Payments\PayPal\PayPal_Helper;
26 31 use SureDonation\Inc\Payments\Stripe\Stripe_Helper;
27 32
28 33 // Exit if accessed directly.
@@ -89,14 +94,17 @@
89 94 require_once SUREDONATION_DIR . 'inc/lib/astra-notices/class-bsf-admin-notices.php';
90 95 }
91 96
92 97 add_action( 'admin_notices', [ $this, 'display_review_notice_donation' ] );
98 + add_action( 'admin_notices', [ $this, 'display_test_mode_notice' ] );
93 99 add_action( 'admin_notices', [ $this, 'display_review_notice_gateway' ] );
94 100 add_action( 'admin_notices', [ $this, 'display_setup_gateway_notice' ] );
101 + add_action( 'admin_notices', [ $this, 'display_webhook_notice' ] );
102 + add_action( 'admin_notices', [ $this, 'display_paypal_reconnect_notice' ] );
95 103
96 - // Load the setup-notice styles from the admin <head> (not the late
104 + // Load the banner-notice styles from the admin <head> (not the late
97 105 // after-markup hook) so the banner never renders unstyled first.
98 - add_action( 'admin_enqueue_scripts', [ $this, 'maybe_enqueue_setup_notice_style' ] );
106 + add_action( 'admin_enqueue_scripts', [ $this, 'maybe_enqueue_banner_notice_style' ] );
99 107
100 108 add_action( 'wp_ajax_suredonation_notice_response', [ $this, 'handle_notice_response' ] );
101 109 }
102 110
@@ -146,9 +154,11 @@
146 154 WEEK_IN_SECONDS,
147 155 true
148 156 ),
149 157 'repeat-notice-after' => WEEK_IN_SECONDS,
150 - 'show_if' => $this->is_three_days_elapsed() && $this->has_live_donation(),
158 + // Test-mode takes priority: while the site is in test mode the
159 + // "switch to live" warning is more useful than a review ask.
160 + 'show_if' => $this->is_three_days_elapsed() && $this->has_live_donation() && ! $this->should_show_test_mode_notice(),
151 161 'display-with-other-notices' => true,
152 162 ]
153 163 );
154 164
@@ -155,8 +165,92 @@
155 165 add_action( 'astra_notice_after_markup_sd-review-donation', [ $this, 'enqueue_notice_response_script' ] );
156 166 }
157 167
158 168 /**
169 + * Test-mode notice shown when a gateway is connected but the site is still
170 + * in test mode (notice A2).
171 + *
172 + * Nudges the admin to switch to live mode so real donations can be
173 + * accepted. Takes priority over the gateway review notice: there is no
174 + * point asking for a review while the site cannot yet take real money.
175 + *
176 + * @return void
177 + * @since 1.3.0
178 + */
179 + public function display_test_mode_notice() {
180 + if ( ! Helper::current_user_can() ) {
181 + return;
182 + }
183 +
184 + if ( ! apply_filters( 'suredonation_show_test_mode_notice', true ) ) {
185 + return;
186 + }
187 +
188 + if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
189 + return;
190 + }
191 +
192 + \BSF_Admin_Notices::add_notice(
193 + [
194 + 'id' => 'sd-test-mode',
195 + 'type' => '',
196 + 'message' => $this->build_test_mode_notice_markup(),
197 + 'repeat-notice-after' => WEEK_IN_SECONDS,
198 + 'show_if' => $this->should_show_test_mode_notice() && ! $this->should_show_webhook_notice(),
199 + 'display-with-other-notices' => true,
200 + ]
201 + );
202 +
203 + add_action( 'astra_notice_after_markup_sd-test-mode', [ $this, 'enqueue_notice_response_script' ] );
204 + }
205 +
206 + /**
207 + * Stripe webhook-not-configured notice.
208 + *
209 + * Shown on wp-admin pages when Stripe is connected but its webhook is not
210 + * configured for the current mode, so donation/subscription statuses may not
211 + * sync. Mirrors the SureForms webhook notice: a standard dismissible core
212 + * notice (dismissal is per-page-load and reappears until the webhook is set).
213 + * Takes priority over the test-mode banner, which is suppressed while this
214 + * shows (matching the React dashboard notice chain).
215 + *
216 + * Hooked - admin_notices
217 + *
218 + * @return void
219 + * @since 1.3.0
220 + */
221 + public function display_webhook_notice() {
222 + if ( ! Helper::current_user_can() ) {
223 + return;
224 + }
225 +
226 + if ( ! $this->should_show_webhook_notice() ) {
227 + return;
228 + }
229 +
230 + // Load the analytics tracker so the configure-click and dismiss are
231 + // recorded, matching the other notices (see handle_notice_response()).
232 + $this->enqueue_notice_response_script();
233 + ?>
234 + <div id="sd-webhook-not-configured" class="notice notice-error is-dismissible">
235 + <p>
236 + <?php
237 + printf(
238 + /* translators: %1$s: link to configure the Stripe webhook */
239 + esc_html__( 'Webhooks keep SureDonation in sync with Stripe by automatically updating donation and subscription data. Please %1$s the webhook.', 'suredonation' ),
240 + sprintf(
241 + '<a class="sd-notice-cta" href="%1$s">%2$s</a>',
242 + esc_url( Payment_Helper::get_settings_url( 'stripe' ) ),
243 + esc_html__( 'configure', 'suredonation' )
244 + )
245 + );
246 + ?>
247 + </p>
248 + </div>
249 + <?php
250 + }
251 +
252 + /**
159 253 * Review notice shown when a gateway is configured but there are no live
160 254 * donations yet (notice B).
161 255 *
162 256 * @return void
@@ -189,9 +283,11 @@
189 283 WEEK_IN_SECONDS,
190 284 true
191 285 ),
192 286 'repeat-notice-after' => WEEK_IN_SECONDS,
193 - 'show_if' => $this->is_three_days_elapsed() && ! $this->has_live_donation() && $this->is_gateway_configured(),
287 + // In test mode the test-mode notice takes this slot instead,
288 + // keeping the notice chain mutually exclusive.
289 + 'show_if' => $this->is_three_days_elapsed() && ! $this->has_live_donation() && $this->is_gateway_configured() && ! $this->should_show_test_mode_notice(),
194 290 'display-with-other-notices' => true,
195 291 ]
196 292 );
197 293
@@ -223,9 +319,9 @@
223 319 'id' => 'sd-setup-gateway',
224 320 'type' => '',
225 321 'message' => $this->build_setup_notice_markup(),
226 322 'repeat-notice-after' => WEEK_IN_SECONDS,
227 - 'show_if' => ! $this->has_live_donation() && ! $this->is_gateway_configured(),
323 + 'show_if' => $this->should_show_setup_gateway_notice(),
228 324 'display-with-other-notices' => true,
229 325 ]
230 326 );
231 327
@@ -285,23 +381,36 @@
285 381 $notice_id = isset( $_POST['notice_id'] ) ? sanitize_text_field( wp_unslash( $_POST['notice_id'] ) ) : '';
286 382 $button = isset( $_POST['button'] ) ? sanitize_text_field( wp_unslash( $_POST['button'] ) ) : '';
287 383
288 384 $valid = [
289 - 'sd-review-donation' => [
385 + 'sd-review-donation' => [
290 386 'rate_suredonation' => 'review_notice_donation_cta',
291 387 'maybe_later' => 'review_notice_donation_snooze',
292 388 'dismissed' => 'review_notice_donation_dismiss',
293 389 ],
294 - 'sd-review-gateway' => [
390 + 'sd-test-mode' => [
391 + 'switch_to_live' => 'test_mode_notice_cta',
392 + 'maybe_later' => 'test_mode_notice_snooze',
393 + 'dismissed' => 'test_mode_notice_dismiss',
394 + ],
395 + 'sd-review-gateway' => [
295 396 'rate_suredonation' => 'review_notice_gateway_cta',
296 397 'maybe_later' => 'review_notice_gateway_snooze',
297 398 'dismissed' => 'review_notice_gateway_dismiss',
298 399 ],
299 - 'sd-setup-gateway' => [
400 + 'sd-setup-gateway' => [
300 401 'configure_gateway' => 'setup_gateway_notice_cta',
301 402 'maybe_later' => 'setup_gateway_notice_snooze',
302 403 'dismissed' => 'setup_gateway_notice_dismiss',
303 404 ],
405 + 'sd-paypal-reconnect' => [
406 + 'paypal_reconnect_notice_cta' => 'paypal_reconnect_notice_cta',
407 + 'paypal_reconnect_notice_dismiss' => 'paypal_reconnect_notice_dismiss',
408 + ],
409 + 'sd-webhook-not-configured' => [
410 + 'configure_webhook' => 'webhook_notice_cta',
411 + 'dismissed' => 'webhook_notice_dismiss',
412 + ],
304 413 ];
305 414
306 415 if ( ! isset( $valid[ $notice_id ][ $button ] ) ) {
307 416 wp_send_json_error( [ 'message' => __( 'Invalid parameters.', 'suredonation' ) ], 400 );
@@ -379,39 +488,209 @@
379 488 /**
380 489 * Build the markup for the "configure a payment gateway" setup notice
381 490 * (notice C).
382 491 *
383 - * This notice uses a dedicated banner layout (accent bar, icon, heading,
384 - * body, primary CTA and a right-side illustration) styled via
385 - * setup-gateway-notice.css, rather than the shared review-notice markup.
386 - *
387 492 * @return string The notice HTML markup.
388 493 * @since 1.2.0
389 494 */
390 495 private function build_setup_notice_markup() {
496 + return $this->build_banner_notice_markup(
497 + esc_html__( 'Your donation site is almost ready!', 'suredonation' ),
498 + esc_html__( 'Connect Stripe or PayPal and you can start accepting donations today. It takes a few minutes.', 'suredonation' ),
499 + Payment_Helper::get_settings_url( 'stripe' ),
500 + esc_html__( 'Connect Stripe or PayPal', 'suredonation' ),
501 + SUREDONATION_URL . 'images/payment-gateway-notice.png'
502 + );
503 + }
504 +
505 + /**
506 + * PayPal needs reconnecting in live mode notice.
507 + *
508 + * The partner client id used to be stored without a mode, so on a site that
509 + * connected both test and live the last connect overwrote the other's value.
510 + * A site left holding the sandbox id renders the sandbox SDK against a live
511 + * merchant, PayPal refuses to reconcile that pairing, and no live donation
512 + * can be approved at all — silently, since the donor never reaches approval
513 + * and nothing is logged.
514 + *
515 + * The stored value is an opaque string with nothing distinguishing sandbox
516 + * from production, so the environment cannot be recovered after the fact.
517 + * Reconnecting in live mode writes the mode-specific value and settles it,
518 + * which is all this notice asks for.
519 + *
520 + * Hooked - admin_notices
521 + *
522 + * @return void
523 + * @since 1.5.1
524 + */
525 + public function display_paypal_reconnect_notice() {
526 + if ( ! Helper::current_user_can() ) {
527 + return;
528 + }
529 +
530 + if ( ! $this->should_show_paypal_reconnect_notice() ) {
531 + return;
532 + }
533 +
534 + $this->enqueue_notice_response_script();
535 + ?>
536 + <div id="sd-paypal-reconnect" class="notice notice-error is-dismissible">
537 + <p>
538 + <?php
539 + printf(
540 + /* translators: %1$s: link to the PayPal payment settings */
541 + esc_html__( 'PayPal needs reconnecting before it can take live donations on this site. Test and live are separate PayPal connections, and an earlier version stored one of them over the other. Please %1$s while in live mode. It takes a moment and no settings are lost.', 'suredonation' ),
542 + sprintf(
543 + '<a class="sd-notice-cta" href="%1$s">%2$s</a>',
544 + esc_url( Payment_Helper::get_settings_url( 'paypal' ) ),
545 + esc_html__( 'reconnect PayPal', 'suredonation' )
546 + )
547 + );
548 + ?>
549 + </p>
550 + </div>
551 + <?php
552 + }
553 +
554 + /**
555 + * Whether the PayPal reconnect notice is eligible to show.
556 + *
557 + * Scoped as tightly as the stored data allows:
558 + *
559 + * - **Both modes connected.** The only state where the stored value is
560 + * ambiguous. A live-only site's value must be the production id, because
561 + * nothing else could have written it; a test-only site takes no live
562 + * donations either way.
563 + * - **The legacy key is still in use for live.** Keyed on the live value
564 + * rather than on both being empty: reconnecting test first would otherwise
565 + * clear the notice while live — the mode that handles real money — was
566 + * still loading a possibly-sandbox id.
567 + * - **No completed live PayPal donation.** One is proof the stored value is
568 + * the production id, so there is nothing to fix. (A site whose only live
569 + * PayPal donation was later refunded no longer counts here and would be
570 + * asked to reconnect unnecessarily — one wasted reconnect, against
571 + * silently losing every live donation.)
572 + *
573 + * A site that connected test and then live, so its last connect wrote the
574 + * correct value, and which has not taken a live PayPal donation yet, is
575 + * asked to reconnect when it does not need to. That case cannot be told
576 + * apart from a broken one — which is exactly why the mode is not inferred.
577 + *
578 + * @return bool
579 + * @since 1.5.1
580 + */
581 + private function should_show_paypal_reconnect_notice() {
582 + if ( ! PayPal_Helper::is_paypal_connected( 'live' ) || ! PayPal_Helper::is_paypal_connected( 'test' ) ) {
583 + return false;
584 + }
585 +
586 + $settings = PayPal_Helper::get_all_paypal_settings();
587 +
588 + $legacy_client_id = isset( $settings['partner_client_id'] ) && is_string( $settings['partner_client_id'] )
589 + ? $settings['partner_client_id']
590 + : '';
591 +
592 + if ( '' === $legacy_client_id ) {
593 + return false;
594 + }
595 +
596 + $live_client_id = isset( $settings['partner_client_id_live'] ) && is_string( $settings['partner_client_id_live'] )
597 + ? $settings['partner_client_id_live']
598 + : '';
599 +
600 + if ( '' !== $live_client_id ) {
601 + return false;
602 + }
603 +
604 + // Cached like has_live_donation(): once a live PayPal donation exists the
605 + // answer can never go back, so the count runs at most once per site.
606 + if ( get_option( 'suredonation_has_live_paypal_donation' ) ) {
607 + return false;
608 + }
609 +
610 + if ( Donations::count_live_completed( 'paypal' ) >= 1 ) {
611 + update_option( 'suredonation_has_live_paypal_donation', 1, false );
612 +
613 + return false;
614 + }
615 +
616 + return true;
617 + }
618 +
619 + /**
620 + * Build the markup for the "switch to live mode" test-mode notice
621 + * (notice A2).
622 + *
623 + * @return string The notice HTML markup.
624 + * @since 1.3.0
625 + */
626 + private function build_test_mode_notice_markup() {
627 + // The PayPal sandbox requirement is deliberately not here. It is gateway
628 + // advice rather than something about the site being in test mode, and it
629 + // belongs beside the mode control in payment settings, where a merchant
630 + // is choosing the mode rather than being told about it.
631 + $text = esc_html__( 'Supporters cannot donate while your site is in test mode. Anything they try now is a test and no money reaches you. Switch to live mode when you are ready to accept real donations.', 'suredonation' );
632 +
633 + return $this->build_banner_notice_markup(
634 + esc_html__( 'SureDonation is in test mode', 'suredonation' ),
635 + $text,
636 + Payment_Helper::get_settings_url(),
637 + esc_html__( 'Switch to Live Mode', 'suredonation' )
638 + );
639 + }
640 +
641 + /**
642 + * Build the shared banner-notice markup (accent bar, icon, heading, body and
643 + * a primary CTA, with an optional right-side illustration).
644 + *
645 + * Shared by the setup-gateway and test-mode notices; each is scoped by its
646 + * wrapper id (#sd-setup-gateway / #sd-test-mode) in setup-gateway-notice.css
647 + * so they can carry different accent colors from the same template.
648 + *
649 + * The text parameters must be pre-escaped by the caller (e.g. via
650 + * esc_html__()); the URL and art path are escaped here.
651 + *
652 + * @param string $title The notice heading (pre-escaped).
653 + * @param string $text The notice body text (pre-escaped).
654 + * @param string $cta_url The primary CTA URL (raw; escaped here).
655 + * @param string $cta_text The primary CTA button text (pre-escaped).
656 + * @param string $art Optional right-side illustration URL (raw; escaped
657 + * here). When empty, the banner drops the reserved
658 + * art space via the --no-art modifier.
659 + * @return string The notice HTML markup.
660 + * @since 1.3.0
661 + */
662 + private function build_banner_notice_markup( $title, $text, $cta_url, $cta_text, $art = '' ) {
663 + $has_art = '' !== $art;
664 + $notice_class = $has_art ? 'sd-setup-notice' : 'sd-setup-notice sd-setup-notice--no-art';
665 + $art_markup = $has_art
666 + ? sprintf( '<img class="sd-setup-notice__art" src="%s" alt="" width="187" height="128" />', esc_url( $art ) )
667 + : '';
668 +
391 669 return sprintf(
392 - '<div class="sd-setup-notice">
670 + '<div class="%1$s">
393 671 <div class="sd-setup-notice__main">
394 - <img class="sd-setup-notice__icon" src="%5$s" alt="" width="28" height="28" />
672 + <img class="sd-setup-notice__icon" src="%2$s" alt="" width="28" height="28" />
395 673 <div class="sd-setup-notice__body">
396 - <h2 class="sd-setup-notice__title">%1$s</h2>
397 - <p class="sd-setup-notice__text">%2$s</p>
398 - <a href="%3$s" class="button button-primary sd-setup-notice__button">%4$s</a>
674 + <h2 class="sd-setup-notice__title">%3$s</h2>
675 + <p class="sd-setup-notice__text">%4$s</p>
676 + <a href="%5$s" class="button button-primary sd-setup-notice__button">%6$s</a>
399 677 </div>
400 678 </div>
401 - <img class="sd-setup-notice__art" src="%6$s" alt="" width="187" height="128" />
679 + %7$s
402 680 </div>',
403 - esc_html__( 'Your donation site is almost ready!', 'suredonation' ),
404 - esc_html__( 'Connect a payment gateway to start accepting donations. Set up Stripe or PayPal in just a few clicks to go live.', 'suredonation' ),
405 - esc_url( admin_url( 'admin.php?page=suredonation#/settings?tab=payments&subpage=stripe' ) ),
406 - esc_html__( 'Configure Payment Gateway', 'suredonation' ),
681 + esc_attr( $notice_class ),
407 682 esc_url( SUREDONATION_URL . 'images/suredonation-icon.svg' ),
408 - esc_url( SUREDONATION_URL . 'images/payment-gateway-notice.png' )
683 + $title,
684 + $text,
685 + esc_url( $cta_url ),
686 + $cta_text,
687 + $art_markup
409 688 );
410 689 }
411 690
412 691 /**
413 - * Enqueue the setup-notice stylesheet.
692 + * Enqueue the banner-notice stylesheet.
414 693 *
415 694 * @return void
416 695 * @since 1.2.0
417 696 */
@@ -428,14 +707,14 @@
428 707 );
429 708 }
430 709
431 710 /**
432 - * Enqueue the setup-notice stylesheet from the admin <head> when the setup
433 - * notice is eligible to show.
711 + * Enqueue the banner-notice stylesheet from the admin <head> when either
712 + * banner notice (setup-gateway or test-mode) is eligible to show.
434 713 *
435 714 * Hooked on admin_enqueue_scripts (which runs before admin_head) and gated
436 - * by the same conditions as the notice itself, so the stylesheet is in the
437 - * page head before the banner paints. This avoids the flash of unstyled
715 + * by the same conditions as the notices themselves, so the stylesheet is in
716 + * the page head before the banner paints. This avoids the flash of unstyled
438 717 * content that occurred when the CSS was enqueued on the notice's
439 718 * after-markup hook (which fires at admin_notices priority 30, after styles
440 719 * have already been printed).
441 720 *
@@ -441,18 +720,17 @@
441 720 *
442 721 * @return void
443 722 * @since 1.2.0
444 723 */
445 - public function maybe_enqueue_setup_notice_style() {
724 + public function maybe_enqueue_banner_notice_style() {
446 725 if ( ! Helper::current_user_can() ) {
447 726 return;
448 727 }
449 728
450 - if ( ! apply_filters( 'suredonation_show_setup_gateway_notice', true ) ) {
451 - return;
452 - }
729 + $setup_eligible = apply_filters( 'suredonation_show_setup_gateway_notice', true ) && $this->should_show_setup_gateway_notice();
730 + $test_eligible = apply_filters( 'suredonation_show_test_mode_notice', true ) && $this->should_show_test_mode_notice();
453 731
454 - if ( $this->has_live_donation() || $this->is_gateway_configured() ) {
732 + if ( ! $setup_eligible && ! $test_eligible ) {
455 733 return;
456 734 }
457 735
458 736 $this->enqueue_setup_notice_style();
@@ -458,8 +736,46 @@
458 736 $this->enqueue_setup_notice_style();
459 737 }
460 738
461 739 /**
740 + * Whether the test-mode notice is eligible to show: a gateway is connected
741 + * (in any mode) but the site is currently running in test mode. This fires
742 + * regardless of past donations — a site switched back to test mode still
743 + * needs the "switch to live" nudge — and takes priority over the review
744 + * notices, which are suppressed while it is showing.
745 + *
746 + * @return bool
747 + * @since 1.3.0
748 + */
749 + private function should_show_test_mode_notice() {
750 + return $this->is_gateway_configured()
751 + && 'test' === Payment_Helper::get_payment_mode();
752 + }
753 +
754 + /**
755 + * Whether the webhook-not-configured notice is eligible to show: Stripe is
756 + * connected but its webhook is not configured for the current mode.
757 + *
758 + * @return bool
759 + * @since 1.3.0
760 + */
761 + private function should_show_webhook_notice() {
762 + return Stripe_Helper::is_stripe_connected()
763 + && ! Stripe_Helper::is_webhook_configured();
764 + }
765 +
766 + /**
767 + * Whether the setup-gateway notice is eligible to show: no gateway is
768 + * connected and no live donation has been recorded.
769 + *
770 + * @return bool
771 + * @since 1.3.0
772 + */
773 + private function should_show_setup_gateway_notice() {
774 + return ! $this->has_live_donation() && ! $this->is_gateway_configured();
775 + }
776 +
777 + /**
462 778 * Whether the 3-day install grace has elapsed.
463 779 *
464 780 * @return bool
465 781 * @since 1.2.0
@@ -521,13 +837,12 @@
521 837 * @since 1.2.0
522 838 */
523 839 private function is_gateway_configured() {
524 840 if ( null === $this->gateway_configured ) {
525 - // "Configured" means connected in any mode. Stripe's check is
526 - // mode-agnostic; PayPal's is per-mode, so check both explicitly.
527 - $this->gateway_configured = Stripe_Helper::is_stripe_connected()
528 - || PayPal_Helper::is_paypal_connected( 'live' )
529 - || PayPal_Helper::is_paypal_connected( 'test' );
841 + // "Configured" means connected in any mode; delegated to the shared
842 + // Payment_Helper check (memoized here so repeated notice-chain reads
843 + // only resolve it once per request).
844 + $this->gateway_configured = Payment_Helper::is_any_gateway_connected();
530 845 }
531 846
532 847 return $this->gateway_configured;
533 848 }