PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / includes / gateways / class-charitable-gateways.php

class-charitable-gateways.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at includes/gateways/class-charitable-gateways.php

2,010 lines 64.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class that sets up the gateways.
4 *
5 * @package Charitable/Classes/Charitable_Gateways
6 * @author David Bisset
7 * @copyright Copyright (c) 2023, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.0.0
10 * @version 1.6.57
11 * @version 1.8.7 - Added Square.
12 */
13
14 // Exit if accessed directly.
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 if ( ! class_exists( 'Charitable_Gateways' ) ) :
20
21 /**
22 * Charitable_Gateways
23 *
24 * @since 1.0.0
25 */
26 class Charitable_Gateways {
27
28 /**
29 * The single instance of this class.
30 *
31 * @since 1.0.0
32 *
33 * @var Charitable_Gateways|null
34 */
35 private static $instance = null;
36
37 /**
38 * All available payment gateways.
39 *
40 * @since 1.0.0
41 *
42 * @var array
43 */
44 private $gateways;
45
46 /**
47 * Set up the class.
48 *
49 * Note that the only way to instantiate an object is with the charitable_start method,
50 * which can only be called during the start phase. In other words, don't try
51 * to instantiate this object.
52 *
53 * @since 1.0.0
54 */
55 protected function __construct() {
56 add_action( 'init', array( $this, 'check_settings' ), 10 );
57 add_action( 'init', array( $this, 'register_gateways' ), 1 );
58 add_action( 'init', array( $this, 'connect_gateways' ), 99 ); // Stripe.
59 add_action( 'init', array( $this, 'disconnect_gateways' ), 99 ); // Stripe.
60
61 add_action( 'charitable_make_default_gateway', array( $this, 'handle_gateway_settings_request' ) );
62 add_action( 'charitable_enable_gateway', array( $this, 'handle_gateway_settings_request' ) );
63 add_action( 'charitable_disable_gateway', array( $this, 'handle_gateway_settings_request' ) );
64 add_filter( 'charitable_settings_fields_gateways_gateway', array( $this, 'register_gateway_settings' ), 10, 2 );
65
66 add_action( 'charitable_disable_gateway', array( $this, 'handle_gateway_settings_request' ) );
67
68 /**
69 * Square Connect
70 */
71 add_action( 'init', array( $this, 'disconnect_gateways_square' ), 99 ); // Square.
72 add_action( 'init', array( $this, 'refresh_square_token' ), 99 ); // Square.
73
74 add_action( 'init', array( $this, 'get_gateway_error_message' ), 99 );
75
76 do_action( 'charitable_gateway_start', $this );
77
78 add_action( 'wp_ajax_charitable-show-stripe-keys', array( $this, 'show_stripe_manual_keys_ajax' ), 10 );
79 add_action( 'wp_ajax_charitable_switch_square_gateways', array( $this, 'handle_square_gateway_switch' ) );
80 add_action( 'wp_ajax_charitable_switch_square_core_to_legacy', array( $this, 'handle_square_core_to_legacy_switch' ) );
81
82 // Dequeue Square Legacy scripts if Square Legacy is not active.
83 add_action( 'wp_enqueue_scripts', array( $this, 'maybe_dequeue_square_legacy_scripts' ), 999 );
84
85 // PayPal Hard Fork (1.8.11): last-word defense against third-party
86 // callbacks that re-inject the hidden PayPal gateway. Mirrors the
87 // register_gateways + get_active_gateways strips.
88 add_filter( 'charitable_active_gateways', array( $this, 'apply_paypal_tier_visibility' ), PHP_INT_MAX );
89 }
90
91
92 /**
93 * Returns and/or create the single instance of this class.
94 *
95 * @since 1.2.0
96 *
97 * @return Charitable_Gateways
98 */
99 public static function get_instance() {
100 if ( is_null( self::$instance ) ) {
101 self::$instance = new self();
102 }
103
104 return self::$instance;
105 }
106
107 /**
108 * Register gateways.
109 *
110 * To register a new gateway, you need to hook into the `charitable_payment_gateways`
111 * hook and give Charitable the name of your gateway class.
112 *
113 * @since 1.2.0
114 *
115 * @return void
116 */
117 public function register_gateways() {
118 /**
119 * Filter the list of payment gateways.
120 *
121 * @since 1.2.0
122 * @version 1.8.7
123 *
124 * @param array $gateways The list of gateways in gateway ID => gateway class format.
125 */
126
127 $gateways = array(
128 'stripe' => 'Charitable_Gateway_Stripe_AM',
129 'paypal' => 'Charitable_Gateway_Paypal',
130 'paypal_commerce' => 'Charitable_Gateway_Paypal_Commerce',
131 'offline' => 'Charitable_Gateway_Offline',
132 );
133
134 // Always add Square Core to the available gateways.
135 $gateways['square_core'] = 'Charitable_Gateway_Square';
136
137 $this->gateways = apply_filters(
138 'charitable_payment_gateways',
139 $gateways
140 );
141
142 // PayPal Hard Fork (1.8.11): hide the off-tier PayPal gateway entirely so it
143 // cannot be enabled, listed in admin, or referenced in active_gateways.
144 $this->gateways = $this->apply_paypal_tier_visibility( $this->gateways );
145
146 // If Legacy mode is active and Square Legacy gateway is active, deactivate Square Core.
147 if ( charitable_square_legacy_mode() && $this->is_active_gateway( 'square' ) ) {
148 $this->disable_gateway( 'square_core' );
149
150 if ( charitable_is_debug( 'square' ) ) {
151 error_log( '[Charitable Square Core] Deactivated Square Core because Square Legacy is active' ); // phpcs:ignore
152 }
153 }
154 }
155
156 /**
157 * Read the locked PayPal user tier set by the 1.8.11 detection upgrade.
158 *
159 * The result is escapable at runtime via the standard `pre_option_*` filter
160 * (`pre_option_charitable_paypal_user_tier`) since this is a vanilla `get_option`
161 * read. See `Charitable_Upgrade::detect_paypal_user_tier()` for how the value
162 * is computed and persisted.
163 *
164 * @since 1.8.11
165 *
166 * @return string `legacy` or `commerce`. Defaults to `commerce` when unset.
167 */
168 public function get_paypal_tier() {
169 $tier = get_option( 'charitable_paypal_user_tier', 'commerce' );
170 return ( 'legacy' === $tier ) ? 'legacy' : 'commerce';
171 }
172
173 /**
174 * Strip the off-tier PayPal gateway from a gateways map.
175 *
176 * `legacy` tier hides `paypal_commerce`. `commerce` tier hides `paypal`.
177 * Used in three places to layer defense:
178 * 1. `register_gateways()` — strips before `$this->gateways` is exposed.
179 * 2. `get_active_gateways()` — inline cleanup mirroring the Square pattern.
180 * 3. `charitable_active_gateways` filter (PHP_INT_MAX priority) — last word
181 * against third-party callbacks that re-inject the hidden gateway.
182 *
183 * @since 1.8.11
184 *
185 * @param array $gateways Map of gateway_id => class_name (or anything keyed by id).
186 * @return array Same shape, with the off-tier PayPal key removed when present.
187 */
188 public function apply_paypal_tier_visibility( $gateways ) {
189 if ( ! is_array( $gateways ) ) {
190 return $gateways;
191 }
192
193 if ( 'legacy' === $this->get_paypal_tier() ) {
194 unset( $gateways['paypal_commerce'] );
195 } else {
196 unset( $gateways['paypal'] );
197 }
198
199 return $gateways;
200 }
201
202 /**
203 * Receives a request to enable or disable a payment gateway and validates it before passing it off.
204 *
205 * @since 1.0.0
206 *
207 * @return void
208 */
209 public function handle_gateway_settings_request() {
210 // phpcs:disable WordPress.Security.NonceVerification.Missing
211 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
212 if ( ! isset( $_REQUEST['_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_REQUEST['_nonce'] ), 'gateway' ) ) {
213 wp_die( __( 'Cheatin\' eh?!', 'charitable' ) ); // phpcs:ignore
214 }
215
216 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Gateway ID, sanitized below
217 $gateway = isset( $_REQUEST['gateway_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['gateway_id'] ) ) : false;
218 // phpcs:enable WordPress.Security.NonceVerification.Missing
219
220 /* Gateway must be set */
221 if ( false === $gateway ) {
222 wp_die( __( 'Missing gateway.', 'charitable' ) ); // phpcs:ignore
223 }
224
225 /* Validate gateway. */
226 if ( ! isset( $this->gateways[ $gateway ] ) ) {
227 wp_die( __( 'Invalid gateway.', 'charitable' ) ); // phpcs:ignore
228 }
229
230 switch ( $_REQUEST['charitable_action'] ) { // phpcs:ignore
231 case 'disable_gateway':
232 $this->disable_gateway( $gateway );
233 break;
234 case 'enable_gateway':
235 $this->enable_gateway( $gateway );
236 $this->redirect_gateway_settings( $gateway );
237 break;
238 case 'make_default_gateway':
239 $this->set_default_gateway( $gateway );
240 break;
241 default:
242 /**
243 * Do something when a gateway settings request takes place.
244 *
245 * @since 1.0.0
246 *
247 * @param string $action The action taking place.
248 * @param string $gateway_id The gateway ID.
249 */
250 do_action( 'charitable_gateway_settings_request', $_REQUEST['charitable_action'], $gateway ); // phpcs:ignore
251 }
252 }
253
254 /**
255 * Returns all available payment gateways.
256 *
257 * @since 1.0.0
258 *
259 * @return array
260 */
261 public function get_available_gateways() {
262 if ( isset( $this->gateways['stripe'] ) ) {
263 $this->gateways['stripe'] = charitable()->is_stripe_connect_addon() ? 'Charitable_Gateway_Stripe_AM' : $this->gateways['stripe'];
264 }
265 return $this->gateways;
266 }
267
268 /**
269 * Returns the current active gateways.
270 *
271 * @since 1.0.0
272 *
273 * @return string[]
274 */
275 public function get_active_gateways() {
276 $active_gateways = charitable_get_option( 'active_gateways', array() );
277
278 // Force cleanup of 'square' gateway if charitable-square plugin is not active (runs once per 24 hours).
279 $this->maybe_force_cleanup_square_gateway( $active_gateways );
280
281 foreach ( $active_gateways as $gateway_id => $gateway_class ) {
282 if ( ! class_exists( $gateway_class ) ) {
283 unset( $active_gateways[ $gateway_id ] );
284 }
285 }
286
287 // Remove 'square' gateway if charitable-square plugin is not active.
288 if ( isset( $active_gateways['square'] ) && ! $this->is_square_addon_active() ) {
289 unset( $active_gateways['square'] );
290 }
291
292 // PayPal Hard Fork (1.8.11): strip the off-tier PayPal gateway in case both
293 // were saved into active_gateways (direct DB edit, multisite weirdness, etc.).
294 $active_gateways = $this->apply_paypal_tier_visibility( $active_gateways );
295
296 uksort( $active_gateways, array( $this, 'sort_by_default' ) );
297
298 // force the strip active gateway normally for stripe addon to point to the stripe built into core.
299 if ( isset( $active_gateways['stripe'] ) ) {
300 $active_gateways['stripe'] = 'Charitable_Gateway_Stripe_AM';
301 }
302
303 /**
304 * Filter the list of active gateways.
305 *
306 * @since 1.0.0
307 *
308 * @param array $gateways Active gateways.
309 */
310 return apply_filters( 'charitable_active_gateways', $active_gateways );
311 }
312
313 /**
314 * Returns an array of the active gateways, in ID => name format.
315 *
316 * This is useful for select/radio input fields.
317 *
318 * @since 1.0.0
319 *
320 * @return string[]
321 */
322 public function get_gateway_choices() {
323 $gateways = array();
324
325 foreach ( $this->get_active_gateways() as $id => $class ) {
326 $gateway = new $class();
327 $gateways[ $id ] = $gateway->get_label();
328 }
329
330 return $gateways;
331 }
332
333 /**
334 * Returns a text description of the active gateways.
335 *
336 * @since 1.3.0
337 *
338 * @return string[]
339 */
340 public function get_active_gateways_names() {
341 $gateways = array();
342
343 foreach ( $this->get_active_gateways() as $id => $class ) {
344 $gateway = new $class();
345 $gateways[] = $gateway->get_name();
346 }
347
348 return $gateways;
349 }
350
351 /**
352 * Return the gateway class name for a given gateway.
353 *
354 * @since 1.0.0
355 *
356 * @param string $gateway Gateway ID.
357 * @return string|false
358 */
359 public function get_gateway( $gateway ) {
360 return isset( $this->gateways[ $gateway ] ) ? $this->gateways[ $gateway ] : false;
361 }
362
363 /**
364 * Return the gateway object for a given gateway.
365 *
366 * @since 1.0.0
367 *
368 * @param string $gateway Gateway ID.
369 * @return Charitable_Gateway|null
370 */
371 public function get_gateway_object( $gateway ) {
372 $class = $this->get_gateway( $gateway );
373 $object = $class ? new $class() : null;
374
375 /**
376 * Filter the gateway object.
377 *
378 * @since 1.6.30
379 *
380 * @param Charitable_Gateway|null $object The gateway object.
381 */
382 return apply_filters( 'charitable_gateway_object_' . $gateway, $object, $gateway );
383 }
384
385 /**
386 * Returns whether the passed gateway is active.
387 *
388 * @since 1.0.0
389 *
390 * @param string $gateway_id Gateway ID.
391 * @return boolean
392 */
393 public function is_active_gateway( $gateway_id ) {
394 return array_key_exists( $gateway_id, $this->get_active_gateways() );
395 }
396
397 /**
398 * Checks whether the submitted gateway is valid.
399 *
400 * @since 1.4.3
401 *
402 * @param string $gateway Gateway ID.
403 * @return boolean
404 */
405 public function is_valid_gateway( $gateway ) {
406 /**
407 * Validate a particular gatewya.
408 *
409 * @since 1.4.3
410 *
411 * @param boolean $valid Whether a gateway is valid.
412 * @param string $gateway The gateway ID.
413 */
414
415 if ( charitable_is_debug( 'webhook' ) ) {
416 // phpcs:disable
417 error_log( 'is_valid_gateway' );
418 error_log( print_r( $gateway, true ) );
419 error_log( print_r( $this->gateways, true ) );
420 // phpcs:enable
421 }
422
423 return apply_filters( 'charitable_is_valid_gateway', array_key_exists( $gateway, $this->gateways ), $gateway );
424 }
425
426 /**
427 * Returns the default gateway.
428 *
429 * @since 1.0.0
430 *
431 * @return string
432 */
433 public function get_default_gateway() {
434 return charitable_get_option( 'default_gateway', '' );
435 }
436
437 /**
438 * Provide default gateway settings fields.
439 *
440 * @since 1.0.0
441 *
442 * @param array $settings Gateway settings.
443 * @param Charitable_Gateway $gateway The gateway's helper object.
444 * @return array
445 */
446 public function register_gateway_settings( $settings, Charitable_Gateway $gateway ) {
447 add_filter( 'charitable_settings_fields_gateways_gateway_' . $gateway->get_gateway_id(), array( $gateway, 'default_gateway_settings' ), 5 );
448 add_filter( 'charitable_settings_fields_gateways_gateway_' . $gateway->get_gateway_id(), array( $gateway, 'gateway_settings' ), 15 );
449
450 /**
451 * Filter the settings to show for a particular gateway.
452 *
453 * @since 1.0.0
454 *
455 * @param array $settings Gateway settings.
456 */
457 return apply_filters( 'charitable_settings_fields_gateways_gateway_' . $gateway->get_gateway_id(), $settings );
458 }
459
460 /**
461 * Returns true if test mode is enabled.
462 *
463 * @since 1.0.0
464 *
465 * @return boolean
466 */
467 public function in_test_mode() {
468 $enabled = charitable_get_option( 'test_mode', false );
469
470 /**
471 * Return whether Charitable is in test mode.
472 *
473 * @since 1.0.0
474 *
475 * @param boolean $enabled Whether test mode is on.
476 */
477 return apply_filters( 'charitable_in_test_mode', $enabled );
478 }
479
480 /**
481 * Checks whether all of the active gateways support a feature.
482 *
483 * If ANY gateway doesn't support the feature, this returns false.
484 *
485 * @since 1.4.0
486 *
487 * @param string $feature Feature to search for.
488 * @return boolean
489 */
490 public function all_gateways_support( $feature ) {
491 foreach ( $this->get_active_gateways() as $gateway_id => $gateway_class ) {
492
493 $gateway_object = new $gateway_class();
494
495 if ( false === $gateway_object->supports( $feature ) ) {
496 return false;
497 }
498 }
499
500 return true;
501 }
502
503 /**
504 * Checks whether any of the active gateways support a feature.
505 *
506 * If any gateway supports the feature, this returns true. Otherwise false.
507 *
508 * @since 1.4.0
509 *
510 * @param string $feature Feature to check for.
511 * @return boolean
512 */
513 public function any_gateway_supports( $feature ) {
514 foreach ( $this->get_active_gateways() as $gateway_id => $gateway_class ) {
515
516 $gateway_object = new $gateway_class();
517
518 if ( true === $gateway_object->supports( $feature ) ) {
519 return true;
520 }
521 }
522
523 return false;
524 }
525
526 /**
527 * Checks whether all of the active gateways support AJAX.
528 *
529 * If ANY gateway doesn't support AJAX, this returns false.
530 *
531 * @since 1.3.0
532 *
533 * @return boolean
534 */
535 public function gateways_support_ajax() {
536 return $this->all_gateways_support( '1.3.0' );
537 }
538
539 /**
540 * Return an array of recommended gateways for the current site.
541 *
542 * Note that this will only return gateways that are not already
543 * available on the site. i.e. If you have Stripe installed, it
544 * will not suggest that.
545 *
546 * @since 1.5.0
547 *
548 * @return array
549 */
550 public function get_recommended_gateways() {
551 $available = $this->get_available_gateways();
552 $gateways = array(
553 'payfast' => __( 'Payfast', 'charitable' ),
554 'paystack' => __( 'Paystack', 'charitable' ),
555 'stripe' => __( 'Stripe', 'charitable' ),
556 'authorize_net' => __( 'Authorize.Net', 'charitable' ),
557 'windcave' => __( 'Windcave', 'charitable' ),
558 'braintree' => __( 'Braintree', 'charitable' ),
559 'mollie' => __( 'Mollie', 'charitable' ),
560 'gocardless' => __( 'GoCardless', 'charitable' ),
561 'payrexx' => __( 'Payrexx', 'charitable' ),
562 );
563
564 /* If the user has already enabled one of these, leave them alone. :) */
565 foreach ( $gateways as $gateway_id => $gateway ) {
566 if ( array_key_exists( $gateway_id, $available ) ) {
567 return array();
568 }
569 }
570
571 $currency = charitable_get_default_currency();
572 $locale = get_locale();
573
574 if ( 'en_ZA' == $locale || 'ZAR' == $currency ) {
575 return charitable_array_subset( $gateways, array( 'payfast', 'paystack' ) );
576 }
577
578 if ( in_array( $currency, array( 'NGN', 'GHS' ) ) ) {
579 return charitable_array_subset( $gateways, array( 'paystack' ) );
580 }
581
582 if ( in_array( $locale, array( 'en_NZ', 'en_AU', 'en_GB' ) ) || in_array( $currency, array( 'NZD', 'AUD', 'GBP' ) ) ) {
583 return charitable_array_subset( $gateways, array( 'stripe', 'gocardless' ) );
584 }
585
586 if ( in_array( $locale, array( 'ms_MY', 'ja', 'zh_HK' ) ) || in_array( $currency, array( 'MYR', 'JPY', 'HKD' ) ) ) {
587 return charitable_array_subset( $gateways, array( 'stripe', 'windcave' ) );
588 }
589
590 if ( in_array( $locale, array( 'th' ) ) || in_array( $currency, array( 'BND', 'FJD', 'KWD', 'PGK', 'SBD', 'THB', 'TOP', 'VUV', 'WST' ) ) ) {
591 return charitable_array_subset( $gateways, array( 'windcave' ) );
592 }
593
594 if ( in_array( $currency, array( 'EUR' ) ) ) {
595 return charitable_array_subset( $gateways, array( 'stripe', 'mollie' ) );
596 }
597
598 if ( in_array( $currency, array( 'CHF', 'DKK' ) ) ) {
599 return charitable_array_subset( $gateways, array( 'stripe', 'payrexx' ) );
600 }
601
602 return charitable_array_subset( $gateways, array( 'stripe', 'braintree' ) );
603 }
604
605 /**
606 * Sets the default gateway.
607 *
608 * @since 1.0.0
609 *
610 * @param string $gateway Gateway ID.
611 * @return void
612 */
613 protected function set_default_gateway( $gateway ) {
614 $settings = get_option( 'charitable_settings' );
615
616 $settings['default_gateway'] = $gateway;
617
618 update_option( 'charitable_settings', $settings );
619
620 charitable_get_admin_notices()->add_success( __( 'Default Gateway Updated', 'charitable' ) );
621
622 do_action( 'charitable_set_gateway_gateway', $gateway );
623 }
624
625 /**
626 * Enable a payment gateway.
627 *
628 * @since 1.0.0
629 *
630 * @param string $gateway Gateway ID.
631 * @param bool $show_update_message Whether to show the update message.
632 * @return void
633 */
634 protected function enable_gateway( $gateway, $show_update_message = true ) {
635 $settings = get_option( 'charitable_settings' );
636
637 if ( false === $settings || ! is_array( $settings ) ) {
638 echo 'settings is false';
639 $settings = array();
640 }
641
642 $active_gateways = isset( $settings['active_gateways'] ) ? $settings['active_gateways'] : array();
643 $active_gateways[ $gateway ] = $this->gateways[ $gateway ];
644
645 if ( ! isset( $settings['active_gateways'] ) ) {
646 $settings['active_gateways'] = array();
647 }
648
649 $settings['active_gateways'] = $active_gateways;
650
651 /* If this is the only gateway, make it the default gateway */
652 if ( 1 == count( $settings['active_gateways'] ) ) {
653 $settings['default_gateway'] = $gateway;
654 }
655
656 update_option( 'charitable_settings', $settings );
657
658 if ( $show_update_message ) {
659 Charitable_Settings::get_instance()->add_update_message( __( 'Gateway enabled', 'charitable' ), 'success' );
660 }
661
662 /**
663 * Do something when a payment gateway is enabled.
664 *
665 * @since 1.0.0
666 *
667 * @param string $gateway The payment gateway.
668 */
669 do_action( 'charitable_gateway_enable', $gateway );
670 }
671
672 /**
673 * Disable a payment gateway.
674 *
675 * @since 1.0.0
676 *
677 * @param string $gateway Gateway ID.
678 * @return void
679 */
680 protected function disable_gateway( $gateway ) {
681 $settings = get_option( 'charitable_settings' );
682
683 if ( ! isset( $settings['active_gateways'][ $gateway ] ) ) {
684 return;
685 }
686
687 unset( $settings['active_gateways'][ $gateway ] );
688
689 /* Set a new default gateway */
690 if ( $gateway == $this->get_default_gateway() ) {
691
692 $settings['default_gateway'] = count( $settings['active_gateways'] ) ? key( $settings['active_gateways'] ) : '';
693
694 }
695
696 update_option( 'charitable_settings', $settings );
697
698 Charitable_Settings::get_instance()->add_update_message( __( 'Gateway disabled', 'charitable' ), 'success' );
699
700 /**
701 * Do something when a payment gateway is disabled.
702 *
703 * @since 1.0.0
704 *
705 * @param string $gateway The payment gateway.
706 */
707 do_action( 'charitable_gateway_disable', $gateway );
708 }
709
710 /**
711 * Connects to Stripe by saving account information passed back to the plugin.
712 *
713 * @since 4.2.2
714 * @version 1.8.9
715 *
716 * @return void
717 */
718 public function connect_gateways() {
719 // Current user cannot handle this request.
720 if ( ! current_user_can( 'manage_options' ) ) {
721 return;
722 }
723
724 // Do not need to handle this request, bail.
725 if (
726 ! isset( $_GET['wpcharitable_gateway_connect_completion'] ) || // phpcs:ignore
727 'stripe_connect' !== $_GET['wpcharitable_gateway_connect_completion'] || // phpcs:ignore
728 ! isset( $_GET['state'] ) // phpcs:ignore
729 ) {
730 return;
731 }
732
733 // Unable to redirect, bail.
734 if ( headers_sent() ) {
735 return;
736 }
737
738 if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Server variables for URL construction
739 $current_url = ( is_ssl() ? 'https' : 'http' ) . '://' . wp_unslash( $_SERVER['HTTP_HOST'] ) . wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Server variables are unslashed and then sanitized via esc_url_raw()
740 $current_url = esc_url_raw( $current_url ); // phpcs:ignore
741 } else {
742 $current_url = '';
743 }
744
745 $customer_site_url = remove_query_arg(
746 array(
747 'state',
748 'wpcharitable_gateway_connect_completion',
749 ),
750 $current_url
751 );
752
753 $wpcharitable_credentials_url = add_query_arg(
754 array(
755 'live_mode' => (int) ! charitable_get_option( 'test_mode' ),
756 'state' => sanitize_text_field( $_GET['state'] ), // phpcs:ignore
757 'customer_site_url' => urlencode( $customer_site_url ),
758 ),
759 'https://wpcharitable.com/stripe-connect/?wpcharitable_gateway_connect_credentials=stripe_connect'
760 );
761
762 // You might need to add add_filter('https_ssl_verify', '__return_false'); here if testing locally.
763 $response = wp_remote_get( esc_url_raw( $wpcharitable_credentials_url ) );
764
765 if (
766 is_wp_error( $response ) ||
767 200 !== wp_remote_retrieve_response_code( $response )
768 ) {
769
770 // Log error details for debugging.
771 if ( is_wp_error( $response ) ) {
772 if ( charitable_is_debug( 'stripe' ) ) {
773 error_log( 'Charitable Stripe Connect Error: ' . $response->get_error_message() . ' | URL: ' . $wpcharitable_credentials_url ); // phpcs:ignore
774 }
775 } else {
776 $response_code = wp_remote_retrieve_response_code( $response );
777 $response_body = wp_remote_retrieve_body( $response );
778 if ( charitable_is_debug( 'stripe' ) ) {
779 error_log( 'Charitable Stripe Connect Error: HTTP ' . $response_code . ' | URL: ' . $wpcharitable_credentials_url . ' | Response: ' . substr( $response_body, 0, 500 ) ); // phpcs:ignore
780 }
781 }
782
783 $stripe_account_settings_url = add_query_arg(
784 array(
785 'tab' => 'gateways',
786 'page' => 'charitable-settings',
787 'group' => 'gateways_stripe',
788 ),
789 admin_url( 'admin.php' )
790 );
791
792 $message = wpautop(
793 sprintf(
794 /* translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. */
795 __(
796 'There was an error getting your Stripe credentials. Please %1$stry again%2$s. If you continue to have this problem, please contact support.',
797 'charitable'
798 ),
799 '<a href="' . esc_url( $stripe_account_settings_url ) . '">',
800 '</a>'
801 )
802 );
803
804 wp_die( $message ); // phpcs:ignore
805 }
806
807 $body = wp_remote_retrieve_body( $response );
808
809 /** @var string $body */
810 $body = json_decode( $body, true );
811
812 /** @var array<array<string>> $body */
813 $account_data = $body['data'];
814
815 $this->save_account_information( $account_data );
816
817 // update the option to track that we are using stripe connect.
818 update_option( 'charitable_using_stripe_connect', time() );
819
820 /**
821 * Allow further processing after connecting a Stripe account.
822 *
823 * @since 3.6.0
824 *
825 * @param array $data Stripe response data.
826 */
827 do_action( 'wpcharitable_stripe_account_connected', $account_data ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- This is a legacy hook used by other Charitable code. Changing it would break existing functionality.
828
829 wp_safe_redirect( esc_url_raw( $customer_site_url ) );
830 exit;
831 }
832
833 /**
834 * Refreshes the Square token.
835 *
836 * @since 1.8.7
837 *
838 * @return void
839 */
840 public function refresh_square_token() {
841 // Current user cannot handle this request.
842 if ( ! current_user_can( 'manage_options' ) || charitable_square_legacy_mode() ) {
843 return;
844 }
845
846 // Do not need to handle this request, bail.
847 if (
848 ! isset( $_GET['wpcharitable_gateway_refresh_token_completion'] ) || // phpcs:ignore
849 'square_connect' !== $_GET['wpcharitable_gateway_refresh_token_completion'] || // phpcs:ignore
850 ! isset( $_GET['state'] ) // phpcs:ignore
851 ) {
852 return;
853 }
854
855 // Unable to redirect, bail.
856 if ( headers_sent() ) {
857 return;
858 }
859
860 if ( charitable_is_debug( 'square' ) ) {
861 error_log( 'checking for use: refresh_square_token' ); // phpcs:ignore
862 }
863
864 if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
865 $current_url = ( is_ssl() ? 'https' : 'http' ) . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; // phpcs:ignore
866 } else {
867 $current_url = '';
868 }
869
870 $customer_site_url = remove_query_arg(
871 array(
872 'state',
873 'wpcharitable_gateway_refresh_token_completion',
874 'message',
875 ),
876 $current_url
877 );
878
879 $wpcharitable_credentials_url = add_query_arg(
880 array(
881 'live_mode' => (int) ! charitable_get_option( 'test_mode' ),
882 'state' => sanitize_text_field( $_GET['state'] ), // phpcs:ignore
883 'code' => 'request_credentials',
884 'customer_site_url' => urlencode( $customer_site_url ),
885 ),
886 'https://connect.wpcharitable.com/square-connect/'
887 );
888
889 $response = wp_remote_get( esc_url_raw( $wpcharitable_credentials_url ) );
890
891 if (
892 is_wp_error( $response ) ||
893 200 !== wp_remote_retrieve_response_code( $response )
894 ) {
895
896 $square_account_settings_url = add_query_arg(
897 array(
898 'tab' => 'gateways',
899 'page' => 'charitable-settings',
900 'group' => 'gateways_square_core',
901 ),
902 admin_url( 'admin.php' )
903 );
904
905 $message = wpautop(
906 sprintf(
907 /* translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. */
908 __(
909 'There was an error getting your Square credentials. Please %1$stry again%2$s. If you continue to have this problem, please contact support.',
910 'charitable'
911 ),
912 '<a href="' . esc_url( $square_account_settings_url ) . '">',
913 '</a>'
914 )
915 );
916
917 wp_die( $message ); // phpcs:ignore
918 }
919
920 $body = wp_remote_retrieve_body( $response );
921
922 $body = json_decode( $body, true );
923
924 $account_data = $body['data'];
925
926 $this->save_account_information_square( $account_data );
927
928 // Don't show the success message if we are on the welcome / getting started page (otherwise it's fine).
929 if ( ! isset( $_GET['page'] ) || 'charitable-getting-started' !== $_GET['page'] ) { // phpcs:ignore
930 // translators: %s is the mode of the connection.
931 charitable_get_admin_notices()->add_success( sprintf( __( 'You have reconnected and updated your access token for Square in %s mode.', 'charitable' ), charitable_get_option( 'test_mode' ) ? 'test' : 'live' ) );
932 }
933
934 // update the option to track that we are using square connect.
935 // Get the value and update it.
936 $charitable_using_square_connect = get_option( 'charitable_using_square_connect', array() );
937 if ( ! is_array( $charitable_using_square_connect ) ) {
938 $charitable_using_square_connect = array();
939 }
940 $charitable_using_square_connect['test'] = time();
941 update_option( 'charitable_using_square_connect', $charitable_using_square_connect );
942
943 /**
944 * Allow further processing after reconnecting a Square account.
945 *
946 * @since 3.6.0
947 *
948 * @param array $data Square response data.
949 */
950 do_action( 'wpcharitable_square_token_refreshed', $account_data ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- This is a legacy hook used by other Charitable code. Changing it would break existing functionality.
951
952 wp_safe_redirect( esc_url_raw( $customer_site_url ) );
953 exit;
954 }
955
956 /**
957 * Schedule the Square token renewal cron job.
958 *
959 * @since 1.8.7
960 *
961 * @param string $mode The mode to schedule the cron for (test/live).
962 * @return void
963 */
964 private function schedule_square_token_renewal_cron( $mode ) {
965 // Clear any existing cron jobs for this mode.
966 wp_clear_scheduled_hook( 'charitable_square_token_renewal', array( $mode ) );
967
968 // Schedule the cron job to run every 14 days.
969 wp_schedule_event( time() + ( 14 * DAY_IN_SECONDS ), '14days', 'charitable_square_token_renewal', array( $mode ) );
970 }
971
972 /**
973 * Renew the Square token via cron job.
974 *
975 * @since 1.8.7
976 *
977 * @param string $mode The mode to renew the token for (test/live).
978 * @return void
979 */
980 public function renew_square_token_cron( $mode ) {
981 if ( charitable_is_debug( 'square' ) ) {
982 // phpcs:disable
983 error_log( 'renew_square_token_cron for test mode: ' . $mode );
984 // phpcs:enable
985 }
986
987 // Get the URL to renew the token.
988 $url = Charitable_Gateway_Square::get_instance()->get_square_renew_token_url( '', true );
989
990 if ( charitable_is_debug( 'square' ) ) {
991 // phpcs:disable
992 error_log( 'url: ' . $url );
993 // phpcs:enable
994 }
995
996 // Clear both failed and success options.
997 delete_option( 'wpcharitable_square_failed_token_renewal_' . $mode );
998 delete_option( 'wpcharitable_square_token_refreshed_' . $mode );
999
1000 if ( empty( $url ) ) {
1001 update_option( 'wpcharitable_square_failed_token_renewal_' . $mode, time() );
1002 return;
1003 }
1004
1005 // Add SSL verification argsget_square_renew_token_url.
1006 $args = array(
1007 'timeout' => 30,
1008 'sslverify' => false, // Temporarily disable SSL verification.
1009 'headers' => array(
1010 'Content-Type' => 'application/json',
1011 ),
1012 );
1013
1014 // Make the request to the proxy server.
1015 $response = wp_remote_get( esc_url_raw( $url ), $args );
1016
1017 if ( charitable_is_debug( 'square' ) ) {
1018 // phpcs:disable
1019 error_log( 'response: ' . print_r( $response, true ) );
1020 // phpcs:enable
1021 }
1022
1023 if ( is_wp_error( $response ) ) {
1024 if ( charitable_is_debug( 'square' ) ) {
1025 // phpcs:disable
1026 error_log( 'The cron job url has failed with a wp_error: ' . $response->get_error_message() );
1027 // phpcs:enable
1028 }
1029 update_option( 'wpcharitable_square_failed_token_renewal_' . $mode, time() );
1030 return;
1031 }
1032
1033 $response_code = wp_remote_retrieve_response_code( $response );
1034 if ( 200 !== $response_code ) {
1035 if ( charitable_is_debug( 'square' ) ) {
1036 // phpcs:disable
1037 error_log( 'The cron job url has failed with response code: ' . $response_code );
1038 // phpcs:enable
1039 }
1040 update_option( 'wpcharitable_square_failed_token_renewal_' . $mode, time() );
1041 return;
1042 }
1043
1044 $body = wp_remote_retrieve_body( $response );
1045 $data = json_decode( $body, true );
1046
1047 if ( charitable_is_debug( 'square' ) ) {
1048 // phpcs:disable
1049 error_log( 'data: ' . print_r( $data, true ) );
1050 error_log( 'body: ' . print_r( $body, true ) );
1051 // phpcs:enable
1052 }
1053
1054 if ( empty( $data ) || ! isset( $data['data'] ) ) {
1055 if ( charitable_is_debug( 'square' ) ) {
1056 // phpcs:disable
1057 error_log( 'The cron job url has failed - no data returned.' );
1058 // phpcs:enable
1059 }
1060 update_option( 'wpcharitable_square_failed_token_renewal_' . $mode, time() );
1061 return;
1062 }
1063
1064 // Update the stored tokens.
1065 $settings = get_option( 'charitable_settings' );
1066 $settings['gateways_square'][ $mode ]['access_token'] = isset( $data['data']['access_token'] ) ? sanitize_text_field( $data['data']['access_token'] ) : false;
1067 $settings['gateways_square'][ $mode ]['refresh_token'] = isset( $data['data']['refresh_token'] ) ? sanitize_text_field( $data['data']['refresh_token'] ) : false;
1068 $settings['gateways_square'][ $mode ]['token_refresh_date'] = time();
1069
1070 update_option( 'charitable_settings', $settings );
1071
1072 // Clear any failure notices.
1073 delete_option( 'wpcharitable_square_failed_token_renewal_' . $mode );
1074
1075 // Add a success notice.
1076 update_option( 'wpcharitable_square_token_refreshed_' . $mode, time() );
1077
1078 if ( charitable_is_debug( 'square' ) ) {
1079 // phpcs:disable
1080 error_log( 'wpcharitable_square_token_refreshed_' . $mode . ' updated to: ' . time() );
1081 // phpcs:enable
1082 }
1083 }
1084
1085 /**
1086 * Saves the account information sent back from Square, alongside other, to identify the connected account.
1087 *
1088 * @since 1.8.7
1089 *
1090 * @param array<string> $data Square oAuth account data.
1091 * @param string $gateway_id The gateway ID.
1092 * @return void
1093 */
1094 private function save_account_information_square( $data, $gateway_id = 'square' ) {
1095 if ( charitable_is_debug( 'square' ) ) {
1096 // phpcs:disable
1097 error_log( 'save_account_information_square' );
1098 error_log( print_r( $data, true ) );
1099 // phpcs:enable
1100 }
1101
1102 $mode = $this->in_test_mode()
1103 ? 'test'
1104 : 'live';
1105
1106 $settings = get_option( 'charitable_settings' );
1107
1108 $settings[ 'gateways_' . $gateway_id ][ $mode ]['access_token'] = isset( $data['access_token'] ) ? sanitize_text_field( $data['access_token'] ) : false;
1109 $settings[ 'gateways_' . $gateway_id ][ $mode ]['refresh_token'] = isset( $data['refresh_token'] ) ? sanitize_text_field( $data['refresh_token'] ) : false;
1110 $settings[ 'gateways_' . $gateway_id ][ $mode ]['merchant_id'] = isset( $data['merchant_id'] ) ? sanitize_text_field( $data['merchant_id'] ) : false;
1111 $settings[ 'gateways_' . $gateway_id ][ $mode ]['location_id'] = isset( $data['location_id'] ) ? sanitize_text_field( $data['location_id'] ) : false;
1112 $settings[ 'gateways_' . $gateway_id ][ $mode ]['token_refresh_date'] = time();
1113
1114 update_option( 'charitable_settings', $settings );
1115
1116 // We save the AES key in it's own WordPress options field, marked as 'charitable_square_aes_key_test' or 'charitable_square_aes_key_live'.
1117 $aes_key = isset( $data['aes_key'] ) ? sanitize_text_field( $data['aes_key'] ) : false;
1118
1119 if ( charitable_is_debug( 'square' ) ) {
1120 // phpcs:disable
1121 error_log( 'aes_key: ' . $aes_key );
1122 // phpcs:enable
1123 }
1124
1125 update_option( 'charitable_square_aes_key_' . $mode, $aes_key );
1126
1127 // Schedule the token renewal cron job.
1128 $this->schedule_square_token_renewal_cron( $mode );
1129
1130 do_action( 'charitable_save_account_information_gateway_' . $gateway_id, $settings, $data, $gateway_id );
1131 }
1132
1133 /**
1134 * Saves the account information sent back from Stripe, alongside other, to identify the connected account.
1135 *
1136 * @since 4.4.2
1137 * @version 1.8.1.12
1138 *
1139 * @param array<string> $data Stripe oAuth account data.
1140 * @param string $gateway_id The gateway ID.
1141 * @return void
1142 */
1143 private function save_account_information( $data, $gateway_id = 'stripe' ) {
1144
1145 $prefix = $this->in_test_mode()
1146 ? 'test'
1147 : 'live';
1148
1149 $settings = get_option( 'charitable_settings' );
1150
1151 $settings[ 'gateways_' . $gateway_id ]['live_secret_key'] = '';
1152 $settings[ 'gateways_' . $gateway_id ]['live_public_key'] = '';
1153 $settings[ 'gateways_' . $gateway_id ]['test_secret_key'] = '';
1154 $settings[ 'gateways_' . $gateway_id ]['test_public_key'] = '';
1155
1156 $settings[ 'gateways_' . $gateway_id ][ $prefix . '_secret_key' ] = isset( $data['secret_key'] ) ? sanitize_text_field( $data['secret_key'] ) : false;
1157 $settings[ 'gateways_' . $gateway_id ][ $prefix . '_public_key' ] = isset( $data['publishable_key'] ) ? sanitize_text_field( $data['publishable_key'] ) : false;
1158
1159 if ( charitable_is_debug( 'webhook' ) ) {
1160 // phpcs:disable
1161 error_log( 'save_account_information' );
1162 error_log( print_r( $settings, true ) );
1163 // phpcs:enable
1164 }
1165
1166 update_option( 'charitable_settings', $settings );
1167
1168 // Don't show the success message if we are on the welcome / getting started page (otherwise it's fine).
1169 if ( ! isset( $_GET['page'] ) || 'charitable-getting-started' !== $_GET['page'] ) { // phpcs:ignore
1170 charitable_get_admin_notices()->add_success( __( 'Stripe Information Updated', 'charitable' ) );
1171 }
1172
1173 do_action( 'charitable_save_account_information_gateway_' . $gateway_id, $settings, $data, $gateway_id );
1174 }
1175
1176 /**
1177 * Disconnects from Stripe by removing associated account information.
1178 *
1179 * This does not deauthorize the application within the Stripe account.
1180 *
1181 * @since 4.2.2
1182 *
1183 * @return void
1184 */
1185 public function disconnect_gateways() {
1186
1187 // Do not need to handle this request, bail.
1188 if (
1189 ! ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] ) ||
1190 ! isset( $_GET['wpcharitable-stripe-disconnect'] )
1191 ) {
1192 return;
1193 }
1194
1195 // Current user cannot handle this request.
1196 if ( ! current_user_can( 'manage_options' ) ) {
1197 return;
1198 }
1199
1200 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display/routing parameter
1201 if ( ! isset( $_GET['_wpnonce'] ) ) {
1202 return;
1203 }
1204
1205 // Invalid nonce, bail.
1206 // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1207 if ( ! wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), 'wpcharitable-stripe-connect-disconnect' ) ) {
1208 return;
1209 }
1210
1211 $settings = get_option( 'charitable_settings' );
1212
1213 unset( $settings['gateways_stripe']['live_secret_key'] );
1214 unset( $settings['gateways_stripe']['live_public_key'] );
1215 unset( $settings['gateways_stripe']['test_secret_key'] );
1216 unset( $settings['gateways_stripe']['test_public_key'] );
1217
1218 update_option( 'charitable_settings', $settings );
1219
1220 // update the option to track that we are using stripe connect.
1221 delete_option( 'charitable_using_stripe_connect' );
1222
1223 charitable_get_admin_notices()->add_success( __( 'Stripe Connected Removed', 'charitable' ) );
1224
1225 do_action( 'charitable_remove_connection_gateway_stripe', $settings );
1226
1227 $redirect_url = add_query_arg(
1228 array(
1229 'tab' => 'gateways',
1230 'page' => 'charitable-settings',
1231 'group' => 'gateways_stripe',
1232 ),
1233 admin_url( 'admin.php' )
1234 );
1235
1236 wp_safe_redirect( $redirect_url );
1237 exit;
1238 }
1239
1240 /**
1241 * Disconnects from Square by removing associated account information.
1242 * Also remove webhooks from the Square account.
1243 *
1244 * This does not deauthorize the application within the Square account.
1245 * LEGIT BEING CALLED.
1246 *
1247 * @since 1.8.7
1248 *
1249 * @return void
1250 */
1251 public function disconnect_gateways_square() {
1252
1253 // Do not need to handle this request, bail.
1254 if (
1255 ! ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] ) ||
1256 ! isset( $_GET['wpcharitable-square-disconnect'] ) ||
1257 charitable_square_legacy_mode()
1258 ) {
1259 return;
1260 }
1261
1262 // We need to know if this is a sandbox or live disconnect.
1263 if ( ! isset( $_GET['mode'] ) ) {
1264 return;
1265 }
1266
1267 $mode = sanitize_text_field( wp_unslash( $_GET['mode'] ) );
1268 $mode = 'sandbox' === $mode ? 'test' : 'live';
1269
1270 // Current user cannot handle this request or nonce is invalid.
1271 // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1272 if ( ! current_user_can( 'manage_options' ) || ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), 'wpcharitable-square-connect-disconnect' ) ) {
1273 if ( charitable_is_debug( 'webhook' ) ) {
1274 error_log( 'disconnect_gateways_square - user cannot manage options or nonce is invalid' ); // phpcs:ignore
1275 }
1276 return;
1277 }
1278
1279 $settings = get_option( 'charitable_settings' );
1280
1281 // Delete the Square connection error notice.
1282 delete_option( 'charitable_square_connection_error_notice' );
1283
1284 // Attempt to disconnect / remove webhooks from the Square account.
1285 $this->remove_webhooks_from_square_account( $mode, $settings );
1286
1287 $settings['gateways_square'][ $mode ] = array();
1288
1289 update_option( 'charitable_settings', $settings );
1290
1291 // remove the option to track that we are using square connect (test or live).
1292 $charitable_using_square_connect = get_option( 'charitable_using_square_connect', array() );
1293 if ( isset( $charitable_using_square_connect[ $mode ] ) ) {
1294 unset( $charitable_using_square_connect[ $mode ] );
1295 update_option( 'charitable_using_square_connect', $charitable_using_square_connect );
1296 }
1297
1298 // translators: %s is the mode of the connection.
1299 charitable_get_admin_notices()->add_success( sprintf( __( 'Your connection to Square in %s mode has been removed.', 'charitable' ), $mode ) );
1300
1301 do_action( 'charitable_remove_connection_gateway_square', $settings );
1302
1303 $redirect_url = add_query_arg(
1304 array(
1305 'tab' => 'gateways',
1306 'page' => 'charitable-settings',
1307 'group' => 'gateways_square_core',
1308 ),
1309 admin_url( 'admin.php' )
1310 );
1311
1312 wp_safe_redirect( $redirect_url );
1313 exit;
1314 }
1315
1316 /**
1317 * Redirects to the gateway settings page once a gateway has been activated.
1318 *
1319 * @since 1.7.0
1320 *
1321 * @param string $gateway Gateway ID.
1322 * @return void
1323 */
1324 public function redirect_gateway_settings( $gateway ) {
1325
1326 $settings_url = add_query_arg(
1327 array(
1328 'group' => 'gateways_' . $gateway,
1329 ),
1330 admin_url( 'admin.php?page=charitable-settings&tab=gateways' )
1331 );
1332
1333 wp_safe_redirect( $settings_url );
1334 exit;
1335 }
1336
1337 /**
1338 * Sort the active gateways, placing the default gateway first.
1339 *
1340 * @since 1.4.0
1341 *
1342 * @param string $a Gateway to compare.
1343 * @param string $b Gateway to compare against.
1344 * @return int
1345 */
1346 protected function sort_by_default( $a, $b ) {
1347 $default = $this->get_default_gateway();
1348
1349 if ( $a == $default ) { // phpcs:ignore
1350 return -1;
1351 }
1352
1353 if ( $b == $default ) { // phpcs:ignore
1354 return 1;
1355 }
1356
1357 return 0;
1358 }
1359
1360 /**
1361 * Remove webhooks from the Square account.
1362 *
1363 * @since 1.8.7
1364 *
1365 * @param string $mode The mode to remove the webhooks from.
1366 * @param array $settings The settings array.
1367 * @param bool $force_remove Whether to force remove the webhooks.
1368 * @return void
1369 */
1370 public function remove_webhooks_from_square_account( $mode, $settings = array(), $force_remove = true ) {
1371
1372 if ( charitable_is_debug( 'square' ) ) {
1373 error_log( 'remove_webhooks_from_square_account' ); // phpcs:ignore
1374 }
1375
1376 // Check if settings are empty and if so, get the settings from the database.
1377 if ( empty( $settings ) ) {
1378 $settings = get_option( 'charitable_settings' );
1379 }
1380
1381 $access_token = $settings['gateways_square'][ $mode ]['access_token'];
1382
1383 // Test to see if we have the access token.
1384 if ( empty( $access_token ) ) {
1385 // if force_remove is true, we can remove the webhooks from the Square account in Charitable.
1386 if ( $force_remove ) {
1387 if ( charitable_is_debug( 'square' ) ) {
1388 error_log( 'remove_webhooks_from_square_account:force_remove' ); // phpcs:ignore
1389 }
1390 $settings['gateways_square'][ $mode ]['webhooks'] = array();
1391 update_option( 'charitable_settings', $settings );
1392 return;
1393
1394 }
1395 return;
1396 } elseif ( isset( $settings['gateways_square'][ $mode ]['webhook_id'] ) ) {
1397 $webhook_manager = new Charitable_Square_WebhooksManager( $mode, $access_token );
1398 $result = $webhook_manager->remove_webhook( $access_token, $settings['gateways_square'][ $mode ]['webhook_id'] );
1399
1400 }
1401
1402 if ( charitable_is_debug( 'square' ) ) {
1403 error_log( 'remove_webhooks_from_square_account:deactivate_webhook' ); // phpcs:ignore
1404 }
1405
1406 return $result;
1407 }
1408
1409 /**
1410 * Ensure there is a settings option when landing on settings pages, even if init blank. Mostly applicable to new installs.
1411 *
1412 * @since 1.4.0
1413 * @since 1.8.1.11
1414 *
1415 * @return void
1416 */
1417 public function check_settings() {
1418
1419 if ( isset( $_GET['page'] ) && 'charitable-settings' !== $_GET['page'] ) { // phpcs:ignore
1420 return;
1421 }
1422
1423 $settings = get_option( 'charitable_settings' );
1424
1425 // attach this to CHARITABLE_DEBUG constant.
1426 if ( charitable_is_debug() ) {
1427 // phpcs:disable
1428 // error_log( 'checking_settings' );
1429 // error_log( print_r( $settings, true ) );
1430 // phpcs:enable
1431 }
1432
1433 // If there is no settings, or the settings are empty, set the test mode to true and stripe is default gateway.
1434 if ( false === $settings || ( is_array( $settings ) && empty( $settings ) ) ) {
1435 $this->enable_gateway( 'stripe', false );
1436 $settings = (array) get_option( 'charitable_settings' );
1437 $settings['test_mode'] = true;
1438 update_option( 'charitable_settings', $settings );
1439 }
1440 }
1441
1442 /**
1443 * Check the setup stored value of the payment methods.
1444 *
1445 * @since 1.8.4
1446 *
1447 * @return bool
1448 */
1449 public function check_setup_payment_methods() {
1450
1451 $payment_methods = get_option( 'charitable_setup_payment_methods', array() );
1452
1453 if ( ! is_array( $payment_methods ) ) {
1454 return false;
1455 }
1456
1457 $payment_methods = array_map( 'sanitize_text_field', $payment_methods );
1458
1459 if ( empty( $payment_methods ) ) {
1460 return false;
1461 }
1462
1463 $gateways_enbled = 0;
1464
1465 if ( in_array( 'stripe', $payment_methods, true ) ) {
1466 // check if this gateway is already enabled.
1467 if ( ! $this->is_active_gateway( 'stripe' ) ) {
1468 $this->enable_gateway( 'stripe', false );
1469 ++$gateways_enbled;
1470 }
1471 }
1472
1473 if ( in_array( 'paypal', $payment_methods, true ) ) {
1474 // check if this gateway is already enabled.
1475 if ( ! $this->is_active_gateway( 'paypal' ) ) {
1476 $this->enable_gateway( 'paypal', false );
1477 ++$gateways_enbled;
1478 }
1479 }
1480
1481 if ( in_array( 'offline', $payment_methods, true ) ) {
1482 // check if this gateway is already enabled.
1483 if ( ! $this->is_active_gateway( 'offline' ) ) {
1484 $this->enable_gateway( 'offline', false );
1485 }
1486 }
1487
1488 if ( $gateways_enbled > 0 && class_exists( 'Charitable_Checklist' ) ) {
1489 $checklist_class = Charitable_Checklist::get_instance();
1490 $checklist_class->mark_step_completed( 'connect-payment' );
1491 }
1492
1493 return true;
1494 }
1495
1496 /**
1497 * Display the test and live fields that represent the manual keys for Stripe that users might be already using prior to 1.7.0.
1498 *
1499 * @since 1.7.0
1500 */
1501 public function show_stripe_manual_keys_ajax() {
1502
1503 if ( ! isset( $_POST ) || 'charitable-show-stripe-keys' !== $_POST['action'] ) { // phpcs:ignore
1504 return;
1505 }
1506
1507 // Invalid nonce, bail.
1508 if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'charitable-show-stripe-keys' ) ) { // phpcs:ignore
1509 return;
1510 }
1511
1512 // Current user cannot handle this request.
1513 if ( ! current_user_can( 'manage_options' ) ) {
1514 return;
1515 }
1516
1517 $settings = get_option( 'charitable_settings' );
1518
1519 if ( false === $settings ) {
1520 return;
1521 }
1522
1523 $gateway_id = 'stripe';
1524
1525 $live_secret_key = $settings[ 'gateways_' . $gateway_id ]['live_secret_key'];
1526 $live_public_key = $settings[ 'gateways_' . $gateway_id ]['live_public_key'];
1527 $test_secret_key = $settings[ 'gateways_' . $gateway_id ]['test_secret_key'];
1528 $test_public_key = $settings[ 'gateways_' . $gateway_id ]['test_public_key'];
1529
1530 $html = '<tr><th scope="row">Live Settings</th><td><hr />
1531 </td></tr><tr class="wide"><th scope="row">Live Secret Key</th><td><input type="text"
1532 id="charitable_settings_gateways_stripe_live_secret_key"
1533 name="charitable_settings[gateways_stripe][live_secret_key]"
1534 value="' . trim( $live_secret_key ) . '"
1535 class="charitable-settings-field wide"
1536 />
1537 </td></tr><tr class="wide"><th scope="row">Live Publishable Key</th><td><input type="text"
1538 id="charitable_settings_gateways_stripe_live_public_key"
1539 name="charitable_settings[gateways_stripe][live_public_key]"
1540 value="' . trim( $live_public_key ) . '"
1541 class="charitable-settings-field wide"
1542 />
1543 </td></tr><tr><th scope="row">Test Settings</th><td><hr />
1544 </td></tr><tr class="wide"><th scope="row">Test Secret Key</th><td><input type="text"
1545 id="charitable_settings_gateways_stripe_test_secret_key"
1546 name="charitable_settings[gateways_stripe][test_secret_key]"
1547 value="' . trim( $test_secret_key ) . '"
1548 class="charitable-settings-field wide"
1549 />
1550 </td></tr><tr class="wide"><th scope="row">Test Publishable Key</th><td><input type="text"
1551 id="charitable_settings_gateways_stripe_test_public_key"
1552 name="charitable_settings[gateways_stripe][test_public_key]"
1553 value="' . trim( $test_public_key ) . '"
1554 class="charitable-settings-field wide"
1555 />
1556 </td></tr>';
1557
1558 wp_send_json_success( $html );
1559 }
1560
1561 /**
1562 * Returns the error message for the square connect gateway.
1563 *
1564 * @since 1.8.7
1565 *
1566 * @return void
1567 */
1568 public function get_gateway_error_message() {
1569
1570 if ( ! isset( $_GET['square_connect_error'] ) && ! isset( $_GET['square_connect_error_description'] ) ) { // phpcs:ignore
1571 return;
1572 }
1573
1574 $square_connect_error = sanitize_text_field( wp_unslash( $_GET['square_connect_error'] ) ); // phpcs:ignore
1575 $square_connect_error_description = sanitize_text_field( wp_unslash( $_GET['square_connect_error_description'] ) ); // phpcs:ignore
1576 $support_link = 'https://wpcharitable.com/support';
1577
1578 switch ( $square_connect_error ) {
1579 case 'access_denied':
1580 switch ( $square_connect_error_description ) {
1581 case 'user_denied':
1582 $message = __( 'You have denied access to Square Connect.', 'charitable' );
1583 break;
1584 case 'missing_state_or_code':
1585 $message = sprintf(
1586 /* translators: %s: Support link URL */
1587 __( 'There has been an issue with your request. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1588 $support_link
1589 );
1590 break;
1591 case 'missing_init_request':
1592 $message = sprintf(
1593 /* translators: %s: Support link URL */
1594 __( 'There has been an issue confirming your request. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1595 $support_link
1596 );
1597 break;
1598 default:
1599 $message = sprintf(
1600 /* translators: %s: Support link URL */
1601 __( 'Your connect to Square has been canceled for an unknown reason. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1602 $support_link
1603 );
1604 break;
1605 }
1606 break;
1607 case 'connect_error':
1608 switch ( $square_connect_error_description ) {
1609 case 'error_refreshing_token':
1610 $refresh_by_date = Charitable_Gateway_Square::get_instance()->get_personal_access_token_should_refresh( 'test' );
1611 $message = sprintf(
1612 /* translators: %s: Refresh by date */
1613 __( 'There has been an issue refreshing your Square token. Token should be refreshed by: %1$s. Please try again or <a href="%2$s">contact support</a>.', 'charitable' ),
1614 $refresh_by_date,
1615 $support_link
1616 );
1617 break;
1618 case 'missing_response_fields':
1619 case 'credentials_not_saved':
1620 $message = sprintf(
1621 /* translators: %s: Support link URL */
1622 __( 'There has been an issue confirming your Square token. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1623 $support_link
1624 );
1625 break;
1626 case 'response_error':
1627 case 'merchant_id_not_found':
1628 case 'save_credentials_error':
1629 case 'missing_state_or_code':
1630 case 'invalid_request':
1631 case 'error_getting_credentials':
1632 case 'missing_state_or_site_url':
1633 case 'error_getting_credentials':
1634 case 'missing_information_for_gateway_connect':
1635 case 'error_saving_init_request':
1636 $message = sprintf(
1637 /* translators: %s: Support link URL */
1638 __( 'There has been an issue communicating with Square. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1639 $support_link
1640 );
1641 break;
1642 default:
1643 $message = sprintf(
1644 /* translators: %s: Support link URL */
1645 __( 'There has been an issue with your request. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1646 $support_link
1647 );
1648 break;
1649 }
1650 break;
1651 default:
1652 $message = sprintf(
1653 /* translators: %s: Support link URL */
1654 __( 'There has been an issue with your request. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1655 $support_link
1656 );
1657 }
1658
1659 charitable_get_admin_notices()->add_notice( $message, 'error', false, true );
1660 }
1661
1662 /**
1663 * Get the personal access refresh token.
1664 *
1665 * @since 1.8.7
1666 *
1667 * @param string $mode The mode to get the personal access refresh token for.
1668 * @return string
1669 */
1670 private function get_personal_access_refresh_token( $mode = 'test' ) {
1671 $square_settings = charitable_get_option( 'gateways_square', array() );
1672
1673 if ( ! empty( $square_settings[ $mode ]['refresh_token'] ) ) {
1674 return $square_settings[ $mode ]['refresh_token'];
1675 }
1676
1677 return '';
1678 }
1679
1680 /**
1681 * Add custom cron schedules.
1682 *
1683 * @since 1.8.7
1684 *
1685 * @param array $schedules Existing cron schedules.
1686 * @return array
1687 */
1688 public function add_cron_schedules( $schedules ) {
1689 $schedules['14days'] = array(
1690 'interval' => 14 * DAY_IN_SECONDS,
1691 'display' => __( 'Every 14 days', 'charitable' ),
1692 );
1693 return $schedules;
1694 }
1695
1696 /**
1697 * Handle Square gateway switch.
1698 *
1699 * @since 1.8.7
1700 * @version 1.8.9.1
1701 *
1702 * @return void
1703 */
1704 public function handle_square_gateway_switch() {
1705 // phpcs:disable WordPress.Security.NonceVerification.Missing
1706 // Verify nonce.
1707 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1708 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_nonce' ) ) {
1709 wp_send_json_error( array( 'message' => __( 'Security check failed.', 'charitable' ) ) );
1710 }
1711 // phpcs:enable WordPress.Security.NonceVerification.Missing
1712
1713 // Check user permissions.
1714 if ( ! current_user_can( 'manage_options' ) ) {
1715 wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'charitable' ) ) );
1716 }
1717
1718 // Check if Square Legacy is active and Square Core is not active.
1719 if ( ! $this->is_active_gateway( 'square' ) || $this->is_active_gateway( 'square_core' ) ) {
1720 wp_send_json_error( array( 'message' => __( 'Invalid gateway state for switching.', 'charitable' ) ) );
1721 }
1722
1723 // Disable Square Legacy.
1724 $this->disable_gateway( 'square' );
1725
1726 // Enable Square Core.
1727 $this->enable_gateway( 'square_core', false );
1728
1729 // Get the redirect URL for Square Core settings.
1730 $redirect_url = add_query_arg(
1731 array(
1732 'page' => 'charitable-settings',
1733 'tab' => 'gateways',
1734 'group' => 'gateways_square_core',
1735 ),
1736 admin_url( 'admin.php' )
1737 );
1738
1739 wp_send_json_success( array( 'redirect_url' => $redirect_url ) );
1740 }
1741
1742 /**
1743 * Handle Square core to legacy switch.
1744 *
1745 * @since 1.8.7
1746 * @version 1.8.9.1
1747 *
1748 * @return void
1749 */
1750 public function handle_square_core_to_legacy_switch() {
1751 // phpcs:disable WordPress.Security.NonceVerification.Missing
1752 // Verify nonce.
1753 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1754 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_nonce' ) ) {
1755 wp_send_json_error( array( 'message' => __( 'Security check failed.', 'charitable' ) ) );
1756 }
1757 // phpcs:enable WordPress.Security.NonceVerification.Missing
1758
1759 // Check user permissions.
1760 if ( ! current_user_can( 'manage_options' ) ) {
1761 wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'charitable' ) ) );
1762 }
1763
1764 // Check if Square Core is active and Square Legacy is not active.
1765 if ( ! $this->is_active_gateway( 'square_core' ) || $this->is_active_gateway( 'square' ) ) {
1766 wp_send_json_error( array( 'message' => __( 'Invalid gateway state for switching.', 'charitable' ) ) );
1767 }
1768
1769 if ( charitable_is_debug() ) {
1770 error_log( 'Charitable: Starting Square Core to Legacy gateway switch process.' ); // phpcs:ignore
1771 }
1772
1773 // Disconnect Square Core connections for both live and test modes.
1774 $this->disconnect_square_core_connections();
1775
1776 // Disable Square Core.
1777 $this->disable_gateway( 'square_core' );
1778
1779 // Enable Square Legacy.
1780 $this->enable_gateway( 'square', false );
1781
1782 if ( charitable_is_debug() ) {
1783 error_log( 'Charitable: Successfully switched from Square Core to Square Legacy gateway.' ); // phpcs:ignore
1784 }
1785
1786 // Get the redirect URL for Square Legacy settings.
1787 $redirect_url = add_query_arg(
1788 array(
1789 'page' => 'charitable-settings',
1790 'tab' => 'gateways',
1791 'group' => 'gateways_square',
1792 ),
1793 admin_url( 'admin.php' )
1794 );
1795
1796 wp_send_json_success( array( 'redirect_url' => $redirect_url ) );
1797 }
1798
1799 /**
1800 * Disconnect Square Core connections for both live and test modes.
1801 *
1802 * @since 1.8.7
1803 *
1804 * @return void
1805 */
1806 private function disconnect_square_core_connections() {
1807
1808 if ( charitable_is_debug() ) {
1809 error_log( 'Charitable: Starting Square Core connection disconnection process.' ); // phpcs:ignore
1810 }
1811
1812 // Check if Charitable_Square_Connection class exists.
1813 if ( ! class_exists( 'Charitable_Square_Connection' ) ) {
1814 if ( charitable_is_debug() ) {
1815 error_log( 'Charitable: Charitable_Square_Connection class not found. Skipping disconnect process.' ); // phpcs:ignore
1816 }
1817 return;
1818 }
1819
1820 // Check and disconnect live mode connection.
1821 $live_connection = Charitable_Square_Connection::get( 'live' );
1822 if ( $live_connection && $live_connection->is_configured() ) {
1823 if ( charitable_is_debug() ) {
1824 error_log( 'Charitable: Found active Square Core live connection. Attempting to disconnect.' ); // phpcs:ignore
1825 }
1826 $this->disconnect_square_core_mode( $live_connection, 'live' );
1827 } elseif ( charitable_is_debug() ) {
1828 error_log( 'Charitable: No active Square Core live connection found.' ); // phpcs:ignore
1829
1830 }
1831
1832 // Check and disconnect test mode connection.
1833 $test_connection = Charitable_Square_Connection::get( 'test' );
1834 if ( $test_connection && $test_connection->is_configured() ) {
1835 if ( charitable_is_debug() ) {
1836 error_log( 'Charitable: Found active Square Core test connection. Attempting to disconnect.' ); // phpcs:ignore
1837 }
1838 $this->disconnect_square_core_mode( $test_connection, 'test' );
1839 } elseif ( charitable_is_debug() ) {
1840 error_log( 'Charitable: No active Square Core test connection found.' ); // phpcs:ignore
1841
1842 }
1843
1844 if ( charitable_is_debug() ) {
1845 error_log( 'Charitable: Completed Square Core connection disconnection process.' ); // phpcs:ignore
1846 }
1847 }
1848
1849 /**
1850 * Disconnect Square Core connection for a specific mode.
1851 *
1852 * @since 1.8.7
1853 *
1854 * @param Charitable_Square_Connection $connection The connection object.
1855 * @param string $mode The mode (live/test).
1856 * @return void
1857 */
1858 private function disconnect_square_core_mode( $connection, $mode ) {
1859
1860 if ( charitable_is_debug() ) {
1861 error_log( 'Charitable: Disconnecting Square Core connection for mode: ' . $mode ); // phpcs:ignore
1862 }
1863
1864 // Check if Charitable_Square_Connect class exists.
1865 if ( ! class_exists( 'Charitable_Square_Connect' ) ) {
1866 if ( charitable_is_debug() ) {
1867 error_log( 'Charitable: Charitable_Square_Connect class not found. Cannot disconnect mode: ' . $mode ); // phpcs:ignore
1868 }
1869 return;
1870 }
1871
1872 try {
1873 // Get the Square Connect instance.
1874 $square_connect = Charitable_Square_Connect::get_instance();
1875
1876 // Use reflection to access the private handle_disconnect method.
1877 $reflection = new ReflectionClass( $square_connect );
1878 $method = $reflection->getMethod( 'handle_disconnect' );
1879 $method->setAccessible( true );
1880
1881 // Set the live_mode parameter for the disconnect process.
1882 $_GET['live_mode'] = ( $mode === 'live' ) ? 1 : 0;
1883
1884 // Call the disconnect method without redirect.
1885 $method->invoke( $square_connect, false );
1886
1887 if ( charitable_is_debug() ) {
1888 error_log( 'Charitable: Successfully disconnected Square Core connection for mode: ' . $mode ); // phpcs:ignore
1889 }
1890 } catch ( Exception $e ) {
1891 if ( charitable_is_debug() ) {
1892 error_log( 'Charitable: Error disconnecting Square Core connection for mode ' . $mode . ': ' . $e->getMessage() ); // phpcs:ignore
1893 }
1894 }
1895 }
1896
1897 /**
1898 * Dequeue Square Legacy scripts if Square Legacy is not active.
1899 *
1900 * @since 1.8.7
1901 *
1902 * @return void
1903 */
1904 public function maybe_dequeue_square_legacy_scripts() {
1905 // Only run on frontend.
1906 if ( is_admin() ) {
1907 return;
1908 }
1909
1910 // Debug: Log the status checks.
1911 if ( charitable_is_debug( 'square' ) ) {
1912 error_log( '[Charitable Square] Script dequeue check - Legacy mode: ' . ( charitable_square_legacy_mode() ? 'true' : 'false' ) ); // phpcs:ignore
1913 error_log( '[Charitable Square] Script dequeue check - Square gateway active: ' . ( $this->is_active_gateway( 'square' ) ? 'true' : 'false' ) ); // phpcs:ignore
1914 }
1915
1916 // Check if Square Legacy is active.
1917 if ( ! charitable_square_legacy_mode() || ! $this->is_active_gateway( 'square' ) ) {
1918 // Dequeue Square Legacy scripts and styles.
1919 wp_dequeue_script( 'charitable-square' );
1920 wp_dequeue_script( 'square' );
1921 wp_dequeue_style( 'charitable-square' );
1922
1923 // Deregister scripts to prevent them from being loaded.
1924 wp_deregister_script( 'charitable-square' );
1925 wp_deregister_script( 'square' );
1926 wp_deregister_style( 'charitable-square' );
1927
1928 if ( charitable_is_debug( 'square' ) ) {
1929 error_log( '[Charitable Square] Dequeued Square Legacy scripts - Square Legacy is not active' ); // phpcs:ignore
1930 }
1931 } elseif ( charitable_is_debug( 'square' ) ) {
1932 error_log( '[Charitable Square] Square Legacy scripts will remain loaded - Square Legacy is active' ); // phpcs:ignore
1933
1934 }
1935 }
1936
1937 /**
1938 * Check if charitable-square plugin is active.
1939 *
1940 * @since 1.8.7
1941 *
1942 * @return boolean
1943 */
1944 private function is_square_addon_active() {
1945 return is_plugin_active( 'charitable-square/charitable-square.php' );
1946 }
1947
1948 /**
1949 * Force cleanup of 'square' gateway if charitable-square plugin is not active.
1950 * Runs once per 24 hours (adjustable via filter) to avoid constant database checks.
1951 *
1952 * @since 1.8.7
1953 *
1954 * @param array $active_gateways The current active gateways array.
1955 * @return void
1956 */
1957 private function maybe_force_cleanup_square_gateway( $active_gateways ) {
1958 // Check if we need to run cleanup (once per 24 hours).
1959 $transient_name = 'charitable_square_cleanup_last_run';
1960 $check_interval = apply_filters( 'charitable_square_cleanup_check_interval', 24 * HOUR_IN_SECONDS );
1961
1962 if ( get_transient( $transient_name ) ) {
1963 return;
1964 }
1965
1966 // Check if 'square' gateway exists and charitable-square plugin is not active.
1967 if ( isset( $active_gateways['square'] ) && ! $this->is_square_addon_active() ) {
1968 try {
1969 // Get current settings.
1970 $settings = get_option( 'charitable_settings', array() );
1971
1972 if ( ! empty( $settings['active_gateways']['square'] ) ) {
1973 // Remove 'square' gateway from active gateways.
1974 unset( $settings['active_gateways']['square'] );
1975
1976 // Update default gateway if it was set to 'square'.
1977 if ( isset( $settings['default_gateway'] ) && 'square' === $settings['default_gateway'] ) {
1978 $settings['default_gateway'] = '';
1979 }
1980
1981 // Remove the option that warns users about incorrect square settings.
1982 delete_option( 'charitable_square_mode_connection_warning' );
1983
1984 // Save updated settings.
1985 $updated = update_option( 'charitable_settings', $settings );
1986
1987 if ( $updated ) {
1988 if ( charitable_is_debug( 'square' ) ) {
1989 error_log( '[Charitable Square] Successfully removed legacy square gateway from active gateways' ); // phpcs:ignore
1990 }
1991 } else {
1992 if ( charitable_is_debug( 'square' ) ) {
1993 error_log( '[Charitable Square] Failed to update settings when removing legacy square gateway' ); // phpcs:ignore
1994 }
1995 }
1996 }
1997 } catch ( Exception $e ) {
1998 if ( charitable_is_debug( 'square' ) ) {
1999 error_log( '[Charitable Square] Error during square gateway cleanup: ' . $e->getMessage() ); // phpcs:ignore
2000 }
2001 }
2002 }
2003
2004 // Set transient to prevent running again for the specified interval.
2005 set_transient( $transient_name, time(), $check_interval );
2006 }
2007 }
2008
2009 endif;
2010