PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.11.3
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.11.3
1.8.13.1 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 All 212 releases
charitable / includes / square / admin / class-charitable-square-connect.php

class-charitable-square-connect.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.11.3, at includes/square/admin/class-charitable-square-connect.php

1,030 lines 26.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The class responsible for adding & saving extra settings in the Charitable admin.
4 *
5 * @package Charitable Square/Classes/Charitable_Square_Admin
6 * @author David Bisset
7 * @copyright Copyright (c) 2021-2022, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.1.0
10 * @version 1.3.0
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 use Square\Legacy\Models\Location;
19 use Square\Legacy\Models\LocationStatus;
20 use Square\Legacy\Models\LocationCapability;
21
22 if ( ! class_exists( 'Charitable_Square_Connect' ) ) :
23
24 /**
25 * Charitable_Square_Admin
26 *
27 * @since 1.1.0
28 */
29 class Charitable_Square_Connect {
30
31 /**
32 * Charitable website URL.
33 *
34 * @since 1.8.7
35 */
36 private const CHARITABLE_SQUARE_CONNECT_URL = 'https://connect.wpcharitable.com';
37
38 /**
39 * Webhooks manager.
40 *
41 * @since 1.8.7
42 *
43 * @var WebhooksManager
44 */
45 private $webhooks_manager;
46
47 /**
48 * Single instance of this class.
49 *
50 * @since 1.1.0
51 *
52 * @var Charitable_Square_Connect
53 */
54 private static $instance = null;
55
56 /**
57 * Create class object. Private constructor.
58 *
59 * @since 1.1.0
60 */
61 public function __construct() {
62 }
63
64 /**
65 * Initialize the class.
66 *
67 * @since 1.8.7
68 */
69 public function init() {
70
71 $this->webhooks_manager = new Charitable_Square_WebhooksManager();
72
73 $this->hooks();
74
75 return $this;
76 }
77
78 /**
79 * Hooks.
80 *
81 * @since 1.8.7
82 */
83 public function hooks() {
84 add_action( 'charitable_square_refresh_connection', array( $this, 'refresh_connection_schedule' ) );
85 add_action( 'wp_ajax_charitable_square_refresh_connection', array( $this, 'refresh_connection_manual' ) );
86 }
87
88 /**
89 * Handle actions.
90 *
91 * @since 1.8.7
92 */
93 public function handle_actions() {
94
95 if ( ! charitable_current_user_can( 'manage_options' ) || wp_doing_ajax() ) {
96 return;
97 }
98
99 $this->validate_scopes();
100
101 if (
102 isset( $_GET['_wpnonce'] ) &&
103 wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'charitable_square_disconnect' )
104 ) {
105 $this->handle_disconnect();
106
107 return;
108 }
109
110 $this->schedule_refresh();
111
112 if (
113 ! empty( $_GET['state'] ) &&
114 isset( $_GET['square_connect'] ) &&
115 $_GET['square_connect'] === 'complete'
116 ) {
117 $this->handle_connected();
118 }
119 }
120
121 /**
122 * Validate connection scopes.
123 *
124 * @since 1.8.7
125 */
126 private function validate_scopes() {
127
128 if ( Charitable_Square_Helpers::is_license_ok() ) {
129 return;
130 }
131
132 $connection = Charitable_Square_Connection::get();
133
134 if ( ! $connection || ! $connection->is_configured() ) {
135 return;
136 }
137
138 // Bail early if currency is not supported for applying a fee.
139 if ( ! Charitable_Square_Helpers::is_application_fee_supported() ) {
140 return;
141 }
142
143 if ( $connection->get_scopes_updated() ) {
144 return;
145 }
146
147 // Revoke tokens if the license is not valid and scopes are missing.
148 $connection->revoke_tokens();
149 }
150
151 /**
152 * Handle a successful connection.
153 *
154 * @since 1.8.7
155 */
156 private function handle_connected() {
157
158 $state = sanitize_text_field( wp_unslash( $_GET['state'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
159
160 if ( empty( $state ) ) {
161 return;
162 }
163
164 $connection_raw = $this->fetch_new_connection( $state );
165 $connection = $this->maybe_save_connection( $connection_raw );
166
167 if ( ! $connection ) {
168 return;
169 }
170
171 $mode = $connection->get_mode();
172
173 $this->prepare_locations( $mode );
174
175 $redirect_url = add_query_arg(
176 array(
177 'tab' => 'gateways',
178 'page' => 'charitable-settings',
179 'group' => 'gateways_square_core',
180 ),
181 admin_url( 'admin.php' )
182 );
183
184 if ( ! $connection->is_usable() ) {
185 $redirect_url .= '&redirect=true#charitable-setting-row-square-heading';
186 }
187
188 // delete the option that stores the error admin notice.
189 delete_option( 'charitable_square_connection_error_notice' );
190
191 wp_safe_redirect( $redirect_url );
192 exit;
193 }
194
195 /**
196 * Handle disconnection.
197 *
198 * @since 1.8.7
199 * @version 1.8.9.1
200 *
201 * @param bool $redirect Whether to redirect after disconnection.
202 */
203 private function handle_disconnect( $redirect = true ) {
204 // phpcs:disable WordPress.Security.NonceVerification.Recommended
205 if ( isset( $_GET['live_mode'] ) ) {
206 $live_mode = absint( wp_unslash( $_GET['live_mode'] ) );
207 } else {
208 // phpcs:enable WordPress.Security.NonceVerification.Recommended
209 $live_mode = charitable_get_option( 'test_mode' ) ? 0 : 1;
210 }
211 $mode = $live_mode ? 'live' : 'test';
212 $connection = Charitable_Square_Connection::get( $mode );
213
214 // Track the overall success of the disconnection process.
215 $disconnection_success = true;
216 $square_revocation_success = true;
217
218 // Delete the Square connection error notice.
219 delete_option( 'charitable_square_connection_error_notice' );
220
221 // Attempt to revoke the token before deleting local data.
222 if ( $connection && $connection->is_configured() ) {
223 $result = $this->fetch_revoke_connection(
224 $connection->get_merchant_id(),
225 $connection->get_access_token(),
226 $mode
227 );
228
229 if ( is_wp_error( $result ) ) {
230 $square_revocation_success = false;
231 $disconnection_success = false;
232
233 // Add admin notice for Square API revocation failure.
234 Charitable_Settings::get_instance()->add_update_message(
235 sprintf(
236 /* translators: %s: Error message from Square API */
237 __( 'Square Error: Unable to revoke access token. %s', 'charitable' ),
238 $result->get_error_message()
239 ),
240 'error'
241 );
242 }
243 }
244
245 // Attempt to delete local connection data.
246 if ( $connection ) {
247 $delete_result = $connection->delete();
248 if ( ! $delete_result ) {
249 $disconnection_success = false;
250
251 // Add admin notice for local deletion failure.
252 Charitable_Settings::get_instance()->add_update_message(
253 __( 'Error: Unable to remove Square connection from your site. Please try again.', 'charitable' ),
254 'error'
255 );
256 }
257 }
258
259 // Are in live mode (production)?
260 if ( ! charitable_get_option( 'test_mode' ) ) {
261 $this->unschedule_refresh();
262 }
263
264 // Webhooks are now manually configured in Square dashboard - no need to reset.
265 // Previously this was: Charitable_Square_Helpers::reset_webhook_configuration( true );.
266
267 // Not needed because this as stored in the "connection" that was wiped above.
268 $api = new Charitable_Square_API( $connection );
269 $api->set_location_id( '', $mode );
270 $api->set_location_currency( '', $mode );
271 $api->detete_transients( $mode );
272
273 // Add appropriate success/partial success notice.
274 if ( $disconnection_success ) {
275 if ( ! $square_revocation_success ) {
276 // Partial success - local disconnection worked but Square revocation failed.
277 Charitable_Settings::get_instance()->add_update_message(
278 __( 'Square has been disconnected from your site, but there was an issue revoking access on Square\'s end. You may need to manually revoke access in your Square dashboard.', 'charitable' ),
279 'warning'
280 );
281 } else {
282 // Complete success.
283 Charitable_Settings::get_instance()->add_update_message(
284 __( 'Square has been successfully disconnected from your site.', 'charitable' ),
285 'success'
286 );
287 }
288 }
289
290 if ( $redirect ) {
291 wp_safe_redirect( charitable_get_square_settings_page_url() );
292 exit;
293 }
294 }
295
296 /**
297 * Handle refresh connection triggered by AS task.
298 *
299 * @since 1.8.7
300 */
301 public function refresh_connection_schedule() {
302
303 // Don't run refresh tokens for Sandbox connection.
304 if ( Charitable_Square_Helpers::is_sandbox_mode() ) {
305 if ( charitable_is_debug( 'square' ) ) {
306 // phpcs:disable
307 error_log( 'refresh - is sandbox mode' );
308 // phpcs:enable
309 }
310 return;
311 }
312
313 $connection = Charitable_Square_Connection::get();
314
315 // Check connection and cancel AS task if connection is not exists, broken OR already invalid.
316 if ( ! $connection || ! $connection->is_configured() || ! $connection->is_valid() ) {
317 $this->unschedule_refresh();
318 if ( charitable_is_debug( 'square' ) ) {
319 // phpcs:disable
320 error_log( 'refresh - connection is not valid' );
321 // phpcs:enable
322 }
323 return;
324 }
325
326 // If connection is not expired, we'll just fetch active locations.
327 if ( ! $connection->is_expired() ) {
328 $this->prepare_locations( $connection->get_mode() );
329 if ( charitable_is_debug( 'square' ) ) {
330 // phpcs:disable
331 error_log( 'refresh - connection is not expired' );
332 // phpcs:enable
333 }
334 return;
335 }
336
337 // If connection is expired, try to refresh tokens.
338 $connection = $this->try_refresh_connection( $connection );
339
340 if ( is_wp_error( $connection ) ) {
341 if ( charitable_is_debug( 'square' ) ) {
342 // phpcs:disable
343 error_log( 'refresh - connection is wp error' );
344 error_log( print_r( $connection, true ) ); // phpcs:ignore
345 // phpcs:enable
346 }
347 return;
348 }
349
350 // If connection is invalid, we'll cancel AS task.
351 if ( $connection && ! $connection->is_valid() ) {
352 $this->unschedule_refresh();
353 if ( charitable_is_debug( 'square' ) ) {
354 // phpcs:disable
355 error_log( 'refresh - connection is invalid' );
356 // phpcs:enable
357 }
358 return;
359 }
360
361 // Make sure and check connection tokens through fetching active locations.
362 $this->prepare_locations( $connection->get_mode() );
363 }
364
365 /**
366 * Handle refresh connection triggered manually.
367 *
368 * @since 1.8.7
369 */
370 public function refresh_connection_manual() {
371
372 // Security and permissions check.
373 if (
374 ! check_ajax_referer( 'charitable-admin', 'nonce', false ) ||
375 ! current_user_can( 'manage_options' )
376 ) {
377 wp_send_json_error( esc_html__( 'You are not allowed to perform this action', 'charitable' ) );
378 }
379
380 $error_general = esc_html__( 'Something went wrong while performing a refresh tokens request', 'charitable' );
381
382 // Required data check.
383 if ( empty( $_POST['mode'] ) ) {
384 wp_send_json_error( $error_general );
385 }
386
387 $mode = sanitize_key( $_POST['mode'] );
388 $connection = Charitable_Square_Connection::get( $mode );
389
390 // Connection check.
391 if ( ! $connection || ! $connection->is_configured() ) {
392 wp_send_json_error( $error_general );
393 }
394
395 // Try to refresh connection.
396 $connection = $this->try_refresh_connection( $connection );
397
398 if ( charitable_is_debug( 'square' ) ) {
399 // phpcs:disable
400 error_log( 'refresh_connection_manual - connection' );
401 error_log( print_r( $connection, true ) ); // phpcs:ignore
402 // phpcs:enable
403 }
404
405 if ( is_wp_error( $connection ) ) {
406 $error_specific = $connection->get_error_message();
407 $error_message = empty( $error_specific ) ? $error_general : $error_general . ': ' . $error_specific;
408
409 wp_send_json_error( $error_message );
410 }
411
412 $this->prepare_locations( $mode, $connection );
413
414 wp_send_json_success();
415 }
416
417 /**
418 * Try to refresh connection.
419 *
420 * @since 1.8.7
421 *
422 * @param Connection $connection Connection object.
423 *
424 * @return Connection|WP_Error
425 */
426 private function try_refresh_connection( $connection ) {
427
428 if ( charitable_is_debug( 'square' ) ) {
429 // phpcs:disable
430 error_log( 'try_refresh_connection - trying to refresh connection' );
431 error_log( 'try_refresh_connection - refresh token start' );
432 error_log( $connection->get_refresh_token() );
433 error_log( 'try_refresh_connection - refresh token end' );
434 error_log( 'try_refresh_connection - mode start' );
435 error_log( $connection->get_mode() );
436 error_log( 'try_refresh_connection - mode end' );
437 // phpcs:enable
438 }
439
440 $response = $this->fetch_refresh_connection( $connection->get_refresh_token(), $connection->get_mode() );
441
442 if ( is_wp_error( $response ) ) {
443
444 if ( charitable_is_debug( 'square' ) ) {
445 error_log( 'try_refresh_connection - wp error: ' . print_r( $response, true ) ); // phpcs:ignore
446 }
447
448 if ( $response->get_error_code() === 'refresh_connection_fail' && $connection->is_valid() ) {
449 $connection
450 ->set_status( Charitable_Square_Connection::STATUS_INVALID )
451 ->save(); // not it.
452 }
453
454 return $response;
455 }
456
457 if ( charitable_is_debug( 'square' ) ) {
458 // phpcs:disable
459 error_log( 'refresh - is not wp error' );
460 error_log( print_r( $response, true ) );
461 // phpcs:enable
462 }
463
464 $refreshed_connection = $this->maybe_save_connection( $response, true );
465
466 return $refreshed_connection ?? new WP_Error();
467 }
468
469 /**
470 * Schedule the connection refresh using WP Cron.
471 *
472 * @since 1.8.7
473 */
474 private function schedule_refresh() {
475
476 // Schedule WP Cron only if a Production connection exists.
477 if ( ! Charitable_Square_Connection::get( 'live' ) ) {
478 return;
479 }
480
481 // Check if the event is already scheduled.
482 if ( ! wp_next_scheduled( 'charitable_square_refresh_connection' ) ) {
483 // Schedule the event to run daily.
484 wp_schedule_event( time(), 'daily', 'charitable_square_refresh_connection' );
485 }
486 }
487
488 /**
489 * Unschedule the connection refresh WP Cron job.
490 *
491 * @since 1.8.7
492 */
493 public function unschedule_refresh() {
494 $timestamp = wp_next_scheduled( 'charitable_square_refresh_connection' );
495
496 if ( $timestamp ) {
497 wp_unschedule_event( $timestamp, 'charitable_square_refresh_connection' );
498 }
499 }
500
501 /**
502 * Check connection raw data and save it if everything is OK.
503 *
504 * @since 1.8.7
505 * @version 1.8.9.1
506 *
507 * @param array $raw Connection raw data.
508 * @param bool $silent Optional. Whether to prevent showing admin notices. Default false.
509 *
510 * @return Connection|null
511 */
512 private function maybe_save_connection( array $raw, bool $silent = false ) {
513
514 $connection = new Charitable_Square_Connection( $raw, false );
515
516 // Bail if a connection doesn't have required data.
517 if ( ! $connection->is_configured() ) {
518 if ( charitable_is_debug() ) {
519 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
520 error_log(
521 'Square error',
522 'We could not connect to Square. No tokens were given.',
523 array(
524 'type' => array( 'payment', 'error' ),
525 )
526 );
527 }
528 if ( ! $silent ) {
529 Charitable_Settings::get_instance()->add_update_message( __( 'Square Error: We could not connect to Square. No tokens were given.', 'charitable' ), 'error' );
530 }
531
532 return null;
533 }
534
535 if ( charitable_is_debug( 'square' ) ) {
536 // phpcs:disable
537 error_log( 'maybe_save_connection - connection' );
538 error_log( print_r( $connection, true ) ); // phpcs:ignore
539 // phpcs:enable
540 }
541
542 // Prepare connection for save.
543 $connection
544 ->set_renew_at()
545 ->set_scopes_updated()
546 ->encrypt_tokens();
547
548 if ( charitable_is_debug( 'square' ) ) {
549 // phpcs:disable
550 error_log( 'maybe_save_connection - connection after encrypt_tokens' );
551 error_log( print_r( $connection, true ) ); // phpcs:ignore
552 // phpcs:enable
553 }
554
555 // Bail if a connection is not ready for save.
556 if ( ! $connection->is_saveable() ) {
557 if ( charitable_is_debug() ) {
558 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
559 error_log(
560 'Square error',
561 'We could not save an account connection safely. Please, try again later.',
562 array(
563 'type' => array( 'payment', 'error' ),
564 )
565 );
566 }
567 if ( ! $silent ) {
568 Charitable_Settings::get_instance()->add_update_message( __( 'Square Error: We could not save an account connection safely. Please, try again later.', 'charitable' ), 'error' );
569 }
570
571 return null;
572 }
573
574 if ( charitable_is_debug( 'square' ) ) {
575 // phpcs:disable
576 error_log( 'maybe_save_connection - connection before save' );
577 }
578
579 $connection->save(); // not it.
580
581 if ( charitable_is_debug( 'square' ) ) {
582 // phpcs:disable
583 error_log( 'maybe_save_connection - connection after save' );
584 error_log( print_r( $connection, true ) ); // phpcs:ignore
585 // phpcs:enable
586 }
587
588 return $connection;
589 }
590
591 /**
592 * Retrieve active business locations with processing capability.
593 *
594 * @since 1.8.7
595 *
596 * @param array $locations Locations.
597 *
598 * @return array
599 */
600 private function active_locations_filter( array $locations ): array {
601
602 $active_locations = array();
603
604 if ( empty( $locations ) ) {
605 return $active_locations;
606 }
607
608 foreach ( $locations as $location ) {
609
610 if (
611 // ! $location instanceof Location ||
612 $location->getStatus() !== LocationStatus::ACTIVE ||
613 ! is_array( $location->getCapabilities() ) ||
614 ! in_array( LocationCapability::CREDIT_CARD_PROCESSING, $location->getCapabilities(), true )
615 ) {
616 continue;
617 }
618
619 $location_id = $location->getId();
620
621 $active_locations[ $location_id ] = array(
622 'id' => $location_id,
623 'name' => $location->getName(),
624 'currency' => $location->getCurrency(),
625 );
626 }
627
628 return $active_locations;
629 }
630
631 /**
632 * Set/update location things: ID and currency.
633 *
634 * @since 1.8.7
635 *
636 * @param array $locations Active locations.
637 * @param string $mode Square mode.
638 * @param Charitable_Square_Connection $connection Square connection.
639 */
640 private function set_location( array $locations, string $mode, $connection = null ) {
641
642 if ( $connection === null ) {
643 $connection = Charitable_Square_Connection::get( $mode );
644 }
645
646 $api = new Charitable_Square_API( $connection );
647 $stored_location_id = $api->get_location_id( $mode );
648
649 // Location ID was not set previously or saved ID is not available now.
650 if ( empty( $stored_location_id ) || ! isset( $locations[ $stored_location_id ] ) ) {
651 $stored_location_id = charitable_array_key_first( $locations );
652
653 // Set a new location ID.
654 $api->set_location_id( $stored_location_id, $mode );
655 }
656
657 // Set location currency for connection.
658 // In this case, we can make sure that location currency is matched with Charitable currency.
659 if ( $connection !== null ) {
660 $connection->set_currency( $locations[ $stored_location_id ]['currency'] )->save(); // not it.
661 update_option( 'charitable_square_location_currency_' . $mode, $locations[ $stored_location_id ]['currency'] );
662 }
663 }
664
665 /**
666 * Get cached business locations or fetch it from Square.
667 *
668 * @since 1.8.7
669 *
670 * @param string $mode Square mode.
671 *
672 * @return array
673 */
674 public function get_connected_locations( string $mode ): array {
675
676 $locations = get_transient( 'charitable_square_active_locations_' . $mode );
677
678 if ( empty( $locations ) ) {
679 $locations = $this->prepare_locations( $mode );
680 }
681
682 return $locations;
683 }
684
685 /**
686 * Reset location ID and currency if no locations are received.
687 *
688 * @since 1.8.7
689 *
690 * @param string $mode Square mode.
691 */
692 private function reset_location( string $mode ) {
693
694 $connection = Charitable_Square_Connection::get( $mode );
695
696 if ( ! $connection || ! $connection->is_valid() ) {
697 return;
698 }
699
700 $api = new Charitable_Square_API( $connection );
701 $api->set_location_id( '', $mode );
702
703 $connection->set_currency( '' )->save();
704 }
705
706 /**
707 * Prepare Square business locations.
708 *
709 * @since 1.8.7
710 *
711 * @param string $mode Square mode.
712 * @param Charitable_Square_Connection $connection Square connection.
713 *
714 * @return array
715 */
716 public function prepare_locations( string $mode, $connection = null ): array {
717
718 $locations = $this->fetch_locations( $mode );
719
720 if ( $locations === null ) {
721
722 $this->reset_location( $mode );
723
724 delete_transient( 'charitable_square_active_locations_' . $mode );
725
726 return array();
727 }
728
729 $locations = $this->active_locations_filter( $locations );
730
731 if ( empty( $locations ) ) {
732 $this->reset_location( $mode );
733
734 set_transient( 'charitable_square_active_locations_' . $mode, array(), DAY_IN_SECONDS );
735
736 return array();
737 }
738
739 $this->set_location( $locations, $mode, $connection );
740
741 set_transient( 'charitable_square_active_locations_' . $mode, $locations, DAY_IN_SECONDS );
742
743 return $locations;
744 }
745
746 /**
747 * Fetch Square business locations.
748 *
749 * @since 1.8.7
750 *
751 * @param string $mode Square mode.
752 *
753 * @return array|null
754 */
755 private function fetch_locations( string $mode ) {
756
757 $connection = Charitable_Square_Connection::get( $mode );
758
759 if ( ! $connection || ! $connection->is_valid() ) {
760 if ( charitable_is_debug( 'square' ) ) {
761 // phpcs:disable
762 error_log( 'fetch_locations - connection is not valid' );
763 error_log( print_r( $connection, true ) ); // phpcs:ignore
764 // phpcs:enable
765 }
766 return null;
767 }
768
769 if ( charitable_is_debug( 'square' ) ) {
770 // phpcs:disable
771 error_log( 'fetching locations with this connection' );
772 error_log( print_r( $connection, true ) ); // phpcs:ignore
773 // phpcs:enable
774 }
775
776 $api = new Charitable_Square_API( $connection );
777
778 $locations = $api->get_locations();
779
780 if ( ! $locations ) {
781 $connection->set_status( Charitable_Square_Connection::STATUS_INVALID );
782 $connection->save();
783 return null;
784 }
785
786 return is_array( $locations ) ? $locations : array( $locations );
787 }
788
789 /**
790 * Fetch new connection credentials.
791 *
792 * @since 1.8.7
793 *
794 * @param string $state Unique ID to safely fetch connection data.
795 *
796 * @return array
797 */
798 private function fetch_new_connection( string $state ): array {
799
800 $connection = array();
801 $response = wp_remote_post(
802 $this->get_server_url() . '/oauth/square-connect',
803 array(
804 'body' => array(
805 'action' => 'credentials',
806 'state' => $state,
807 ),
808 'timeout' => 30,
809 )
810 );
811
812 if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
813 $body = json_decode( wp_remote_retrieve_body( $response ), true );
814 $connection = is_array( $body ) ? $body : array();
815 }
816
817 return $connection;
818 }
819
820 /**
821 * Fetch refresh connection credentials.
822 *
823 * @since 1.8.7
824 *
825 * @param string $token The refresh token.
826 * @param string $mode Square mode.
827 *
828 * @return array|WP_Error
829 */
830 private function fetch_refresh_connection( string $token, string $mode ) {
831
832 $response = wp_remote_post(
833 $this->get_server_url() . '/oauth/square-connect',
834 array(
835 'body' => array(
836 'action' => 'refresh',
837 'live_mode' => absint( $mode === 'live' ),
838 'token' => $token,
839 ),
840 'timeout' => 30,
841 )
842 );
843
844 if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
845 return new WP_Error();
846 }
847
848 $body = json_decode( wp_remote_retrieve_body( $response ), true );
849
850 if ( ! is_array( $body ) ) {
851 return new WP_Error();
852 }
853
854 if ( ! empty( $body['success'] ) ) {
855 return $body;
856 }
857
858 $error_message = empty( $body['message'] ) ? '' : wp_kses_post( $body['message'] );
859
860 return new WP_Error( 'refresh_connection_fail', $error_message );
861 }
862
863 /**
864 * Fetch revoke connection request.
865 *
866 * @since 1.8.7
867 *
868 * @param string $merchant_id The Square merchant ID.
869 * @param string $access_token The access token to revoke.
870 * @param string $mode Square mode (live/test).
871 *
872 * @return array|WP_Error
873 */
874 private function fetch_revoke_connection( string $merchant_id, string $access_token, string $mode ) {
875 $response = wp_remote_post(
876 $this->get_server_url() . '/oauth/square-connect',
877 array(
878 'body' => array(
879 'action' => 'revoke',
880 'live_mode' => absint( $mode === 'live' ),
881 'merchant_id' => $merchant_id,
882 'access_token' => $access_token,
883 ),
884 'timeout' => 30,
885 )
886 );
887
888 if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
889 return new WP_Error( 'revoke_connection_fail', __( 'Failed to revoke Square connection.', 'charitable' ) );
890 }
891
892 $body = json_decode( wp_remote_retrieve_body( $response ), true );
893
894 if ( ! is_array( $body ) ) {
895 return new WP_Error( 'revoke_connection_fail', __( 'Invalid response from server.', 'charitable' ) );
896 }
897
898 if ( ! empty( $body['success'] ) ) {
899 return $body;
900 }
901
902 $error_message = empty( $body['message'] ) ? '' : wp_kses_post( $body['message'] );
903
904 return new WP_Error( 'revoke_connection_fail', $error_message );
905 }
906
907 /**
908 * Retrieve the connect URL.
909 *
910 * @since 1.8.7
911 *
912 * @param string $mode Square mode.
913 * @param string $redirect_url The URL to redirect to after connecting.
914 *
915 * @return string
916 */
917 public function get_connect_url( string $mode, string $redirect_url = '' ) {
918
919 if ( empty( $redirect_url ) ) {
920
921 $redirect_url = add_query_arg(
922 array(
923 'tab' => 'gateways',
924 'page' => 'charitable-settings',
925 'group' => 'gateways_square_core',
926 ),
927 admin_url( 'admin.php' )
928 );
929
930 }
931
932 return add_query_arg(
933 array(
934 'action' => 'init',
935 'live_mode' => absint( $mode === 'live' ),
936 'state' => uniqid( '', true ),
937 'site_url' => rawurlencode( $redirect_url ),
938 'scopes' => implode( ' ', $this->get_scopes() ),
939 ),
940 $this->get_server_url() . '/oauth/square-connect'
941 );
942 }
943
944
945 /**
946 * Retrieve the disconnect URL.
947 *
948 * @since 1.8.7
949 *
950 * @param string $mode Square mode.
951 *
952 * @return string
953 */
954 public function get_disconnect_url( string $mode ): string {
955
956 $action = 'charitable_square_disconnect';
957 $url = add_query_arg(
958 array(
959 'action' => $action,
960 'live_mode' => absint( $mode === 'live' ),
961 ),
962 charitable_get_square_settings_page_url()
963 );
964
965 return wp_nonce_url( $url, $action );
966 }
967
968 /**
969 * Retrieve a connect server URL.
970 *
971 * @since 1.8.7
972 *
973 * @return string
974 */
975 public function get_server_url(): string {
976
977 if ( defined( 'CHARITABLE_SQUARE_LOCAL_CONNECT_SERVER' ) && CHARITABLE_SQUARE_LOCAL_CONNECT_SERVER ) {
978 return home_url();
979 }
980
981 return self::CHARITABLE_SQUARE_CONNECT_URL;
982 }
983
984 /**
985 * Retrieve the connection scopes (permissions).
986 *
987 * @since 1.8.7
988 *
989 * @return array
990 */
991 public function get_scopes(): array {
992
993 /**
994 * Filter the connection scopes.
995 *
996 * @since 1.8.7
997 *
998 * @param array $scopes The connection scopes.
999 */
1000 return (array) apply_filters(
1001 'charitable_square_admin_connect_get_scopes',
1002 array(
1003 'MERCHANT_PROFILE_READ',
1004 'PAYMENTS_READ',
1005 'PAYMENTS_WRITE',
1006 'ORDERS_READ',
1007 'ORDERS_WRITE',
1008 'PAYMENTS_WRITE_ADDITIONAL_RECIPIENTS',
1009 )
1010 );
1011 }
1012
1013 /**
1014 * Create and return the class object.
1015 *
1016 * @since 1.1.0
1017 *
1018 * @return Charitable_Square_Connect
1019 */
1020 public static function get_instance() {
1021 if ( is_null( self::$instance ) ) {
1022 self::$instance = new Charitable_Square_Connect();
1023 }
1024
1025 return self::$instance;
1026 }
1027 }
1028
1029 endif;
1030