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.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-gateway-square.php

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

2,931 lines 98.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Square Payment Gateway class.
4 *
5 * @package Charitable/Classes/Charitable_Gateway_Square
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.8.7
10 * @version 1.8.7
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Gateway_Square' ) ) :
19
20 /**
21 * Square Gateway.
22 *
23 * @since 1.8.7
24 */
25 class Charitable_Gateway_Square extends Charitable_Gateway {
26
27 /** The gateway ID. */
28 const ID = 'square_core';
29
30 /** The Square API version we are using. */
31 const SQUARE_API_VERSION = '2024-04-25';
32
33 /**
34 * Square Apps URL.
35 *
36 * @since 1.8.7
37 */
38 public const SQUARE_APPS_URL = 'https://developer.squareup.com/apps';
39
40 /**
41 * Gateway badge.
42 *
43 * @since 1.8.7
44 *
45 * @var string
46 */
47 protected $badge = '';
48
49 /**
50 * An array of options for the Square location when in test mode.
51 *
52 * @since 1.8.7
53 *
54 * @var array
55 */
56 private $test_location_options;
57
58 /**
59 * An array of options for the Square location when in live mode.
60 *
61 * @since 1.8.7
62 *
63 * @var array
64 */
65 private $live_location_options;
66
67 /**
68 * Permissions for the Square API.
69 *
70 * @since 1.8.7
71 *
72 * @var string
73 */
74 private $permissions;
75
76 /**
77 * Whether the Square account is connected or disconnected.
78 *
79 * @since 1.8.7
80 *
81 * @var bool
82 */
83 private $is_connected;
84
85 /**
86 * Whether the payment has been processed.
87 *
88 * @since 1.8.7
89 *
90 * @var bool
91 */
92 protected $is_payment_processed = false;
93
94 /**
95 * Main class that communicates with the Square API.
96 *
97 * @since 1.8.7
98 *
99 * @var Api
100 */
101 protected $api;
102
103 /**
104 * Processing errors.
105 *
106 * @since 1.8.7
107 *
108 * @var array
109 */
110 protected $errors;
111
112 /**
113 * Square Connect.
114 *
115 * @since 1.8.7
116 *
117 * @var Connect
118 */
119 protected $connect;
120
121 /**
122 * Connection.
123 *
124 * @since 1.8.7
125 *
126 * @var Charitable_Square_Connection
127 */
128 protected $connection;
129
130 /**
131 * Instance of the class.
132 *
133 * @since 1.8.7
134 *
135 * @var Charitable_Gateway_Square
136 */
137 private static $instance;
138
139 /**
140 * Instantiate the gateway class, defining its key values.
141 *
142 * @since 1.8.7
143 */
144 public function __construct() {
145 /**
146 * Filter the gateway name
147 *
148 * @since 1.8.7
149 *
150 * @param string $name Gateway name.
151 */
152 $this->name = apply_filters( 'charitable_gateway_square_name', __( 'Square', 'charitable' ) );
153
154 $this->defaults = array(
155 'label' => __( 'Square', 'charitable' ),
156 );
157
158 $this->badge = __( 'Recommended', 'charitable' );
159
160 $this->recommended = true;
161
162 $this->supports = array(
163 '1.3.0',
164 'credit-card',
165 'recurring',
166 'refunds',
167 );
168
169 $this->connect = ( new Charitable_Square_Connect() )->init();
170
171 $this->connection = Charitable_Square_Connection::get( charitable_get_option( 'test_mode' ) ? 'test' : 'live' );
172 $this->is_connected = $this->is_square_connected( charitable_get_option( 'test_mode' ) ? 'test' : 'live' );
173
174 // Add action for handling redirects.
175 add_action( 'admin_init', array( $this, 'handle_redirect' ) );
176
177 // Add action for checking token status.
178 add_action( 'admin_init', array( $this, 'check_token_status' ), 10 );
179 }
180
181 /**
182 * Check the locations.
183 *
184 * @since 1.8.7
185 */
186 public function check_locations() {
187
188 // Only check this if page=charitable-settings&tab=gateways&group=gateways_square_core.
189 if ( ! isset( $_GET['page'] ) || 'charitable-settings' !== $_GET['page'] || ! isset( $_GET['tab'] ) || 'gateways' !== $_GET['tab'] || ! isset( $_GET['group'] ) || 'gateways_square_core' !== $_GET['group'] ) { //phpcs:ignore
190 return;
191 }
192
193 // Check if the Square Connect class exists.
194 if ( ! class_exists( 'Charitable_Square_Connect' ) ) {
195 return;
196 }
197 // Check if the Square API class exists.
198 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
199
200 $locations = $this->get_location_options( $mode );
201 }
202
203 /**
204 * Handle redirects for Square gateway settings.
205 *
206 * @since 1.8.7
207 */
208 public function handle_redirect() {
209 if ( isset( $_GET['redirect'] ) && isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
210
211 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
212 $connect = new Charitable_Square_Connect();
213 $connect->prepare_locations( $mode );
214
215 wp_safe_redirect(
216 add_query_arg(
217 array(
218 'page' => 'charitable-settings',
219 'tab' => 'gateways',
220 'group' => 'gateways_square_core',
221 ),
222 admin_url( 'admin.php' )
223 )
224 );
225 exit;
226 }
227 }
228
229 /**
230 * Register the Square payment gateway class.
231 *
232 * @since 1.8.7
233 *
234 * @param string[] $gateways The list of registered gateways.
235 * @return string[]
236 */
237 public static function register_gateway( $gateways ) {
238 // Check if PHP version is compatible with Square SDK.
239 if ( class_exists( 'Charitable_Square_Compatibility' ) && ! Charitable_Square_Compatibility::is_compatible() ) {
240 // Don't register Square gateway if PHP version is incompatible.
241 if ( charitable_is_debug( 'square' ) ) {
242 // phpcs:disable
243 error_log( '[Charitable Square Core] Gateway not registered - PHP version incompatible' );
244 // phpcs:enable
245 }
246 return $gateways;
247 }
248
249 // Only register if not already registered (prevents duplicate registration).
250 if ( ! isset( $gateways['square_core'] ) ) {
251 $gateways['square_core'] = 'Charitable_Gateway_Square';
252
253 if ( charitable_is_debug( 'square' ) ) {
254 // phpcs:disable
255 error_log( '[Charitable Square Core] Gateway registered successfully' );
256 // phpcs:enable
257 }
258 }
259
260 return $gateways;
261 }
262
263 /**
264 * Get the API object.
265 *
266 * @since 1.8.7
267 *
268 * @param boolean|null $test_mode Whether to explicitly get the test or live key.
269 * @param string|null $live_access_token The live access token.
270 * @param string|null $test_access_token The test access token.
271 * @return \Charitable_Square_API
272 */
273 public static function api( $test_mode = null, $live_access_token = null, $test_access_token = null ) {
274 if ( is_null( $test_mode ) ) {
275 return null;
276 }
277 return new \Charitable_Square_API( $test_mode, $live_access_token, $test_access_token );
278 }
279
280 /**
281 * This processes a request from the user to manually renew the Square token.
282 *
283 * @since 1.8.7
284 *
285 * @return void
286 */
287 public function square_renew_token() {
288 $mode = isset( $_GET['mode'] ) ? sanitize_text_field( wp_unslash( $_GET['mode'] ) ) : 'test'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
289
290 if ( 'sandbox' === $mode ) {
291 $mode = 'test';
292 }
293
294 if ( empty( $_GET['action'] ) || 'square_renew_token' !== $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
295 return;
296 }
297
298 // Get current live and/or access tokens.
299 $live_access_token = charitable_square_get_access_token( 'live' );
300 $test_access_token = charitable_square_get_access_token( 'test' );
301
302 // Get the refresh token.
303 $refresh_token = $this->get_personal_access_refresh_token( $mode );
304
305 if ( empty( $refresh_token ) ) {
306 wp_die( esc_html__( 'No refresh token found.', 'charitable' ) );
307 return;
308 }
309
310 // Renew the token using the API.
311 $response = $this->api( $mode, $live_access_token, $test_access_token )->renew_token( $refresh_token );
312
313 if ( false === $response ) {
314 wp_die( esc_html__( 'Failed to renew token. Please try again.', 'charitable' ) );
315 return;
316 }
317
318 // Update the stored tokens.
319 $settings = get_option( 'charitable_settings' );
320 $settings['gateways_square'][ $mode ]['access_token'] = $response['access_token'];
321
322 // Update refresh token if a new one was provided.
323 if ( isset( $response['refresh_token'] ) ) {
324 $settings[ $mode ]['refresh_token'] = $response['refresh_token'];
325 }
326
327 update_option( 'charitable_settings', $settings );
328
329 // Redirect back to settings page with success message.
330 wp_safe_redirect( $this->get_settings_page_url() );
331
332 exit;
333 }
334
335 /**
336 * Register gateway settings.
337 *
338 * @since 1.8.7
339 *
340 * @param array[] $settings Default array of settings for the gateway.
341 * @return array[]
342 */
343 public function gateway_settings( $settings ) {
344
345 if ( $this->connect === null ) {
346 $this->connect = new Charitable_Square_Connect();
347 }
348
349 $connected_status_css = $this->is_connected ? 'square-connected' : 'square-disconnected';
350
351 $settings = array_merge(
352 $settings,
353 array(
354 'square_connection_status' => array(
355 'type' => 'content',
356 'title' => __( 'Connection Status', 'charitable' ),
357 'content' => $this->get_connection_status_content( $this->connection, charitable_get_option( 'test_mode' ) ? 'test' : 'live' ),
358 'priority' => 10,
359 'class' => 'square-connection-status square-non-legacy',
360 ),
361
362 'square_business_locations' => array(
363 'type' => 'content',
364 'title' => __( 'Business Locations', 'charitable' ),
365 'content' => $this->get_business_locations_content(),
366 'class' => 'square-business-locations square-non-legacy ' . $connected_status_css,
367 'priority' => 50,
368 ),
369
370 'square_webhooks' => array(
371 'type' => 'content',
372 'title' => __( 'Enable Webhooks', 'charitable' ),
373 'content' => $this->get_webhooks_content(),
374 'priority' => 60,
375 'class' => 'square-webhooks-content square-non-legacy',
376 ),
377 )
378 );
379
380 $settings = apply_filters( 'charitable_square_gateway_settings', $settings );
381
382 return $settings;
383 }
384
385 /**
386 * Show the link to the documentation about the Webhooks ID.
387 *
388 * @since 1.8.7
389 *
390 * @param string $mode Square mode (e.g. 'live' or 'sandbox').
391 *
392 * @return string
393 */
394 private function get_webhooks_id_desc( string $mode ): string {
395
396 $modes = array(
397 'live' => __( 'Live Mode Endpoint Subscription ID', 'charitable' ),
398 'sandbox' => __( 'Test Mode Endpoint Subscription ID', 'charitable' ),
399 );
400
401 return sprintf(
402 wp_kses( /* translators: %1$s - Live Mode Endpoint ID or Test Mode Endpoint ID. %2$s - Square Dashboard Webhooks Settings URL. */
403 __( 'Retrieve your %1$s from your <a href="%2$s" target="_blank" rel="noopener noreferrer">Square webhook settings</a>. Select the endpoint, then click Copy button.', 'charitable' ),
404 array(
405 'a' => array(
406 'href' => array(),
407 'target' => array(),
408 'rel' => array(),
409 ),
410 )
411 ),
412 $modes[ $mode ],
413 esc_url( self::SQUARE_APPS_URL )
414 );
415 }
416
417 /**
418 * Show the link to the documentation about the Webhook Signature Key.
419 *
420 * @since 1.8.7
421 *
422 * @param string $mode Square mode (e.g. 'live' or 'sandbox').
423 *
424 * @return string
425 */
426 private function get_webhooks_secret_desc( string $mode ): string {
427
428 $modes = array(
429 'live' => __( 'Live Mode Signature Key', 'charitable' ),
430 'sandbox' => __( 'Test Mode Signature Key', 'charitable' ),
431 );
432
433 return sprintf(
434 wp_kses( /* translators: %1$s - Live Mode Signing Secret or Test Mode Signing Secret. %2$s - Square Dashboard Webhooks Settings URL. */
435 __( 'Retrieve your %1$s from your <a href="%2$s" target="_blank" rel="noopener noreferrer">Square webhook settings</a>. Select the endpoint, then click Reveal.', 'charitable' ),
436 array(
437 'a' => array(
438 'href' => array(),
439 'target' => array(),
440 'rel' => array(),
441 ),
442 )
443 ),
444 $modes[ $mode ],
445 esc_url( self::SQUARE_APPS_URL )
446 );
447 }
448
449 /**
450 * Determine if Square is connected.
451 *
452 * @since 1.8.7
453 *
454 * @return int
455 */
456 public function maybe_square_connected() {
457 return $this->check_keys_exist( 'live' ) || $this->check_keys_exist( 'test' );
458 }
459
460 /**
461 * Determine if Square is connected.
462 *
463 * @since 1.8.7
464 *
465 * @param string $mode The mode to check for keys. Should be 'test' or 'live'. If it's sandbox, rename it to 'test'.
466 * @return int
467 */
468 private function is_square_connected( string $mode = 'test' ) {
469
470 if ( ! $this->connection ) {
471 return false;
472 }
473
474 // If the connection is invalid, return false.
475 if ( ! $this->connection->is_valid() ) {
476 return false;
477 }
478
479 return true;
480 }
481
482 /**
483 * Retrieve a Connection Status setting content.
484 *
485 * @since 1.8.7
486 *
487 * @param Charitable_Square_Connection $connection Connection data.
488 * @param string $mode Square mode.
489 *
490 * @return string
491 */
492 private function get_connection_status_content( $connection = null, $mode = 'test' ): string {
493
494 if ( ! $this->is_square_connected() ) {
495 return $this->get_disconnected_status_content( $mode );
496 }
497
498 $content = $this->get_disabled_status_content( $connection );
499
500 if ( ! empty( $content ) ) {
501 return $content;
502 }
503
504 return $this->get_enabled_status_content( $connection );
505 }
506
507 /**
508 * Retrieve a Disconnected Status setting content.
509 *
510 * @since 1.8.7
511 *
512 * @param string $mode Square mode.
513 *
514 * @return string
515 */
516 private function get_disconnected_status_content( string $mode ): string {
517
518 return $this->get_fees_content() .
519 $this->get_connect_button( $mode, false ) .
520 '<p class="desc">' .
521 sprintf(
522 wp_kses( /* translators: %s - Charitable.com Square documentation article URL. */
523 __( 'Securely connect to Square with just a few clicks to begin accepting payments! <a href="%s" target="_blank" rel="noopener noreferrer" class="charitable-learn-more">Learn More</a>', 'charitable' ),
524 array(
525 'a' => array(
526 'href' => array(),
527 'target' => array(),
528 'rel' => array(),
529 'class' => array(),
530 ),
531 )
532 ),
533 esc_url( charitable_utm_link( 'https://wpcharitable.com/docs/how-to-install-and-use-the-square-addon-with-charitable/#connect-square', 'Settings - Payments', 'Square Learn More' ) )
534 ) .
535 '</p>';
536 }
537
538 /**
539 * Retrieve setting content when a connection is disabled.
540 *
541 * @since 1.8.7
542 *
543 * @param Charitable_Square_Connection $connection Connection data.
544 *
545 * @return string
546 */
547 private function get_disabled_status_content( Charitable_Square_Connection $connection ): string {
548
549 if ( ! $connection->is_configured() ) {
550 return $this->get_missing_status_content( $connection->get_mode() );
551 }
552
553 if ( ! $connection->is_valid() ) {
554 return $this->get_invalid_status_content( $connection->get_mode() );
555 }
556
557 return '';
558 }
559
560 /**
561 * Retrieve a connection is missing status content.
562 *
563 * @since 1.8.7
564 *
565 * @param string $mode Square mode.
566 *
567 * @return string
568 */
569 private function get_missing_status_content( string $mode ): string {
570
571 return $this->get_fees_content() . '<div class="charitable-square-connected">' . $this->get_error_icon() . esc_html__( 'Your connection is missing required data. You must reconnect your Square account.', 'charitable' ) . $this->get_disconnect_button( $mode ) . '</div>';
572 }
573
574 /**
575 * Retrieve a connection invalid status content.
576 *
577 * @since 1.8.7
578 *
579 * @param string $mode Square mode.
580 *
581 * @return string
582 */
583 private function get_invalid_status_content( string $mode ): string {
584
585 return $this->get_fees_content() . '<div class="charitable-square-connected">' . $this->get_error_icon() . $this->get_connected_status_content( $mode ) .
586 '<p>' . esc_html__( 'It appears your connection may be invalid. You must refresh tokens or reconnect your account.', 'charitable' ) . '</p>' .
587 '<p>' . $this->get_refresh_button( $mode ) . $this->get_disconnect_button( $mode, false ) . '</p></div>';
588 }
589
590 /**
591 * Retrieve a connection expired status content.
592 *
593 * @since 1.8.7
594 *
595 * @param string $mode Square mode.
596 *
597 * @return string
598 */
599 private function get_expired_status_content( string $mode ): string {
600
601 return $this->get_fees_content() . '<div class="charitable-square-connected">' . $this->get_error_icon() . $this->get_connected_status_content( $mode ) .
602 '<p>' . esc_html__( 'Your connection is expired. You must refresh tokens or reconnect your account.', 'charitable' ) . '</p>' .
603 '<p>' . $this->get_refresh_button( $mode ) . $this->get_disconnect_button( $mode, false ) . '</p></div>';
604 }
605
606 /**
607 * Retrieve a currency mismatch status content.
608 * Might be a false flag.
609 *
610 * @since 1.8.7
611 *
612 * @param string $mode Square mode.
613 *
614 * @return string
615 */
616 private function get_currency_mismatch_status_content( string $mode ): string {
617
618 return $this->get_fees_content() . '<div class="charitable-square-connected">' . $this->get_error_icon() . $this->get_connected_status_content( $mode ) .
619 '<span class="charitable-square-notice-error"><p>' . esc_html__( 'Charitable currency and Business Location currency are not matched.', 'charitable' ) . '</p></span></div>' .
620 $this->get_disconnect_button( $mode );
621 }
622
623 /**
624 * Retrieve a currency missing location currency status content.
625 *
626 * @since 1.8.7
627 *
628 * @param string $mode Square mode.
629 *
630 * @return string
631 */
632 private function get_currency_missing_location_currency_content( string $mode ): string {
633
634 return $this->get_fees_content() . '<div class="charitable-square-connected">' . $this->get_error_icon() . $this->get_connected_status_content( $mode ) .
635 '<span class="charitable-square-notice-error"><p>' . esc_html__( 'Location missing or not valid with the same currency as your Charitable setting. Create or update your location in Square Dashboard.', 'charitable' ) . '</p></span></div>' .
636 $this->get_disconnect_button( $mode );
637 }
638
639 /**
640 * Retrieve setting content when a connection is enabled.
641 *
642 * @since 1.8.7
643 *
644 * @param Charitable_Square_Connection $connection Connection data.
645 *
646 * @return string
647 */
648 private function get_enabled_status_content( Charitable_Square_Connection $connection ): string {
649
650 $fees_html = $this->get_fees_content();
651
652 $location_currency = get_option( 'charitable_square_location_currency_' . $connection->get_mode() );
653 $currency_matched = $location_currency === strtoupper( charitable_get_currency() );
654
655 if ( charitable_is_debug( 'square' ) ) {
656 // phpcs:disable
657 error_log( 'get_enabled_status_content called' );
658 error_log( 'location_currency: ' . $location_currency );
659 error_log( 'charitable_currency: ' . strtoupper( charitable_get_currency() ) );
660 error_log( 'currency_matched: ' . ( $currency_matched ? 'true' : 'false' ) );
661 // phpcs:enable
662 }
663
664 if ( ! $location_currency ) {
665 // If the location currency is not set, we assume it's not matched.
666 return $this->get_currency_missing_location_currency_content( $connection->get_mode() );
667 }
668
669 if ( $connection->is_expired() ) {
670 return $this->get_expired_status_content( $connection->get_mode() );
671 }
672
673 if ( ! $currency_matched ) {
674 return $this->get_currency_mismatch_status_content( $connection->get_mode() );
675 }
676
677 return $fees_html . '<div class="charitable-square-connected"><span class="charitable-success-icon"></span>' . $this->get_connected_status_content( $connection->get_mode() ) . $this->get_disconnect_button( $connection->get_mode() ) . '</div>';
678 }
679
680 /**
681 * Retrieve the fees content.
682 *
683 * @since 1.8.7
684 *
685 * @return string
686 */
687 public function get_fees_content() {
688
689 if ( ! charitable_is_pro() ) {
690 return '<div class="charitable-inline-notice info">
691 <p>
692 <strong>' . esc_html__( 'Pay as you go pricing:', 'charitable' ) . '</strong> ' .
693 sprintf(
694 /* translators: %1$s: opening link tag, %2$s: closing link tag */
695 esc_html__( '3%% per transaction + Square fees. %1$sUpgrade to Pro%2$s for no added fees and priority support.', 'charitable' ),
696 '<a target="_blank" href="' . esc_url( charitable_pro_upgrade_url( 'gateway-square' ) ) . '">',
697 '</a>'
698 ) . '</p>
699 </div>';
700 }
701
702 return '';
703 }
704
705 /**
706 * Retrieve a Connected Status setting content.
707 *
708 * @since 1.8.7
709 *
710 * @param string $mode Square mode.
711 *
712 * @return string
713 */
714 private function get_connected_status_content( string $mode ): string {
715 $connection = Charitable_Square_Connection::get( $mode );
716 $content = '';
717
718 if ( $connection && $connection->is_configured() ) {
719 $api = new Charitable_Square_API( $connection );
720 $merchant_name = $api->get_merchant_name();
721
722 $content .= sprintf(
723 '<p>%s</p>',
724 sprintf(
725 /* translators: %s: Merchant name */
726 esc_html__( 'Connected to Square as %s.', 'charitable' ),
727 '<strong>' . esc_html( $merchant_name ?: $connection->get_merchant_id() ) . '</strong>' //phpcs:ignore
728 )
729 );
730
731 // Check if token is within 14 days of expiration.
732 $token_refresh_date = $this->get_personal_access_token_refresh_date( $mode, true );
733 if ( $token_refresh_date ) {
734 $days_until_expiration = ceil( ( $token_refresh_date - time() ) / DAY_IN_SECONDS );
735 if ( $days_until_expiration <= 14 ) {
736 $content .= sprintf(
737 '<p class="charitable-square-expiration-notice">%s</p><p>%s</p>',
738 sprintf(
739 /* translators: %d: Number of days until expiration */
740 esc_html__( 'Your connection will expire in %d days.', 'charitable' ),
741 abs( $days_until_expiration )
742 ),
743 $this->get_refresh_button( $mode )
744 );
745 }
746 }
747 }
748
749 return $content;
750 }
751
752 /**
753 * Retrieve the connected account data.
754 *
755 * @since 1.8.7
756 *
757 * @param string $mode Square mode.
758 *
759 * @return array
760 */
761 public function get_connected_account( $mode ) {
762 $account_data = charitable_get_option( array( 'gateways_square', $mode ) );
763 return $account_data;
764 }
765
766 /**
767 * Retrieve the Connect button.
768 *
769 * @since 1.8.7
770 *
771 * @param string $mode Square mode.
772 * @param bool $wrap Optional. Wrap a button HTML element or not.
773 *
774 * @return string
775 */
776 private function get_connect_button( string $mode, bool $wrap = true ): string {
777
778 $button = sprintf(
779 $this->get_square_connect_button_html( $mode ),
780 esc_url( $this->connect->get_connect_url( $mode ) ),
781 esc_attr__( 'Connect Square account', 'charitable' ),
782 esc_html__( 'Connect with Square', 'charitable' )
783 );
784
785 return $wrap ? '<p>' . $button . '</p>' : $button;
786 }
787
788 /**
789 * Generate the Square Connect button.
790 *
791 * @since 1.8.7
792 *
793 * @param string $mode The api mode (test|live).
794 *
795 * @return string
796 */
797 public function get_square_connect_button_html( $mode = 'test' ) {
798
799 $html = '';
800
801 if ( 'live' === $mode ) {
802
803 $html .= '<a href="%s" id="charitable-square-connect-btn" class="charitable-square-btn charitable-square-live-btn" title="' . esc_attr__( 'Connect your site with Square account for easy onboarding.', 'charitable' ) . '"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44" width="30" height="30"><path fill="#FFFFFF" d="M36.65 0h-29.296c-4.061 0-7.354 3.292-7.354 7.354v29.296c0 4.062 3.293 7.354 7.354 7.354h29.296c4.062 0 7.354-3.292 7.354-7.354v-29.296c.001-4.062-3.291-7.354-7.354-7.354zm-.646 33.685c0 1.282-1.039 2.32-2.32 2.32h-23.359c-1.282 0-2.321-1.038-2.321-2.32v-23.36c0-1.282 1.039-2.321 2.321-2.321h23.359c1.281 0 2.32 1.039 2.32 2.321v23.36z"></path><path fill="#FFFFFF" d="M17.333 28.003c-.736 0-1.332-.6-1.332-1.339v-9.324c0-.739.596-1.339 1.332-1.339h9.338c.738 0 1.332.6 1.332 1.339v9.324c0 .739-.594 1.339-1.332 1.339h-9.338z"></path></svg><span>' . esc_html__( 'Connect to Square Account', 'charitable' ) . '</span></a>';
804
805 } elseif ( 'test' === $mode ) {
806
807 $html .= '<a href="%s" id="charitable-square-connect-sandbox-btn" class="charitable-square-btn charitable-square-sandbox-btn" title="' . esc_attr__( 'Connect your site with Square account for easy onboarding.', 'charitable' ) . '"><svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 994.6 995.7" style="enable-background:new 0 0 994.6 995 "xml:space="preserve"><style type="text/css">.st0{fill:#1A1A1A;}</style><switch><foreignObject requiredExtensions="&ns_ai;" x="0" y="0" width="1" height="1"><i:aipgfRef xlink:href="#adobe_illustrator_pgf"></i:aipgfRef></foreignObject><g i:extraneous="self"><path class="st0" d="M828.4,0H166.2C74.4,0,0,74.4,0,166.2v662.2c0,91.8,74.4,166.2,166.2,166.2h662.2c91.8,0,166.2-74.4,166.2-166.2V166.2C994.6,74.4,920.2,0,828.4,0z M813.8,761.3c0,29-23.5,52.5-52.5,52.5h-528c-29,0-52.5-23.5-52.5-52.5v-528c0-29,23.5-52.5,52.5-52.5h528c29,0,52.5,23.5,52.5,52.5V761.3z M391.8,632.3c-16.7,0-30.1-13.5-30.1-30.2V391.3c0-16.7,13.4-30.3,30.1-30.3h211.1c16.6,0,30.1,13.5,30.1,30.3V602c0,16.7-13.5,30.2-30.1,30.2H391.8z"/></g></switch></svg><span>' . esc_html__( 'Connect to Square Account (Sandbox)', 'charitable' ) . '</span></a>';
808
809 }
810
811 return $html;
812 }
813
814 /**
815 * Retrieve Business Location options.
816 *
817 * @since 1.8.7
818 *
819 * @return string
820 */
821 public function get_business_locations_content() {
822
823 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
824
825 // We only show content if the connection is valid.
826 $connection = Charitable_Square_Connection::get( $mode );
827 if ( ! $connection ) {
828 return '';
829 }
830
831 return $this->get_location_options_html( $mode );
832 }
833
834
835 /**
836 * Retrieve Business Location options.
837 *
838 * @since 1.8.7
839 *
840 * @param string $mode Square mode.
841 *
842 * @return array
843 */
844 private function get_location_options_html( string $mode ) {
845
846 $locations = $this->get_location_options( $mode );
847
848 $options = array();
849
850 ob_start();
851
852 if ( empty( $locations ) ) {
853 ?>
854 <div class="charitable-help">
855 <?php esc_html_e( 'No locations were found', 'charitable' ); ?>
856 </div>
857 <?php
858 return ob_get_clean();
859 }
860
861 foreach ( $locations as $location_id => $location_name ) {
862 $options[ $location_id ] = $location_name;
863 }
864
865 ob_start();
866
867 $location_id = get_option( 'charitable_square_location_id_' . $mode );
868
869 $attr = array(
870 'id' => 'charitable_settings_gateways_square_' . $mode . '_location',
871 'css' => '',
872 'class' => '',
873 );
874
875 ?>
876 <select
877 name="charitable_settings[gateways_square][<?php echo esc_attr( $mode ); ?>][location_id]"
878 id="<?php echo esc_attr( $attr['id'] ); ?>"
879 style="<?php echo esc_attr( $attr['css'] ); ?>"
880 class="<?php echo esc_attr( $attr['class'] ); ?>"
881 >
882 <?php
883 if ( ! empty( $options ) ) {
884 foreach ( $options as $key => $value ) {
885 ?>
886 <option value="<?php echo esc_attr( $key ); ?>"
887 <?php selected( $key, $location_id ); ?>">
888 <?php echo esc_html( $value ); ?>
889 </option>
890 <?php
891 }
892 }
893 ?>
894 </select>
895
896 <?php
897 /*
898 <a href="<?php echo esc_url_raw( $refresh_url ); ?>" class="button">
899 <?php esc_html_e( 'Refresh', 'charitable' ); ?>
900 </a>
901 */
902 ?>
903 <div class="charitable-help">
904 <?php esc_html_e( 'Only active locations that support credit card processing in Square can be chosen.', 'charitable' ); ?>
905 </div>
906
907 <?php
908 return ob_get_clean();
909 }
910
911 /**
912 * Retrieve Business Location options.
913 *
914 * @since 1.8.7
915 *
916 * @param string $mode Square mode.
917 *
918 * @return array
919 */
920 private function get_location_options( string $mode ): array {
921
922 $locations = $this->connect->get_connected_locations( $mode );
923
924 return ! empty( $locations ) ? array_column( $locations, 'name', 'id' ) : array( '' => esc_html__( 'No locations were found', 'charitable' ) );
925 }
926
927 /**
928 * Retrieve the webhooks content.
929 *
930 * @since 1.8.7
931 *
932 * @return string
933 */
934 private function get_webhooks_content() {
935
936 // Check if Square is connected.
937 if ( ! $this->is_square_connected( charitable_get_option( 'test_mode' ) ? 'test' : 'live' ) ) {
938 return sprintf(
939 '<div class="charitable-webhook-not-connected">
940 <p>%s</p>
941 </div>',
942 esc_html__( 'Connect your Square account to enable webhook functionality.', 'charitable' )
943 );
944 }
945
946 // Always use query parameter format (for when permalinks are not active).
947 $webhook_url = add_query_arg( array( 'charitable-listener' => self::ID ), home_url() );
948
949 $message = sprintf(
950 '<div class="charitable-webhook-manual-setup">
951 <div class="charitable-help">
952 <p><strong>%s</strong></p>
953 <p>%s</p>
954 </div>
955 <div class="charitable-webhook-url">
956 <label for="charitable-webhook-url">%s:</label>
957 <div class="charitable-webhook-url-input-wrapper">
958 <input type="text" id="charitable-webhook-url" value="%s" readonly />
959 <button type="button" class="charitable-copy-icon" data-clipboard-target="#charitable-webhook-url" title="%s">
960 <svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="copy" class="svg-inline--fa fa-copy fa-xs" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
961 <path fill="currentColor" d="M384 336l-192 0c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l140.1 0L400 115.9 400 320c0 8.8-7.2 16-16 16zM192 384l192 0c35.3 0 64-28.7 64-64l0-204.1c0-12.7-5.1-24.9-14.1-33.9L366.1 14.1c-9-9-21.2-14.1-33.9-14.1L192 0c-35.3 0-64 28.7-64 64l0 256c0 35.3 28.7 64 64 64zM64 128c-35.3 0-64 28.7-64 64L0 448c0 35.3 28.7 64 64 64l192 0c35.3 0 64-28.7 64-64l0-32-48 0 0 32c0 8.8-7.2 16-16 16L64 464c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l32 0 0-48-32 0z"></path>
962 </svg>
963 </button>
964 </div>
965 </div>
966 <div class="charitable-help">
967 <strong>%s</strong><br>
968 %s
969 <ul style="margin: 8px 0 0 20px;">
970 <li><code>invoice.payment_made</code></li>
971 <li><code>oauth.authorization.revoked</code></li>
972 <li><code>payment.created</code></li>
973 <li><code>payment.updated</code></li>
974 <li><code>refund.created</code></li>
975 </ul>
976 <p style="margin-top: 20px;">%s</p>
977 </div>
978 </div>',
979 esc_html__( 'Manual Webhook Setup Required', 'charitable' ),
980 esc_html__( 'To enable webhook functionality, you need to manually add a webhook to your Square dashboard using the URL below.', 'charitable' ),
981 esc_html__( 'Webhook URL to add in Square Dashboard', 'charitable' ),
982 esc_url( $webhook_url ),
983 esc_attr__( 'Copy URL', 'charitable' ),
984 esc_html__( 'Required webhook subscriptions:', 'charitable' ),
985 esc_html__( 'When creating the webhook in Square, subscribe to these events:', 'charitable' ),
986 sprintf(
987 wp_kses(
988 /* translators: %s - charitable.com URL for Square webhooks documentation. */
989 __( 'Please see <a href="%s" target="_blank" rel="noopener noreferrer">our documentation on Square webhooks</a> for detailed instructions on how to add this webhook to your Square dashboard.', 'charitable' ),
990 array(
991 'a' => array(
992 'href' => array(),
993 'target' => array(),
994 'rel' => array(),
995 ),
996 )
997 ),
998 esc_url( charitable_utm_link( 'https://www.wpcharitable.com/documentation/setting-up-square-webhooks/', 'Settings - Payments', 'Square Webhooks Documentation' ) )
999 ),
1000 );
1001
1002 return $message;
1003 }
1004
1005 /**
1006 * Get the webhooks endpoint content.
1007 *
1008 * @since 1.8.7
1009 *
1010 * @return string
1011 */
1012 private function get_webhooks_endpoint_content() {
1013
1014 ob_start();
1015
1016 // Always use query parameter format (for when permalinks are not active).
1017 $webhook_url = add_query_arg( array( 'charitable-listener' => self::ID ), home_url() );
1018
1019 echo '<input type="text" disabled style="width: 100%;" value="' . esc_url( $webhook_url ) . '" />';
1020
1021 $help = sprintf(
1022 wp_kses( /* translators: %s - charitable.com URL for Square webhooks documentation. */
1023 __( 'Ensure an endpoint with the above URL is present in the <a href="%s" target="_blank" rel="noopener noreferrer">Square webhook settings</a>.', 'charitable' ),
1024 array(
1025 'a' => array(
1026 'href' => array(),
1027 'target' => array(),
1028 'rel' => array(),
1029 ),
1030 )
1031 ),
1032 esc_url( 'https://developer.squareup.com/apps', 'Settings - Payments', 'Square Webhooks Documentation' )
1033 );
1034
1035 echo '<div class="charitable-help">' . wp_kses_post( $help ) . '</div>'; //phpcs:ignore
1036
1037 return ob_get_clean();
1038 }
1039
1040 /**
1041 * Get the "Connect Webhooks" button.
1042 *
1043 * @since 1.8.7
1044 *
1045 * @return string
1046 */
1047 private function get_webhooks_connect_button() {
1048
1049 // Assume webhooks are configured manually in Square dashboard.
1050 $is_connected = true;
1051
1052 if ( $is_connected ) {
1053 return sprintf(
1054 '<div><span class="%s"></span>%s</div>',
1055 esc_attr( 'charitable-success-icon' ),
1056 esc_html__( 'Webhooks are configured manually in Square dashboard.', 'charitable' )
1057 );
1058 }
1059
1060 $button = sprintf(
1061 '<button class="charitable-btn charitable-btn-md charitable-btn-connect-webhooks" type="button" id="charitable-setting-square-webhooks-connect" title="%1$s">%2$s</button>',
1062 esc_attr__( 'Press here to see the further instructions.', 'charitable' ),
1063 esc_html__( 'Connect Webhooks', 'charitable' )
1064 );
1065
1066 $description = sprintf(
1067 '<p class="desc">%s</p>',
1068 wp_kses(
1069 /* translators: %s - charitable.com URL for Square webhooks documentation. */
1070 __( 'To start using webhooks, please register a webhook route inside our application. You can do this by pressing the button above. Please see <a href="%1$s" target="_blank">our documentation on Square webhooks</a> for full details.', 'charitable' ),
1071 array(
1072 'a' => array(
1073 'href' => array(),
1074 'target' => array(),
1075 ),
1076 )
1077 )
1078 );
1079
1080 $description = sprintf( $description, esc_url( charitable_utm_link( 'https://www.wpcharitable.com/documentation/setting-up-square-webhooks/', 'Settings - Payments', 'Square Webhooks Documentation' ) ) );
1081
1082 return $button . $description;
1083 }
1084
1085 /**
1086 * Get the Square logo (SVG).
1087 *
1088 * @since 1.8.7
1089 *
1090 * @return string
1091 */
1092 public function get_logo() {
1093 return '<svg width="88" height="22" viewBox="0 0 88 22" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M3.6761 1.54849e-07H18.3239C19.299 -0.000282679 20.2344 0.386894 20.924 1.07633C21.6136 1.76576 22.0011 2.70095 22.0011 3.6761V18.3228C22.0011 20.3537 20.3547 22 18.3239 22H3.6761C1.64567 21.9994 0 20.3533 0 18.3228V3.6761C0 1.64584 1.64584 1.54849e-07 3.6761 1.54849e-07ZM16.8434 18.0005C17.4842 18.0005 18.0037 17.481 18.0037 16.8402L18.0005 5.16083C18.0005 4.52004 17.481 4.00058 16.8402 4.00058H5.1619C4.854 4.00058 4.55872 4.12297 4.3411 4.34079C4.12348 4.55861 4.00137 4.854 4.00165 5.1619V16.8402C4.00165 17.481 4.52111 18.0005 5.1619 18.0005H16.8434Z" fill="#3E4348"/><path d="M8.66615 13.9828C8.30039 13.9799 8.00543 13.6826 8.00544 13.3168V8.65442C8.00459 8.47722 8.07438 8.30698 8.19939 8.18138C8.32439 8.05578 8.49429 7.98517 8.67149 7.98517H13.3403C13.5174 7.98545 13.6871 8.05615 13.8121 8.18169C13.937 8.30724 14.0069 8.47731 14.0063 8.65442V13.3157C14.0069 13.4928 13.937 13.6629 13.8121 13.7884C13.6871 13.914 13.5174 13.9847 13.3403 13.985L8.66615 13.9828Z" fill="#3E4348"/><path d="M33.1959 10.0196C32.5149 9.83388 31.8702 9.65883 31.3696 9.43575C30.4431 9.0216 30.0119 8.44734 30.0119 7.62972C30.0119 6.08414 31.5062 5.3882 32.9942 5.3882C34.4084 5.3882 35.6434 5.97313 36.4728 7.03412L36.5293 7.1067L37.7248 6.17167L37.6672 6.09908C36.5646 4.69653 34.9315 3.92801 33.0667 3.92801C31.8254 3.92801 30.6875 4.26317 29.8646 4.87265C28.9381 5.55044 28.4492 6.53351 28.4492 7.70657C28.4492 10.4338 31.0173 11.0987 33.0817 11.6335C35.1706 12.1843 36.4504 12.6027 36.4504 14.1952C36.4504 15.7632 35.1823 16.7762 33.2204 16.7762C32.2502 16.7762 30.4538 16.519 29.3245 14.7941L29.2722 14.7129L28.0148 15.6234L28.0639 15.6971C29.1313 17.3131 30.9736 18.2407 33.1244 18.2407C36.0458 18.2407 38.0098 16.5915 38.0098 14.1387C38.0098 11.3314 35.3392 10.6045 33.1959 10.0196Z" fill="#3E4348"/><path fill-rule="evenodd" clip-rule="evenodd" d="M47.5395 9.45282V7.97662H48.9452V21.9979H47.5395V16.52C46.7368 17.6205 45.5328 18.2225 44.1174 18.2225C41.4447 18.2225 39.5767 16.0824 39.5767 12.9923C39.5767 9.90219 41.4489 7.745 44.1174 7.745C45.5232 7.745 46.7272 8.35021 47.5395 9.45282ZM41.0583 12.9752C41.0583 15.8358 42.6967 16.8552 44.2305 16.8552L44.2337 16.8562C46.2415 16.8562 47.5395 15.3192 47.5395 12.9752C47.5395 10.6312 46.2394 9.11552 44.2305 9.11552C41.8919 9.11552 41.0583 11.1094 41.0583 12.9752Z" fill="#3E4348"/><path d="M58.239 7.97662V13.511C58.239 15.4484 56.9122 16.8552 55.0848 16.8552C53.567 16.8552 52.8284 15.9543 52.8284 14.1024V7.97662H51.4226V14.3895C51.4226 16.7911 52.728 18.2246 54.914 18.2246C56.276 18.2246 57.4459 17.6472 58.24 16.5915V17.9962H59.6458V7.97662H58.239Z" fill="#3E4348"/><path fill-rule="evenodd" clip-rule="evenodd" d="M62.293 9.02907C63.3294 8.21465 64.7362 7.7482 66.1505 7.7482C68.3846 7.7482 69.7177 8.85935 69.7135 10.723V17.9984H68.3067V16.8872C67.5968 17.7763 66.57 18.2268 65.2486 18.2268C63.0956 18.2268 61.7571 17.0494 61.7571 15.1559C61.7571 12.6934 64.0776 12.307 65.0661 12.1426C65.2272 12.116 65.3937 12.0904 65.5601 12.0647L65.5603 12.0647L65.5663 12.0638C66.9168 11.8559 68.3109 11.6414 68.3109 10.4957C68.3109 9.19878 66.6276 9.09845 66.1121 9.09845C65.2016 9.09845 63.9154 9.3685 63.0412 10.1263L62.9612 10.1957L62.2268 9.08137L62.293 9.02907ZM63.2248 15.0769C63.2248 16.6823 64.7362 16.8562 65.3863 16.8562H65.3873C66.8006 16.8562 68.3131 16.1027 68.3099 13.985V12.5354C67.6242 12.9685 66.6483 13.1377 65.7778 13.2886L65.7631 13.2912L65.3265 13.3691C63.9325 13.6274 63.2248 13.9604 63.2248 15.0769Z" fill="#3E4348"/><path d="M77.8577 8.16554C77.5236 7.92752 76.9974 7.78555 76.4487 7.78555C75.3213 7.80037 74.2664 8.34386 73.5998 9.25322V7.97235H72.1941V17.9909H73.5998V12.6326C73.5998 10.2566 74.9352 9.19237 76.2576 9.19237C76.6447 9.1872 77.0279 9.26852 77.3795 9.4304L77.4745 9.48057L77.9196 8.20611L77.8577 8.16554Z" fill="#3E4348"/><path fill-rule="evenodd" clip-rule="evenodd" d="M78.2697 13.0136C78.2697 9.91394 80.2027 7.7482 82.9662 7.7482C85.6282 7.7482 87.4887 9.67057 87.4834 12.4276C87.4826 12.6673 87.4694 12.9067 87.4439 13.1449L87.4353 13.2271H79.7501C79.7853 15.4334 81.1132 16.8562 83.1508 16.8562C84.3186 16.8562 85.3304 16.3813 85.9997 15.5177L86.0605 15.4388L87.0788 16.346L87.0223 16.4143C86.3455 17.2394 85.1116 18.2236 83.0729 18.2236C80.2016 18.2236 78.2697 16.1304 78.2697 13.0136ZM82.9277 9.09738C81.2103 9.09738 79.9924 10.2277 79.8024 11.9879H85.9976C85.8759 10.5725 85.0113 9.09738 82.9277 9.09738Z" fill="#3E4348"/></svg>';
1094 }
1095
1096 /**
1097 * Generate the Square Connect button.
1098 *
1099 * @since 1.8.7
1100 *
1101 * @param string $mode The api mode (test|live).
1102 * @param string $redirect_url The URL to redirect to after connecting.
1103 *
1104 * @return string
1105 */
1106 public function get_square_connect_button_x( $mode = 'sandbox', $redirect_url = '' ) {
1107
1108 $url = $this->get_square_connect_url( $mode, $redirect_url );
1109 $html = '';
1110
1111 if ( 'live' === $mode ) {
1112
1113 $html .= '<a href="' . esc_url( $url ) . '" id="charitable-square-connect-btn" class="charitable-square-btn charitable-square-live-btn" title="' . esc_attr__( 'Connect your site with Square account for easy onboarding.', 'charitable' ) . '"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44" width="30" height="30"><path fill="#FFFFFF" d="M36.65 0h-29.296c-4.061 0-7.354 3.292-7.354 7.354v29.296c0 4.062 3.293 7.354 7.354 7.354h29.296c4.062 0 7.354-3.292 7.354-7.354v-29.296c.001-4.062-3.291-7.354-7.354-7.354zm-.646 33.685c0 1.282-1.039 2.32-2.32 2.32h-23.359c-1.282 0-2.321-1.038-2.321-2.32v-23.36c0-1.282 1.039-2.321 2.321-2.321h23.359c1.281 0 2.32 1.039 2.32 2.321v23.36z"></path><path fill="#FFFFFF" d="M17.333 28.003c-.736 0-1.332-.6-1.332-1.339v-9.324c0-.739.596-1.339 1.332-1.339h9.338c.738 0 1.332.6 1.332 1.339v9.324c0 .739-.594 1.339-1.332 1.339h-9.338z"></path></svg><span>' . esc_html__( 'Connect to Square Account', 'charitable' ) . '</span></a>';
1114
1115 } elseif ( 'sandbox' === $mode ) {
1116
1117 $html .= '<a href="' . esc_url( $url ) . '" id="charitable-square-connect-sandbox-btn" class="charitable-square-btn charitable-square-sandbox-btn" title="' . esc_attr__( 'Connect your site with Square account for easy onboarding.', 'charitable' ) . '"><svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 994.6 995.7" style="enable-background:new 0 0 994.6 995 "xml:space="preserve"><style type="text/css">.st0{fill:#1A1A1A;}</style><switch><foreignObject requiredExtensions="&ns_ai;" x="0" y="0" width="1" height="1"><i:aipgfRef xlink:href="#adobe_illustrator_pgf"></i:aipgfRef></foreignObject><g i:extraneous="self"><path class="st0" d="M828.4,0H166.2C74.4,0,0,74.4,0,166.2v662.2c0,91.8,74.4,166.2,166.2,166.2h662.2c91.8,0,166.2-74.4,166.2-166.2V166.2C994.6,74.4,920.2,0,828.4,0z M813.8,761.3c0,29-23.5,52.5-52.5,52.5h-528c-29,0-52.5-23.5-52.5-52.5v-528c0-29,23.5-52.5,52.5-52.5h528c29,0,52.5,23.5,52.5,52.5V761.3z M391.8,632.3c-16.7,0-30.1-13.5-30.1-30.2V391.3c0-16.7,13.4-30.3,30.1-30.3h211.1c16.6,0,30.1,13.5,30.1,30.3V602c0,16.7-13.5,30.2-30.1,30.2H391.8z"/></g></switch></svg><span>' . esc_html__( 'Connect to Square Account (Sandbox)', 'charitable' ) . '</span></a>';
1118
1119 }
1120
1121 return $html;
1122 }
1123
1124 /**
1125 * Get the Square disconnect button.
1126 *
1127 * @since 1.8.7
1128 *
1129 * @param string $mode The api mode (test|live).
1130 * @param string $redirect_url The URL to redirect to after disconnecting.
1131 * @return string
1132 */
1133 public function get_square_disconnect_button( $mode = 'sandbox', $redirect_url = '' ) {
1134 ob_start();
1135
1136 $html = '';
1137
1138 if ( ! charitable_is_pro() && 'live' === $mode ) {
1139 $html .= '<div class="charitable-inline-notice info">
1140 <p>
1141 <strong>' . esc_html__( 'Pay as you go pricing:', 'charitable' ) . '</strong> ' .
1142 sprintf(
1143 /* translators: % 1$s: opening link tag, % 2$s: closing link tag */
1144 esc_html__( '3%% per transaction + Square fees. %1$sUpgrade to Pro%2$s for no added fees and priority support.', 'charitable' ),
1145 '<a target="_blank" href="' . esc_url( charitable_pro_upgrade_url( 'gateway-square' ) ) . '">',
1146 '</a>'
1147 ) . '</p>
1148 </div>';
1149 }
1150
1151 $html .= '<div id="charitable-square-sandbox-actions-disconnect" class="charitable-square-settings-actions-message charitable-square-settings-actions-message-disconnect charitable-square-settings-actions-message-testmode-' . charitable_get_option( 'test_mode' ) . '" style="display: block;">';
1152
1153 if ( charitable_get_option( 'test_mode' ) ) {
1154 $html .= '<p class="charitable-square-settings-actions-title-testmode-sandbox"><strong>' . __( 'Charitable is in test mode and currently using the Sandbox environment.', 'charitable' ) . '</strong>';
1155 } else {
1156 $html .= '<p class="charitable-square-settings-actions-title-testmode-live"><strong>' . __( 'You have setup your Square account in Sandbox mode. Charitable is in live mode so this is not being used', 'charitable' ) . '</strong>';
1157 }
1158
1159 $html .= '<p>' . __( 'The Sandbox environment is where all operations are simulated and no actual charges will occur. Perfect for testing your setup.', 'charitable' ) . '</p>';
1160 $html .= '<p><a href="' . esc_url( $this->get_square_disconnect_url( $mode, $redirect_url ) ) . '">' . __( 'Disconnect from Square Sandbox.', 'charitable' ) . '</a></p>';
1161
1162 $html .= '<input type="hidden" id="charitable-square-test-access-token" name="charitable_gateways_square_test_access_token" value="' . $this->get_square_access_token( 'test' ) . '">';
1163 $html .= '<input type="hidden" id="charitable-square-test-refresh-token" name="charitable_gateways_square_test_refresh_token" value="' . $this->get_square_refresh_token( 'test' ) . '">';
1164 $html .= '<input type="hidden" id="charitable-square-test-merchant-id" name="charitable_gateways_square_test_merchant_id" value="' . $this->get_square_merchant_id( 'test' ) . '">';
1165 $html .= '<input type="hidden" id="charitable-square-test-merchant-id" name="charitable_gateways_square_test_token_refresh_date" value="' . $this->get_personal_access_token_refresh_date( 'test', true ) . '">';
1166
1167 // check if the locations are set.
1168 $square_locations_check = get_option( '_charitable_square_locations_test' );
1169
1170 if ( ! empty( $square_locations_check ) ) {
1171 $html .= '<input type="hidden" id="charitable-square-test-locations" name="charitable_gateways_square_test_locations" value="' . maybe_serialize( $square_locations_check['locations'] ) . '">';
1172 $html .= '<input type="hidden" id="charitable-square-test-locations-timestamp" name="charitable_gateways_square_test_locations_timestamp" value="' . $square_locations_check['locations_timestamp'] . '">';
1173 }
1174
1175 return $html;
1176 }
1177
1178 /**
1179 * Get the Square access token.
1180 *
1181 * @since 1.8.7
1182 *
1183 * @param string $mode The api mode, test or live.
1184 * @return string
1185 */
1186 public function get_square_access_token( $mode = 'test' ) {
1187
1188 $access_token = charitable_square_get_refresh_token( $mode );
1189
1190 // Debug log the settings.
1191 if ( charitable_is_debug( 'square' ) ) {
1192 // phpcs:disable
1193 error_log(
1194 sprintf(
1195 '[Charitable Square] Retrieving access token for mode: %s',
1196 $mode
1197 )
1198 );
1199 $square_settings = charitable_get_option( 'gateways_square', array() );
1200 error_log(
1201 sprintf(
1202 '[Charitable Square] Settings: %s',
1203 wp_json_encode( $square_settings )
1204 )
1205 );
1206 // phpcs:enable
1207 }
1208
1209 if ( ! empty( $access_token ) ) {
1210 // Debug log token presence.
1211 if ( charitable_is_debug( 'square' ) ) {
1212 // phpcs:disable
1213 error_log(
1214 sprintf(
1215 '[Charitable Square] Found access token: %s',
1216 substr( $access_token, 0, 10 ) . '...' // Only log first 10 chars for security.
1217 )
1218 );
1219 // phpcs:enable
1220 }
1221
1222 return $access_token;
1223 }
1224
1225 // Debug log missing token.
1226 if ( charitable_is_debug( 'square' ) ) {
1227 // phpcs:disable
1228 error_log(
1229 sprintf(
1230 '[Charitable Square] No access token found for mode: %s',
1231 $mode
1232 )
1233 );
1234 // phpcs:enable
1235 }
1236
1237 return '';
1238 }
1239
1240 /**
1241 * Get the Square refresh token.
1242 *
1243 * @since 1.8.7
1244 *
1245 * @param string $mode The api mode, test or live.
1246 * @return string
1247 */
1248 public function get_square_refresh_token( $mode = 'test' ) {
1249 return charitable_square_get_refresh_token( $mode );
1250 }
1251
1252 /**
1253 * Get the Square merchant ID.
1254 *
1255 * @since 1.8.7
1256 *
1257 * @param string $mode The api mode, test or live.
1258 * @return string
1259 */
1260 public function get_square_merchant_id( $mode = 'test' ) {
1261 $square_settings = charitable_get_option( 'gateways_square', array() );
1262
1263 if ( ! empty( $square_settings[ $mode ]['merchant_id'] ) ) {
1264 return $square_settings[ $mode ]['merchant_id'];
1265 }
1266
1267 return '';
1268 }
1269
1270 /**
1271 * Get the URL to connect to Square.
1272 *
1273 * @since 1.8.7
1274 *
1275 * @param string $mode The mode to connect to.
1276 * @param string $redirect_url The URL to redirect to after connecting.
1277 * @return string
1278 */
1279 public function get_square_connect_url( $mode = '', $redirect_url = '' ) {
1280
1281 if ( ! $mode ) {
1282 $mode = 'test' === charitable_get_option( 'test_mode' ) || 1 === charitable_get_option( 'test_mode' ) ? 'test' : 'live';
1283 }
1284
1285 if ( empty( $redirect_url ) ) {
1286
1287 $redirect_url = add_query_arg(
1288 array(
1289 'tab' => 'gateways',
1290 'page' => 'charitable-settings',
1291 'group' => 'gateways_square_core',
1292 ),
1293 admin_url( 'admin.php' )
1294 );
1295
1296 }
1297
1298 // Get the scopes for the needed permissions.
1299 $scopes = Charitable_Square_Connect::get_instance()->get_scopes();
1300
1301 return add_query_arg(
1302 array(
1303 'live_mode' => $mode === 'live' ? 1 : 0,
1304 'state' => str_pad( wp_rand( wp_rand(), PHP_INT_MAX ), 100, wp_rand(), STR_PAD_BOTH ),
1305 'customer_site_url' => rawurlencode( $redirect_url ),
1306 'admin_email' => get_bloginfo( 'admin_email' ),
1307 'permissions' => implode( '+', $scopes ),
1308 ),
1309 'https://connect.wpcharitable.com/square-connect/?wpcharitable_gateway_connect_init=square_connect'
1310 );
1311 }
1312
1313 /**
1314 * Get the URL to renew the Square token.
1315 *
1316 * @since 1.8.7
1317 *
1318 * @param string $redirect_url The URL to redirect to after connecting.
1319 * @param boolean $cron_job_request Whether the request is a cron job request.
1320 * @return string
1321 */
1322 public function get_square_renew_token_url( $redirect_url = '', $cron_job_request = false ) {
1323 if ( empty( $redirect_url ) ) {
1324
1325 $redirect_url = add_query_arg(
1326 array(
1327 'tab' => 'gateways',
1328 'page' => 'charitable-settings',
1329 'group' => 'gateways_square_core',
1330 ),
1331 admin_url( 'admin.php' )
1332 );
1333
1334 }
1335
1336 $mode = 'live' === charitable_get_option( 'test_mode' ) ? 'live' : 'test';
1337
1338 return add_query_arg(
1339 array(
1340 'live_mode' => (int) ! charitable_get_option( 'test_mode' ),
1341 'refresh_token' => charitable_square_get_refresh_token( $mode ),
1342 'key' => get_option( 'charitable_square_aes_key_' . $mode, '' ),
1343 'customer_site_url' => urlencode( $redirect_url ), // phpcs:ignore
1344 'admin_email' => get_bloginfo( 'admin_email' ),
1345 'cron_job_request' => $cron_job_request,
1346 ),
1347 'https://connect.wpcharitable.com/square-connect/?wpcharitable_gateway_connect_refresh_token=square_connect' // todo: filter market site url.
1348 );
1349 }
1350
1351 /**
1352 * Get the URL to disconnect from Square.
1353 *
1354 * @since 1.8.7
1355 *
1356 * @param string $mode The mode to disconnect from.
1357 * @param string $redirect_url The URL to redirect to after disconnecting.
1358 * @return string
1359 */
1360 public function get_square_disconnect_url( $mode = 'sandbox', $redirect_url = '' ) {
1361 return add_query_arg(
1362 array(
1363 'wpcharitable-square-disconnect' => true,
1364 '_wpnonce' => wp_create_nonce(
1365 'wpcharitable-square-connect-disconnect'
1366 ),
1367 'tab' => 'gateways',
1368 'page' => 'charitable-settings',
1369 'group' => 'gateways_square_core',
1370 'mode' => $mode,
1371 ),
1372 admin_url( 'admin.php' )
1373 );
1374 }
1375
1376 /**
1377 * Returns the current gateway's ID.
1378 *
1379 * @return string
1380 * @since 1.8.7
1381 */
1382 public static function get_gateway_id() {
1383 return self::ID;
1384 }
1385
1386 /**
1387 * Save more complex Square gateway settings.
1388 *
1389 * @since 1.8.7
1390 *
1391 * @param array $values The values to save.
1392 * @param array $new_values The new values.
1393 * @param array $old_values The old values.
1394 * @return array
1395 */
1396 public function save_settings( $values, $new_values, $old_values ) {
1397
1398 $square_settings_root_old_settings = ! empty( $old_values['gateways_square'] ) ? $old_values['gateways_square'] : array();
1399 $square_settings_root_new_settings = ! empty( $new_values['gateways_square'] ) ? $new_values['gateways_square'] : array();
1400
1401 // merge the new settings with the old settings, with the new settings taking precedence.
1402 $values['gateways_square'] = array_merge( $square_settings_root_old_settings, $square_settings_root_new_settings );
1403
1404 // Handle location ID for test mode.
1405 if ( isset( $_POST['charitable_settings']['gateways_square']['test']['location_id'] ) ) { //phpcs:ignore
1406 // For the location ID we should save the location ID to the option for test mode.
1407 update_option( 'charitable_square_location_id_test', sanitize_text_field( $_POST['charitable_settings']['gateways_square']['test']['location_id'] ) ); //phpcs:ignore
1408 $values['gateways_square']['test']['location_id'] = sanitize_text_field( $_POST['charitable_settings']['gateways_square']['test']['location_id'] ); //phpcs:ignore
1409 }
1410
1411 // Handle location ID for live mode.
1412 if ( isset( $_POST['charitable_settings']['gateways_square']['live']['location_id'] ) ) { //phpcs:ignore
1413 // For the location ID we should save the location ID to the option for live mode.
1414 update_option( 'charitable_square_location_id_live', sanitize_text_field( $_POST['charitable_settings']['gateways_square']['live']['location_id'] ) ); //phpcs:ignore
1415 $values['gateways_square']['live']['location_id'] = sanitize_text_field( $_POST['charitable_settings']['gateways_square']['live']['location_id'] ); //phpcs:ignore
1416 }
1417
1418 // We need to possibly update the sandbox and live webhook ids and secrets.
1419 // If the values exist in $_POST that means the webhook_settings=1 was added to the settings URL.
1420 if ( isset( $_POST['charitable_settings']['gateways_square']['square_webhooks_id_sandbox'] ) ) { //phpcs:ignore
1421 if ( ! empty( $_POST['charitable_settings']['gateways_square']['square_webhooks_id_sandbox'] ) ) { //phpcs:ignore
1422 $values['gateways_square']['test']['webhooks-id'] = sanitize_text_field( $_POST['charitable_settings']['gateways_square']['square_webhooks_id_sandbox'] ); //phpcs:ignore
1423 } else {
1424 $values['gateways_square']['test']['webhooks-id'] = '';
1425 }
1426 }
1427
1428 if ( isset( $_POST['charitable_settings']['gateways_square']['square_webhooks_secret_sandbox'] ) ) { //phpcs:ignore
1429 if ( ! empty( $_POST['charitable_settings']['gateways_square']['square_webhooks_secret_sandbox'] ) ) { //phpcs:ignore
1430 $values['gateways_square']['test']['webhooks-secret'] = sanitize_text_field( $_POST['charitable_settings']['gateways_square']['square_webhooks_secret_sandbox'] ); //phpcs:ignore
1431 } else {
1432 $values['gateways_square']['test']['webhooks-secret'] = '';
1433 }
1434 }
1435
1436 if ( isset( $_POST['charitable_settings']['gateways_square']['square_webhooks_id_live'] ) ) { //phpcs:ignore
1437 if ( ! empty( $_POST['charitable_settings']['gateways_square']['square_webhooks_id_live'] ) ) { //phpcs:ignore
1438 $values['gateways_square']['live']['webhooks-id'] = sanitize_text_field( $_POST['charitable_settings']['gateways_square']['square_webhooks_id_live'] ); //phpcs:ignore
1439 } else {
1440 $values['gateways_square']['live']['webhooks-id'] = '';
1441 }
1442 }
1443
1444 if ( isset( $_POST['charitable_settings']['gateways_square']['square_webhooks_secret_live'] ) ) { //phpcs:ignore
1445 if ( ! empty( $_POST['charitable_settings']['gateways_square']['square_webhooks_secret_live'] ) ) { //phpcs:ignore
1446 $values['gateways_square']['live']['webhooks-secret'] = sanitize_text_field( $_POST['charitable_settings']['gateways_square']['square_webhooks_secret_live'] ); //phpcs:ignore
1447 } else {
1448 $values['gateways_square']['live']['webhooks-secret'] = '';
1449 }
1450 }
1451
1452 // Check if we should clear the mode connection warning.
1453 $this->maybe_clear_mode_connection_warning( $values );
1454
1455 return $values;
1456 }
1457
1458
1459 /**
1460 * Get the personal access token.
1461 *
1462 * @since 1.8.7
1463 *
1464 * @param string $mode The mode to get the personal access token for.
1465 * @return string
1466 */
1467 public function get_personal_access_token( $mode = 'test' ) {
1468
1469 // Personal access tokens are no longer used - return empty string.
1470 return '';
1471 }
1472
1473
1474 /**
1475 * Get the personal access refresh token.
1476 *
1477 * @since 1.8.7
1478 *
1479 * @param string $mode The mode to get the personal access refresh token for.
1480 * @return string
1481 */
1482 public function get_personal_access_refresh_token( $mode = 'test' ) {
1483
1484 // Personal access tokens are no longer used - return empty string.
1485 return '';
1486 }
1487
1488 /**
1489 * Get the personal access token refresh date.
1490 *
1491 * @since 1.8.7
1492 *
1493 * @param string $mode The mode to get the personal access token refresh date for.
1494 * @param boolean $raw Whether to return the raw date.
1495 * @return string
1496 */
1497 public function get_personal_access_token_refresh_date( $mode = 'test', $raw = false ) {
1498 // Personal access tokens are no longer used - return null.
1499 return null;
1500 }
1501
1502 /**
1503 * Get the personal access token should refresh date.
1504 *
1505 * @since 1.8.7
1506 *
1507 * @param string $mode The mode to get the personal access token should refresh date for.
1508 * @param boolean $raw Whether to return the raw date.
1509 * @return string
1510 */
1511 public function get_personal_access_token_should_refresh( $mode = 'test', $raw = false ) {
1512 // Personal access tokens are no longer used - return false.
1513 return false;
1514 }
1515
1516 /**
1517 * Return the keys to use.
1518 *
1519 * This will return the test keys if test mode is enabled. Otherwise, returns
1520 * the production keys.
1521 *
1522 * @since 1.8.7
1523 *
1524 * @param string $force_mode Forces the "mode" to be used (forcing test for example if the setting is live and vice versa).
1525 * @return string[]
1526 */
1527 public function get_keys( $force_mode = '' ) {
1528 $keys = array();
1529
1530 $settings = charitable_get_option( 'gateways_square', array() );
1531
1532 if ( charitable_get_option( 'test_mode' ) || 'test' === $force_mode ) {
1533 $keys['access_token'] = trim( $settings['test']['access_token'] );
1534 } else {
1535 $keys['access_token'] = trim( $settings['live']['access_token'] );
1536 }
1537
1538 return $keys;
1539 }
1540
1541 /**
1542 * Get the account ID.
1543 *
1544 * @since 1.8.7
1545 *
1546 * @param string $force_mode The mode to get the account ID for.
1547 * @return string
1548 */
1549 public function get_account_id( $force_mode = '' ) {
1550 $settings = charitable_get_option( 'gateways_square', array() );
1551
1552 if ( '' === $force_mode ) {
1553 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
1554 } else {
1555 $mode = $force_mode;
1556 }
1557
1558 return ! empty( $settings[ $mode . '_account_id' ] ) ? $settings[ $mode . '_account_id' ] : '';
1559 }
1560
1561 /**
1562 * Check if Square access token exists.
1563 *
1564 * @since 1.8.7
1565 *
1566 * @param string $mode The mode to check for keys. Should be 'test' or 'live'. If it's sandbox, rename it to 'test'.
1567 * @return boolean
1568 */
1569 public function check_keys_exist( $mode = '' ) {
1570
1571 if ( '' === $mode ) {
1572 return false;
1573 }
1574
1575 if ( 'sandbox' === $mode ) {
1576 $mode = 'test';
1577 }
1578
1579 $settings = get_option( 'charitable_settings' );
1580
1581 $access_token = $settings['gateways_square'][ $mode . '_access_token' ];
1582
1583 if ( ! empty( $access_token ) ) {
1584 return true;
1585 }
1586
1587 return false;
1588 }
1589
1590 /**
1591 * Return the submitted value for a gateway field.
1592 *
1593 * @since 1.8.7
1594 *
1595 * @param string $key The key of the value we want to get.
1596 * @param mixed[] $values An values in which to search.
1597 * @return string|false
1598 */
1599 public function get_gateway_value( $key, $values ) {
1600 if ( isset( $values['gateways']['square_core'][ $key ] ) ) {
1601 return $values['gateways']['square_core'][ $key ];
1602 }
1603
1604 return false;
1605 }
1606
1607 /**
1608 * Return the submitted value for a gateway field.
1609 *
1610 * @since 1.8.7
1611 *
1612 * @param string $key The key of the value we want to get.
1613 * @param Charitable_Donation_Processor $processor The Donation Processor helper object.
1614 * @return string|false
1615 */
1616 public function get_gateway_value_from_processor( $key, Charitable_Donation_Processor $processor ) {
1617 return $this->get_gateway_value( $key, $processor->get_donation_data() );
1618 }
1619
1620 /**
1621 * Returns an array of credit card fields.
1622 *
1623 * If the gateway requires different fields, this can simply be redefined
1624 * in the child class.
1625 *
1626 * @since 1.8.7.
1627 *
1628 * @return array[]
1629 */
1630 public function get_credit_card_fields() {
1631 $fields = parent::get_credit_card_fields();
1632 $no_wallets = false;
1633
1634 /* Remove all fields. Square will take over */
1635 unset(
1636 $fields['cc_name'],
1637 $fields['cc_number'],
1638 $fields['cc_cvc'],
1639 $fields['cc_expiration']
1640 );
1641
1642 ob_start();
1643
1644 ?>
1645
1646 <fieldset id="charitable-square-card-payment-fields" class="<?php echo $no_wallets ? 'no-wallets' : ''; ?>">
1647 <div id="square-card-container"></div>
1648 <input type="hidden" name="square_token" />
1649 <input type="hidden" name="square_verification_token" />
1650 </fieldset>
1651
1652 <div id="square-payment-status-container"></div>
1653
1654 <?php
1655
1656 $fields['cc_element'] = array(
1657 'type' => 'content',
1658 'content' => ob_get_clean(),
1659 'priority' => 2,
1660 );
1661
1662 return $fields;
1663 }
1664
1665 /**
1666 * Process a donation's payment using the Payment Processor API.
1667 *
1668 * @since 1.8.7
1669 * @since 1.8.8.4
1670 *
1671 * @param array $response The response from the Payment Processor API (Example: "1").
1672 * @param int $donation_id The ID of the donation (Example: "677").
1673 * @param \Charitable_Donation_Processor $processor The Donation Processor object.
1674 * @return boolean|null|array
1675 */
1676 public function process_donation( $response, $donation_id, \Charitable_Donation_Processor $processor ) {
1677
1678 // CRITICAL DEBUG: Check if this method is being called at all.
1679 if ( charitable_is_debug( 'square' ) ) {
1680 // phpcs:disable
1681 error_log( '=== SQUARE CORE PROCESS_DONATION METHOD CALLED ===' );
1682 error_log( 'Donation ID: ' . $donation_id );
1683 error_log( 'Response: ' . print_r( $response, true ) );
1684 error_log( 'Processor: ' . get_class( $processor ) );
1685 error_log( 'POST data: ' . print_r( $_POST, true ) );
1686 error_log( 'Gateway from processor: ' . $processor->get_donation_data_value( 'gateway' ) );
1687 // phpcs:enable
1688 }
1689
1690 $donation = new \Charitable_Donation( $donation_id );
1691 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
1692 $connection = Charitable_Square_Connection::get( $mode );
1693 $this->api = new Charitable_Square_API( $connection );
1694
1695 if ( charitable_is_debug( 'square' ) ) {
1696 // phpcs:disable
1697 error_log( 'Mode: ' . $mode );
1698 error_log( 'Connection object: ' . ( $connection ? get_class( $connection ) : 'NULL' ) );
1699 error_log( 'Connection configured: ' . ( $connection ? ( $connection->is_configured() ? 'YES' : 'NO' ) : 'N/A' ) );
1700 error_log( 'Connection valid: ' . ( $connection ? ( $connection->is_valid() ? 'YES' : 'NO' ) : 'N/A' ) );
1701 // phpcs:enable
1702 }
1703
1704 $square_token = isset( $_POST['square_token'] ) ? sanitize_text_field( $_POST['square_token'] ) : ''; // phpcs:ignore
1705 $square_verification_token = isset( $_POST['square_verification_token'] ) ? sanitize_text_field( $_POST['square_verification_token'] ) : ''; // phpcs:ignore
1706 $campaign_name = get_the_title( (int) $_POST['campaign_id'] ); // phpcs:ignore
1707
1708 if ( charitable_is_debug( 'square' ) ) {
1709 // phpcs:disable
1710 error_log( 'Square token: ' . ( $square_token ? 'PRESENT' : 'MISSING' ) );
1711 error_log( 'Square verification token: ' . ( $square_verification_token ? 'PRESENT' : 'MISSING' ) );
1712 error_log( 'Campaign name: ' . $campaign_name );
1713 // phpcs:enable
1714 }
1715
1716 if ( charitable_is_debug( 'square' ) ) {
1717 // phpcs:disable
1718 error_log( 'process_donation - $_POST is ' . print_r( $_POST, true ) );
1719 // phpcs:enable
1720 }
1721
1722 $donation_amount = $this->get_donation_amount_with_fees( $donation );
1723 // Conver to cents.
1724 $donation_amount = $donation_amount * 100;
1725 $args_name = 'Donation ID: ' . $donation_id . ' | ' . ( 'custom' === ! empty( $_POST['donation_amount'] ) ? 'Custom Amount' : 'Suggested Amount' ); // phpcs:ignore
1726
1727 // Set tokens provided by Web Payments SDK.
1728 $this->api->set_payment_tokens( $square_token ); // $entry
1729
1730 if ( charitable_is_debug( 'square' ) ) {
1731 // phpcs:disable
1732 error_log( 'process_donation mode is ' . $mode );
1733 // phpcs:enable
1734 }
1735
1736 $location_id = $this->api->get_location_id( $mode );
1737 $note = get_bloginfo( 'name' ) . ': ' . $campaign_name;
1738
1739 $args_payment_single = array(
1740 'amount' => $donation_amount,
1741 'currency' => charitable_get_currency(),
1742 'location_id' => $location_id,
1743 'note' => $note,
1744 'order_items' => array(
1745 array(
1746 'name' => 'Donations',
1747 'quantity' => 1,
1748 'variations' => array(
1749 array(
1750 'name' => 'Donation',
1751 'quantity' => 1,
1752 'variation_name' => $args_name,
1753 'amount' => $donation_amount,
1754 ),
1755 ),
1756 ),
1757 ),
1758 );
1759
1760 $subscription_post_id = $donation->get( 'post_parent' );
1761
1762 if ( $subscription_post_id ) {
1763
1764 if ( charitable_is_debug( 'square' ) ) {
1765 // phpcs:disable
1766 error_log( 'process_donation - subscription_post_id is ' . $subscription_post_id );
1767 // phpcs:enable
1768 }
1769
1770 $subscription = charitable_get_donation( $subscription_post_id );
1771
1772 if ( charitable_is_debug( 'square' ) ) {
1773 // phpcs:disable
1774 // error_log( 'donation is ' . print_r( $donation, true ) );
1775 error_log( 'subscription is ' . print_r( $subscription, true ) );
1776 error_log( 'POST is ' . print_r( $_POST, true ) );
1777 // phpcs:enable
1778 }
1779
1780 $plan = $_POST; //phpcs:ignore
1781
1782 $args_subscription = $this->get_payment_args_subscription( $plan, $campaign_name, $donation_amount );
1783
1784 $args_subscription_single = array(
1785 'amount' => $donation_amount,
1786 'currency' => charitable_get_currency(),
1787 'location_id' => $location_id,
1788 'subscription' => $args_subscription,
1789 );
1790
1791 $result = $this->api->process_subscription_transaction( $args_subscription_single );
1792
1793 if ( charitable_is_debug( 'square' ) ) {
1794 // phpcs:disable
1795 error_log( 'process_donation - result process subscription transaction' );
1796 error_log( print_r( $result, true ) );
1797 // phpcs:enable
1798 }
1799
1800 $subscription_id = $result->getId();
1801 $plan_variation_id = $result->getPlanVariationId();
1802 $customer_id = $result->getCustomerId();
1803 $start_date = $result->getStartDate();
1804 $status = $result->getStatus();
1805
1806 if ( charitable_is_debug( 'square' ) ) {
1807 // phpcs:disable
1808 error_log( 'process_donation - subscription_id is ' . print_r( $subscription_id, true ) );
1809 error_log( 'process_donation - plan_variation_id is ' . print_r( $plan_variation_id, true ) );
1810 error_log( 'process_donation - customer_id is ' . print_r( $customer_id, true ) );
1811 error_log( 'process_donation - start_date is ' . print_r( $start_date, true ) );
1812 error_log( 'process_donation - status is ' . print_r( $status, true ) );
1813 // phpcs:enable
1814 }
1815
1816 $subscription->set_gateway_subscription_id( $subscription_id );
1817 $message = sprintf( '%s: %s', __( 'Square Subscription ID', 'charitable' ), $subscription_id );
1818 $subscription->log()->add( $message );
1819
1820 if ( 'ACTIVE' === $status ) {
1821 $subscription->update_status( 'charitable-active' );
1822 } else {
1823 $subscription->update_status( 'charitable-pending' );
1824 }
1825
1826 if ( charitable_is_debug( 'webhook' ) ) {
1827 // Create a single array of meta data with the remaining items.
1828 $meta_data = array(
1829 'subscription_id' => $subscription_id,
1830 'plan_variation_id' => $plan_variation_id,
1831 'customer_id' => $customer_id,
1832 'start_date' => $start_date,
1833 'status' => $status,
1834 );
1835 update_post_meta( $subscription_post_id, '_square_subscription_meta_data', $meta_data );
1836 }
1837
1838 // We need to update the donation so that we can find it again when the invoice is paid.
1839 update_post_meta( $donation_id, '_square_subscription_donation_first_donation', $subscription_post_id );
1840
1841 if ( charitable_is_debug( 'square' ) ) {
1842 // phpcs:disable
1843 error_log( 'process_donation - donation_id is ' . $donation_id );
1844 error_log( 'process_donation - subscription_post_id is ' . $subscription_post_id );
1845 // phpcs:enable
1846 }
1847 } else {
1848
1849 $result = $this->api->process_single_transaction( $args_payment_single );
1850
1851 if ( charitable_is_debug( 'square' ) ) {
1852 // phpcs:disable
1853 error_log( 'process_donation - result process single transaction' );
1854 error_log( print_r( $result, true ) );
1855 // phpcs:enable
1856 }
1857
1858 // Set payment processing flag.
1859 $this->is_payment_processed = true;
1860 $order = null;
1861 $order_id = '';
1862
1863 $errors = $this->process_api_errors( 'single' );
1864
1865 if ( ! empty( $errors ) ) {
1866 // Process errors and handle categories.
1867 $error_messages = array();
1868 foreach ( $errors as $error ) {
1869 if ( is_array( $error ) && isset( $error['category'] ) ) {
1870 // Handle specific error categories.
1871 switch ( $error['category'] ) {
1872 case 'AUTHENTICATION_ERROR':
1873 $error_messages[] = __( 'Authentication error with Square. Please check your Square Gateway settings.', 'charitable' );
1874 break;
1875 default:
1876 $error_messages[] = $error['message'];
1877 break;
1878 }
1879 } else {
1880 $error_messages[] = $error;
1881 }
1882 }
1883
1884 // If an array is returned, we need to implode it.
1885 $error_message = implode( ', ', $error_messages );
1886 $message = sprintf( '%s: %s', __( 'Errors', 'charitable' ), $error_message );
1887 $donation->log()->add( $message );
1888
1889 // Add a general admin notice to alert the user that there was an error.
1890 $admin_message = sprintf(
1891 '%s' . "\n" . 'Details: %s',
1892 __( 'Errors have occurred while processing Square donations. You may need to check the connection to your Square account.', 'charitable' ),
1893 $error_message
1894 );
1895
1896 if ( charitable_is_debug( 'square' ) ) {
1897 error_log( 'Adding Square admin notice: ' . $admin_message ); //phpcs:ignore
1898 }
1899
1900 // Get current notices.
1901 $current_notices = charitable_get_admin_notices()->get_notices();
1902 $notice_exists = false;
1903
1904 // Check if this exact error message already exists.
1905 if ( ! empty( $current_notices['error'] ) ) {
1906 foreach ( $current_notices['error'] as $notice ) {
1907 if ( $notice['message'] === $admin_message ) {
1908 $notice_exists = true;
1909 break;
1910 }
1911 }
1912 }
1913
1914 // Only add the notice if it doesn't already exist.
1915 if ( ! $notice_exists ) {
1916 charitable_get_admin_notices()->add_notice( $admin_message, 'error', false, true );
1917 }
1918
1919 if ( charitable_is_debug( 'square' ) ) {
1920 error_log( 'Current admin notices: ' . print_r( charitable_get_admin_notices()->get_notices(), true ) ); //phpcs:ignore
1921 }
1922
1923 $donation->update_status( 'charitable-failed' );
1924
1925 wp_send_json_error( $admin_message );
1926 exit;
1927 }
1928
1929 if ( isset( $result['order'] ) ) {
1930 $order = $result['order'];
1931 $order_id = $order->getId();
1932 }
1933
1934 if ( $order_id ) {
1935 // This Square Order / Transaction ID is used by the webhook to update the donation status.
1936 $donation->set_gateway_transaction_id( $order_id );
1937 $message = sprintf( '%s: %s', __( 'Square Order / Transaction ID', 'charitable' ), $order_id );
1938 $donation->log()->add( $message );
1939 }
1940 }
1941
1942 if ( charitable_is_debug( 'square' ) ) {
1943 // phpcs:disable
1944 error_log( 'process_donation - end' );
1945 // phpcs:enable
1946 }
1947
1948 $donation->update_status( 'charitable-pending' );
1949
1950 return true;
1951 }
1952
1953 /**
1954 * Retrieve subscription payment args.
1955 *
1956 * @since 1.8.7
1957 *
1958 * @param array $plan Plan settings.
1959 * @param string $campaign_name Campaign name.
1960 * @param int $donation_amount Donation amount.
1961 *
1962 * @return array
1963 */
1964 private function get_payment_args_subscription( array $plan, string $campaign_name, int $donation_amount ): array {
1965
1966 $args_sub = array();
1967 $currency = charitable_get_currency();
1968
1969 $recurring_donation_plan = ! empty( $plan['recurring_donation_plan'] ) ? $plan['recurring_donation_plan'] : '';
1970 $recurring_donation_plan = empty( $recurring_donation_plan ) && ! empty( $plan['recurring_donation'] ) ? sanitize_text_field( $plan['recurring_donation'] ) : $recurring_donation_plan;
1971
1972 $recurring_donation_plan = apply_filters( 'charitable_square_recurring_donation_plan', $recurring_donation_plan, $plan );
1973
1974 switch ( $recurring_donation_plan ) {
1975 case 'day':
1976 case 'daily':
1977 $phase_cadence_key = 'daily';
1978 break;
1979 case 'week':
1980 case 'weekly':
1981 $phase_cadence_key = 'weekly';
1982 break;
1983 case 'month':
1984 case 'monthly':
1985 $phase_cadence_key = 'monthly';
1986 break;
1987 case 'quarter':
1988 case 'quarterly':
1989 $phase_cadence_key = 'quarterly';
1990 break;
1991 case 'semiyear':
1992 case 'semiyearly':
1993 $phase_cadence_key = 'semiyearly';
1994 break;
1995 case 'year':
1996 case 'yearly':
1997 case 'annually':
1998 case 'annual':
1999 $phase_cadence_key = 'yearly';
2000 break;
2001 }
2002 $plan['name'] = get_bloginfo( 'name' );
2003
2004 $args_sub['customer']['email'] = sanitize_email( $_POST['email'] ); //phpcs:ignore
2005 $args_sub['customer']['first_name'] = sanitize_text_field( $_POST['first_name'] ); //phpcs:ignore
2006 $args_sub['customer']['last_name'] = sanitize_text_field( $_POST['last_name'] ); //phpcs:ignore
2007
2008 // Customer address.
2009 // There's address_2, city, state, postcode, country.
2010 $args_sub['customer']['address'] = ! empty( $_POST['address'] ) ? sanitize_text_field( $_POST['address'] ) : ''; //phpcs:ignore
2011 $args_sub['customer']['address_2'] = ! empty( $_POST['address_2'] ) ? sanitize_text_field( $_POST['address_2'] ) : ''; //phpcs:ignore
2012 $args_sub['customer']['city'] = ! empty( $_POST['city'] ) ? sanitize_text_field( $_POST['city'] ) : ''; //phpcs:ignore
2013 $args_sub['customer']['state'] = ! empty( $_POST['state'] ) ? sanitize_text_field( $_POST['state'] ) : ''; //phpcs:ignore
2014 $args_sub['customer']['postcode'] = ! empty( $_POST['postcode'] ) ? sanitize_text_field( $_POST['postcode'] ) : ''; //phpcs:ignore
2015 $args_sub['customer']['country'] = ! empty( $_POST['country'] ) ? sanitize_text_field( $_POST['country'] ) : ''; //phpcs:ignore
2016 $args_sub['customer']['phone'] = ! empty( $_POST['phone'] ) ? sanitize_text_field( $_POST['phone'] ) : ''; //phpcs:ignore
2017
2018 $cadences_list = Charitable_Square_Helpers::get_subscription_cadences();
2019
2020 if ( charitable_is_debug( 'square' ) ) {
2021 // phpcs:disable
2022 error_log( 'cadences_list is ' . print_r( $cadences_list, true ) );
2023 // phpcs:enable
2024 }
2025
2026 $phase_cadence = $cadences_list[ $phase_cadence_key ] ?? $cadences_list['yearly'];
2027
2028 // Subscription cadence.
2029 $args_sub['phase_cadence'] = $phase_cadence;
2030
2031 $plan_name = $campaign_name;
2032 $plan_name .= empty( $plan['name'] ) ? '' : ': ' . $plan['name'];
2033
2034 $args_sub['plan_name'] = sprintf( '%s (%s)', $plan_name, $phase_cadence['name'] );
2035 $args_sub['plan_variation_name'] = sprintf( '%s (%s %s %s)', $plan['name'], $donation_amount, $currency, $phase_cadence['name'] );
2036
2037 // Card holder.
2038 $args_sub['card_name'] = sanitize_text_field( $_POST['first_name'] ) . ' ' . sanitize_text_field( $_POST['last_name'] ); // phpcs:ignore
2039
2040 if ( charitable_is_debug( 'square' ) ) {
2041 // phpcs:disable
2042 error_log( 'get_payment_args_subscription - args_sub is ' . print_r( $args_sub, true ) );
2043 // phpcs:enable
2044 }
2045
2046 /**
2047 * Filter subscription payment arguments.
2048 *
2049 * @since 1.8.7
2050 *
2051 * @param array $args The subscription payment arguments.
2052 * @param Process $process The Process instance.
2053 */
2054 return (array) apply_filters( 'square_integrations_square_process_get_payment_args_subscription', $args_sub, $this ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- This is a third-party hook from the Square Integrations plugin. Changing it would break existing functionality.
2055 }
2056
2057 /**
2058 * Collect errors from API and turn it into form errors.
2059 *
2060 * @since 1.8.7
2061 *
2062 * @param string $type Payment type (e.g. 'single').
2063 */
2064 private function process_api_errors( string $type ) {
2065
2066 $errors = $this->api->get_errors();
2067
2068 if ( empty( $errors ) || ! is_array( $errors ) ) {
2069 return;
2070 }
2071
2072 $this->log_errors( $errors );
2073
2074 if ( $type === 'subscription' ) {
2075 $title = esc_html__( 'Square subscription payment stopped', 'charitable' );
2076 } else {
2077 $title = esc_html__( 'Square payment stopped', 'charitable' );
2078 }
2079
2080 $_errors = $this->api->get_response_errors();
2081
2082 if ( ! empty( $_errors ) ) {
2083 $this->process_api_errors_codes( $_errors );
2084 $errors[] = $_errors;
2085 }
2086
2087 // Process errors and extract categories.
2088 $processed_errors = array();
2089 foreach ( $errors as $error ) {
2090 if ( is_array( $error ) && isset( $error['category'] ) ) {
2091 // Handle error with category.
2092 $processed_errors[] = array(
2093 'message' => $error['message'],
2094 'category' => $error['category'],
2095 );
2096 } else {
2097 // Handle regular error string.
2098 $processed_errors[] = $error;
2099 }
2100 }
2101
2102 if ( ! empty( $processed_errors ) && charitable_is_debug( 'square' ) ) {
2103 // phpcs:disable
2104 error_log( 'Charitable_Gateway_Square. Errors reported in API. ' . print_r( $processed_errors, true ) );
2105 // phpcs:enable
2106 }
2107
2108 return $processed_errors;
2109 }
2110
2111 /**
2112 * Display form errors.
2113 *
2114 * @since 1.8.7
2115 *
2116 * @param array $errors Errors to display.
2117 */
2118 private function log_errors( array $errors = array() ) {
2119
2120 if ( ! $errors ) {
2121 $errors = $this->errors;
2122 }
2123
2124 if ( ! $errors || ! is_array( $errors ) ) {
2125 return;
2126 }
2127
2128 if ( charitable_is_debug( 'square' ) ) {
2129 // phpcs:disable
2130 error_log( 'Charitable_Gateway_Square. Errors replorted in API. ' . print_r( $errors, true ) );
2131 // phpcs:enable
2132 }
2133 }
2134
2135 /**
2136 * Check specific error codes.
2137 *
2138 * @since 1.8.7
2139 *
2140 * @param array $errors The last API call errors.
2141 */
2142 private function process_api_errors_codes( array $errors ) {
2143
2144 $codes = $this->get_oauth_error_codes();
2145
2146 foreach ( $errors as $error ) {
2147
2148 if (
2149 empty( $error['code'] ) ||
2150 ! in_array( $error['code'], $codes, true )
2151 ) {
2152 continue;
2153 }
2154
2155 // If the error indicates that access token is bad, set a connection as invalid.
2156 $this->connection
2157 ->set_status( Charitable_Square_Connection::STATUS_INVALID )
2158 ->save();
2159 }
2160 }
2161
2162 /**
2163 * Retrieve OAuth-related errors.
2164 * used to be return [ ErrorCode::ACCESS_TOKEN_EXPIRED, ErrorCode::ACCESS_TOKEN_REVOKED, ErrorCode::UNAUTHORIZED ];
2165 *
2166 * @since 1.8.7
2167 *
2168 * @link https://developer.squareup.com/docs/oauth-api/best-practices#ensure-api-calls-made-with-oauth-tokens-handle-token-based-errors-appropriately
2169 *
2170 * @return array
2171 */
2172 private function get_oauth_error_codes(): array {
2173 return array( 'ACCESS_TOKEN_EXPIRED', 'ACCESS_TOKEN_REVOKED', 'UNAUTHORIZED' );
2174 }
2175
2176 /**
2177 * Validate the submitted credit card details.
2178 *
2179 * @since 1.8.7
2180 * @since 1.4.0 Deprecated.
2181 * @since 1.4.7 Restored. No longer deprecated.
2182 *
2183 * @param boolean $valid Whether the donation is valid.
2184 * @param string $gateway The chosen gateway.
2185 * @param mixed[] $values The filtered values from the donation form submission.
2186 * @return boolean
2187 */
2188 public static function validate_donation( $valid, $gateway, $values ) {
2189 if ( 'square_core' !== $gateway ) {
2190 return $valid;
2191 }
2192
2193 if ( charitable_is_debug( 'square' ) ) {
2194 // phpcs:disable
2195 error_log( 'validate_donation : Charitable_Gateway_Square is making an attempt to validate the donation' );
2196 error_log( '$valid: ' . $valid );
2197 error_log( '$gateway: ' . $gateway );
2198 error_log( '$values: ' . print_r( $values, true ) );
2199 // phpcs:enable
2200 }
2201
2202 $gateway = new Charitable_Gateway_Square();
2203 $test_mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2204
2205 if ( empty( charitable_square_get_access_token( $test_mode ) ) ) {
2206 charitable_get_notices()->add_error( __( 'Missing access token for Square payment gateway. Unable to proceed with payment.', 'charitable' ) );
2207 return false;
2208 }
2209 return $valid;
2210 }
2211
2212 /**
2213 * Check whether a particular donation can be refunded automatically in Square.
2214 *
2215 * @since 1.8.7
2216 *
2217 * @param Charitable_Donation $donation The donation object.
2218 * @return boolean
2219 */
2220 public function is_donation_refundable( Charitable_Donation $donation ) {
2221
2222 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2223 $connection = Charitable_Square_Connection::get( $mode );
2224
2225 if ( ! $connection || ! $connection->is_configured() ) {
2226 $this->errors[] = esc_html__( 'Square account connection is missing.', 'charitable' );
2227 return false;
2228 }
2229
2230 if ( ! $connection->is_valid() ) {
2231 $this->errors[] = esc_html__( 'Square account connection is invalid.', 'charitable' );
2232 return false;
2233 }
2234
2235 if ( ! $connection->is_currency_matched() ) {
2236 $this->errors[] = esc_html__( 'The currency associated with the payment is not valid for the provided business location.', 'charitable' );
2237 return false;
2238 }
2239
2240 // We need to get the payment id from the donation.
2241 $payment_id = $donation->get_gateway_payment_id();
2242 if ( ! $payment_id ) {
2243 return false;
2244 }
2245
2246 return true;
2247 }
2248
2249 /**
2250 * Process a refund initiated in the WordPress dashboard.
2251 *
2252 * @since 1.8.7
2253 * @since 1.8.8.4
2254 *
2255 * @param int $donation_id The donation ID.
2256 * @return boolean
2257 */
2258 public static function refund_donation_from_dashboard( $donation_id ) {
2259 // Debug: Log that the Square Core refund method is being called.
2260 if ( charitable_is_debug( 'square' ) ) {
2261 // phpcs:disable
2262 error_log( '[Square Core] Refund method called for donation ID: ' . $donation_id );
2263 // phpcs:enable
2264 }
2265
2266 $donation = charitable_get_donation( $donation_id );
2267
2268 if ( ! $donation ) {
2269 if ( charitable_is_debug( 'square' ) ) {
2270 // phpcs:disable
2271 error_log( '[Square Core] Refund failed - donation not found for ID: ' . $donation_id );
2272 // phpcs:enable
2273 }
2274 return false;
2275 }
2276
2277 try {
2278
2279 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2280 $connection = Charitable_Square_Connection::get( $mode );
2281 $api = new Charitable_Square_API( $connection );
2282
2283 $payment_id = $donation->get_gateway_payment_id();
2284
2285 if ( ! $payment_id ) {
2286 return false;
2287 }
2288
2289 $currency = $donation->get_currency();
2290 $amount = self::get_donation_amount_with_fees_static( $donation );
2291 // Convert amount to cents.
2292 $amount_cents = $amount * 100;
2293
2294 $args = array(
2295 'reason' => 'Refunded from the WordPress dashboard',
2296 'amount' => $amount_cents,
2297 'currency' => $currency,
2298 );
2299
2300 $refund = $api->refund_payment( $payment_id, $args );
2301
2302 if ( ! $refund ) {
2303 // Log the failed refund.
2304 $donation->log()->add(
2305 sprintf(
2306 /* translators: %s: error message. */
2307 __( 'Square refund failed: %s', 'charitable' ),
2308 $api->get_errors()
2309 )
2310 );
2311 return false;
2312 }
2313 // Log the refund.
2314 $donation->log()->add(
2315 sprintf(
2316 /* translators: %s: transaction reference. */
2317 __( 'Square refund successful. Refund transaction ID: %s', 'charitable' ),
2318 $refund->getId()
2319 )
2320 );
2321
2322 } catch ( Exception $e ) {
2323 $donation->log()->add(
2324 sprintf(
2325 /* translators: %s: error message. */
2326 __( 'Square refund failed: %s', 'charitable' ),
2327 $e->getMessage()
2328 )
2329 );
2330 return false;
2331 }
2332 }
2333
2334 /**
2335 * Get the donation amount including fees if fee relief is enabled.
2336 *
2337 * @since 1.8.8.4
2338 *
2339 * @param Charitable_Donation $donation The donation object.
2340 * @return float
2341 */
2342 private static function get_donation_amount_with_fees_static( $donation ) {
2343 if ( ! $donation ) {
2344 return 0.0;
2345 }
2346
2347 // Check if fee relief is enabled and donor opted to cover fees.
2348 if ( $donation->get( 'cover_fees' ) ) {
2349 return \Charitable_Currency::get_instance()->cast_to_decimal_format( $donation->get( 'total_donation_with_fees' ) );
2350 }
2351
2352 // Return the standard donation amount.
2353 return (float) $donation->get_total_donation_amount( true );
2354 }
2355
2356 /**
2357 * Cancel a subscription.
2358 *
2359 * To cancel a subscription, you need to:
2360 * Create a CancelSubscriptionsRequest object
2361 * Pass it to the subscriptions->cancel() method
2362 * Handle the response to determine success or failure
2363 *
2364 * @since 1.8.7
2365 *
2366 * @param boolean $cancelled Whether the subscription was cancelled successfully in the gateway.
2367 * @param Charitable_Recurring_Donation $donation The recurring donation object.
2368 * @return boolean
2369 */
2370 public static function cancel_subscription( $cancelled, $donation ) {
2371
2372 $subscription_id = $donation->get_gateway_subscription_id();
2373
2374 if ( charitable_is_debug( 'square' ) ) {
2375 // phpcs:disable
2376 error_log( 'cancel_subscription - subscription_id: ' . $subscription_id );
2377 // phpcs:enable
2378 }
2379
2380 if ( ! $subscription_id ) {
2381 return false;
2382 }
2383
2384 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2385 $connection = Charitable_Square_Connection::get( $mode );
2386 $api = new Charitable_Square_API( $connection );
2387
2388 $result = $api->process_subscription_cancellation( $subscription_id );
2389
2390 if ( charitable_is_debug( 'square' ) ) {
2391 // phpcs:disable
2392 error_log( 'cancel_subscription - result: ' . $result );
2393 // phpcs:enable
2394 }
2395
2396 if ( $result ) {
2397 $donation->log()->add( __( 'Subscription initialised for cancellation in Square.', 'charitable' ) );
2398 update_post_meta( $donation->ID, '_square_cancelled_to_be_confirmed', 'true' );
2399 return true;
2400 } else {
2401 $errors = $api->get_errors();
2402 $message = ! empty( $errors ) ? implode( ', ', $errors ) : __( 'Unknown error.', 'charitable' );
2403
2404 $donation->log()->add(
2405 sprintf(
2406 /* translators: %s: error message. */
2407 __( 'Square cancellation failed: %s', 'charitable' ),
2408 $message
2409 )
2410 );
2411
2412 return false;
2413 }
2414 }
2415
2416 /**
2417 * Sets up the API.
2418 *
2419 * This sets the API key, specifies an API version to use, and also
2420 * sets the App info.
2421 *
2422 * This function is deprecated.
2423 *
2424 * @since 1.8.7
2425 *
2426 * @param string|null $api_key The API key. Might be the access token. (Maybe later: If null, will use the secret key).
2427 * @return boolean
2428 */
2429 public function setup_api( $api_key = null ) {
2430 return true;
2431 }
2432
2433
2434 /**
2435 * Determine if the Square addon should be shown on the frontend if no keys are found
2436 *
2437 * @param array[] $active_gateways The list of registered gateways.
2438 * @return string[]
2439 * @since 1.8.7
2440 * @version 1.8.7 - not currently used.
2441 */
2442 public function maybe_active_public_gateway( $active_gateways ) {
2443
2444 if ( ! isset( $active_gateways['square_core'] ) ) {
2445 return $active_gateways;
2446 }
2447
2448 if ( is_admin() ) {
2449 // if this is the admin backend don't mess with this list.
2450 return $active_gateways;
2451 }
2452
2453 global $post;
2454
2455 if ( false === $post || ! isset( $post->ID ) ) {
2456 return $active_gateways;
2457 }
2458
2459 // ok, this is being loaded as an active gateway - but do we have any keys?
2460 $keys = $this->get_keys();
2461
2462 /* Make sure that the keys are set, otherwise there will likely be JS error in the template that will effect other JS generated by the plugin on the page. */
2463 if ( empty( $keys['secret_key'] ) || empty( $keys['public_key'] ) ) {
2464 charitable_get_notices()->add_error( __( 'Missing keys for Square payment gateway. Unable to proceed with payment.', 'charitable' ) );
2465 unset( $active_gateways['square_core'] );
2466 }
2467
2468 return $active_gateways;
2469 }
2470
2471 /**
2472 * Enqueue Square Web Payments SDK scripts.
2473 * Only load on donation forms.
2474 *
2475 * @since 1.8.7
2476 *
2477 * @return void
2478 */
2479 public function enqueue_square_scripts() {
2480 // Check if Square Core is active before loading scripts.
2481 if ( ! self::is_square_core_active() ) {
2482 if ( charitable_is_debug( 'square' ) ) {
2483 // phpcs:disable
2484 error_log( '[Charitable Square Core] Scripts not loaded - Square Core is not active' );
2485 // phpcs:enable
2486 }
2487 return;
2488 }
2489
2490 if ( charitable_is_debug( 'square' ) ) {
2491 // phpcs:disable
2492 error_log( '[Charitable Square Core] Loading Square Core scripts - gateway is active' );
2493 // phpcs:enable
2494 }
2495
2496 if ( ! is_admin() ) {
2497
2498 $connection = Charitable_Square_Connection::get();
2499
2500 if ( ! $connection || ! $connection->is_usable() ) {
2501 return;
2502 }
2503
2504 // Get current mode and debug information.
2505 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2506
2507 // Ensure the JavaScript reference in your HTML is pointing to the correct domain.
2508
2509 $script_url = $mode === 'test' ? 'https://sandbox.web.squarecdn.com/v1/square.js' : 'https://web.squarecdn.com/v1/square.js';
2510
2511 /* Register Square JS script. */
2512 wp_register_script(
2513 'square-payments-sdk',
2514 $script_url,
2515 array(),
2516 '1.0.0',
2517 true
2518 );
2519
2520 $dependencies = array( 'charitable-script', 'square-payments-sdk', 'jquery' );
2521
2522 wp_register_script(
2523 'charitable',
2524 charitable()->get_path( 'assets', false ) . 'js/square/charitable-square.js',
2525 $dependencies,
2526 '1.0.0',
2527 true
2528 );
2529
2530 /**
2531 * This filter allows to set a card configuration and styles.
2532 *
2533 * @since 1.8.7.
2534 * @version 1.8.7 removed 'billing_details' => $this->get_mapped_contact_fields( $forms ),
2535 *
2536 * @param array $card_config Configuration and style options.
2537 */
2538 $card_config = (array) apply_filters( 'charitable_square_frontend_enqueues_card_config', array() );
2539
2540 wp_localize_script(
2541 'charitable',
2542 'CHARITABLE_SQUARE_VARS',
2543 array(
2544 'client_id' => $connection->get_client_id(),
2545 'location_id' => Charitable_Square_Helpers::get_location_id(),
2546 'card_config' => $card_config,
2547 'enable_apple_pay' => false,
2548 'enable_google_pay' => false,
2549 'i18n' => array(
2550 'missing_sdk_script' => esc_html__( 'Square.js failed to load properly.', 'charitable' ),
2551 'general_error' => esc_html__( 'An unexpected Square SDK error has occurred.', 'charitable' ),
2552 'missing_creds' => esc_html__( 'Client ID and/or Location ID is incorrect.', 'charitable' ),
2553 'card_init_error' => esc_html__( 'Initializing Card failed.', 'charitable' ),
2554 'token_process_fail' => esc_html__( 'Tokenization of the payment card failed.', 'charitable' ),
2555 'token_status_error' => esc_html__( 'Tokenization failed with status:', 'charitable' ),
2556 'buyer_verify_error' => esc_html__( 'The verification was not successful. An issue occurred while verifying the buyer.', 'charitable' ),
2557 'empty_details' => esc_html__( 'Please fill out payment details to continue.', 'charitable' ),
2558 'wallet_not_supported' => esc_html__( 'Recurring payments can\'t be processed using Google Pay or Apple Pay. Please enter your card details.', 'charitable' ),
2559 ),
2560 )
2561 );
2562
2563 wp_enqueue_script( 'charitable' );
2564
2565 // Debug information.
2566 if ( charitable_is_debug( 'square' ) ) {
2567 // phpcs:disable
2568 error_log( 'Test / Square Mode: ' . $mode );
2569 error_log( 'Application ID: ' . $this->get_square_application_id( $mode ) );
2570 // phpcs:enable
2571 }
2572 }
2573 }
2574
2575 /**
2576 * Get the Square application ID.
2577 *
2578 * @since 1.8.7
2579 *
2580 * @param string $mode The mode to get the application ID for (test or live).
2581 * @return string
2582 */
2583 private function get_square_application_id( $mode = 'test' ) {
2584 if ( 'sandbox' === $mode ) {
2585 $mode = 'test';
2586 }
2587
2588 $option_value = charitable_get_option( array( 'gateways_square', $mode, 'application_id' ) );
2589
2590 return $option_value;
2591 }
2592
2593 /**
2594 * Get the Square location ID.
2595 * Might be deprecated.
2596 *
2597 * @since 1.8.7
2598 *
2599 * @param string $mode The mode to get the location ID for (test or live).
2600 * @return string
2601 */
2602 private function get_square_location_id( $mode = 'test' ) {
2603 if ( empty( $mode ) ) {
2604 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2605 }
2606 if ( 'sandbox' === $mode ) {
2607 $mode = 'test';
2608 }
2609
2610 $option_value = charitable_get_option( array( 'gateways_square', $mode, 'location' ) );
2611 return $option_value;
2612 }
2613
2614 /**
2615 * Retrieve the Disconnect button.
2616 *
2617 * @since 1.8.7
2618 *
2619 * @param string $mode Square mode.
2620 * @param bool $wrap Optional. Wrap a button HTML element or not.
2621 *
2622 * @return string
2623 */
2624 private function get_disconnect_button( string $mode, bool $wrap = true ): string {
2625
2626 $button = sprintf(
2627 '<a class="charitable-btn charitable-btn-md charitable-btn-light-grey" href="%1$s" title="%2$s">%3$s</a>',
2628 esc_url( $this->connect->get_disconnect_url( $mode ) ),
2629 esc_attr__( 'Disconnect Square account', 'charitable' ),
2630 esc_html__( 'Disconnect', 'charitable' )
2631 );
2632
2633 return $wrap ? '<p class="charitable-square-disconnect-btn">' . $button . '</p>' : $button;
2634 }
2635
2636 /**
2637 * Retrieve the Refresh tokens button.
2638 *
2639 * @since 1.8.7
2640 *
2641 * @param string $mode Square mode.
2642 *
2643 * @return string
2644 */
2645 private function get_refresh_button( string $mode ): string {
2646
2647 return sprintf(
2648 '<button class="charitable-btn charitable-btn-md charitable-btn-light-grey charitable-square-refresh-btn" type="button" data-mode="%1$s" data-url="%2$s" title="%3$s">%4$s</button>',
2649 esc_attr( $mode ),
2650 esc_url( $this->get_settings_page_url() ),
2651 esc_attr__( 'Refresh connection tokens', 'charitable' ),
2652 esc_html__( 'Refresh tokens', 'charitable' )
2653 );
2654 }
2655
2656 /**
2657 * Retrieve the Error icon emoji.
2658 *
2659 * @since 1.8.7
2660 *
2661 * @return string
2662 */
2663 private function get_error_icon(): string {
2664
2665 return '<span class="charitable-error-icon"></span>';
2666 }
2667
2668 /**
2669 * Get the settings page URL.
2670 *
2671 * @since 1.8.7
2672 *
2673 * @return string
2674 */
2675 public function get_settings_page_url() {
2676
2677 return add_query_arg(
2678 array(
2679 'page' => 'charitable-settings',
2680 'tab' => 'gateways',
2681 'group' => 'gateways_square_core',
2682 ),
2683 admin_url( 'admin.php' )
2684 );
2685 }
2686
2687 /**
2688 * Create instance of the class.
2689 *
2690 * @since 1.8.7
2691 *
2692 * @return Charitable_Gateway_Square
2693 */
2694 public static function get_instance() {
2695 if ( null === self::$instance ) {
2696 self::$instance = new self();
2697 }
2698 return self::$instance;
2699 }
2700
2701 /**
2702 * Check the Square token status and display admin notice if needed.
2703 *
2704 * @since 1.8.7
2705 */
2706 public function check_token_status() {
2707 global $wpdb;
2708
2709 // Only run on Square settings page.
2710 if ( ! is_admin() ) {
2711 return;
2712 }
2713
2714 // If the error notice is already set, do not run this again.
2715 $option = get_option( 'charitable_square_connection_error_notice' );
2716 if ( ! empty( $option ) ) {
2717 // If the error notice is already set, do not run this again.
2718 if ( charitable_is_debug( 'square' ) ) {
2719 // phpcs:disable
2720 error_log( 'Square token status check already performed. Skipping.' );
2721 // phpcs:enable
2722 }
2723 return;
2724 }
2725
2726 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2727 $connection = Charitable_Square_Connection::get( $mode );
2728 $api = new Charitable_Square_API( $connection );
2729
2730 if ( isset( $_GET['test_delete'] ) ) { //phpcs:ignore
2731 $status['is_valid'] = '';
2732
2733 $this->generate_admin_notice_for_square_connection_error( __( 'Your Square connection has expired or been revoked. Please reconnect to continue accepting donations.', 'charitable' ), 'square_connection_error' );
2734 }
2735
2736 // Only check if we have a configured connection.
2737 if ( ! $connection || ! $connection->is_configured() ) {
2738 return;
2739 }
2740
2741 $status = $api->check_token_status();
2742
2743 if ( charitable_is_debug( 'square' ) ) {
2744 error_log( 'Square token status check: ' . print_r( $status, true ) ); //phpcs:ignore
2745 }
2746
2747 if ( ! $status['is_valid'] ) {
2748
2749 if ( charitable_is_debug( 'square' ) ) {
2750 error_log( 'Square token status check failed: ' . $status['message'] ); //phpcs:ignore
2751 }
2752
2753 if ( $connection ) {
2754 // Delete the transient to ensure we can recreate the error message.
2755 $connections = (array) get_option( 'charitable_square_connections', array() );
2756 unset( $connections[ $mode ] );
2757
2758 empty( $connections ) ? delete_option( 'charitable_square_connections' ) : update_option( 'charitable_square_connections', $connections );
2759
2760 $settings = get_option( 'charitable_settings' );
2761
2762 $settings['gateways_square'][ $mode ] = array();
2763
2764 $data = array(
2765 'option_value' => maybe_serialize( $settings ),
2766 );
2767
2768 $where = array(
2769 'option_name' => 'charitable_settings',
2770 );
2771
2772 $ret = $wpdb->update( //phpcs:ignore
2773 $wpdb->options,
2774 $data,
2775 $where,
2776 array( '%s' ),
2777 array( '%s' )
2778 );
2779
2780 }
2781
2782 // Are in live mode (production)?
2783 if ( ! charitable_get_option( 'test_mode' ) ) {
2784 Charitable_Square_Connect::get_instance()->unschedule_refresh();
2785 }
2786
2787 // Webhooks are now manually configured in Square dashboard - no need to reset.
2788 // Charitable_Square_Helpers::reset_webhook_configuration( true );.
2789
2790 // Not needed because this as stored in the "connection" that was wiped above.
2791 $api->set_location_id( '', $mode );
2792 $api->set_location_currency( '', $mode );
2793 $api->detete_transients( $mode );
2794
2795 // Use a regex to extract the value between the quotes after "detail".
2796 preg_match( '/"detail":"([^"]+)"/', $status['message'], $matches );
2797 $message = isset( $matches[1] ) ? $matches[1] : '';
2798 // Get the Status code.
2799 preg_match( '/Status code: (\d+)/', $status['message'], $matches );
2800 $status_code = isset( $matches[1] ) ? $matches[1] : '';
2801
2802 $this->generate_admin_notice_for_square_connection_error( $message, $status_code, 'square_connection_error' );
2803
2804 // redirect to the settings page.
2805 if ( ! wp_doing_ajax() ) {
2806 wp_safe_redirect( $this->get_settings_page_url() );
2807 exit;
2808 }
2809 }
2810 }
2811
2812 /**
2813 * Generate admin notice for Square connection error.
2814 *
2815 * @since 1.8.7
2816 *
2817 * @param string $message The error message.
2818 * @param string $status_code The status code.
2819 * @param string $notice_key The notice key.
2820 */
2821 private function generate_admin_notice_for_square_connection_error( $message = '', $status_code = '', $notice_key = '' ) {
2822 // Store the notice in the database.
2823 $admin_notice = array(
2824 'message' => $message,
2825 'status_code' => $status_code,
2826 'time_added' => time(),
2827 );
2828
2829 update_option( 'charitable_square_connection_error_notice', $admin_notice );
2830 }
2831
2832 /**
2833 * Check if this is the Square settings page.
2834 *
2835 * @since 1.8.7
2836 *
2837 * @return boolean
2838 */
2839 private function is_square_settings_page() {
2840 return charitable_is_admin_screen( 'settings' ) && isset( $_GET['group'] ) && 'gateways_square_core' === $_GET['group']; //phpcs:ignore
2841 }
2842
2843 /**
2844 * Check if we should clear the mode connection warning.
2845 *
2846 * @since 1.8.5
2847 *
2848 * @param array $values The values being saved.
2849 * @return void
2850 */
2851 private function maybe_clear_mode_connection_warning( $values ) {
2852 $warning = get_option( 'charitable_square_mode_connection_warning' );
2853
2854 if ( ! $warning ) {
2855 return;
2856 }
2857
2858 $mode = $warning['mode'];
2859
2860 // Check if there's now a valid connection for the mode that had the warning.
2861 if ( ! empty( $values['gateways_square'][ $mode ]['access_token'] ) ) {
2862 $connection = Charitable_Square_Connection::get( $mode );
2863
2864 if ( $connection && $connection->is_configured() && $connection->is_valid() ) {
2865 // Clear the warning since we now have a valid connection.
2866 delete_option( 'charitable_square_mode_connection_warning' );
2867 }
2868 }
2869 }
2870
2871 /**
2872 * Check if Square Core gateway is active.
2873 *
2874 * @since 1.8.7
2875 *
2876 * @return boolean
2877 */
2878 public static function is_square_core_active() {
2879 $active_gateways = charitable_get_helper( 'gateways' )->get_active_gateways();
2880 $is_active = isset( $active_gateways['square_core'] );
2881
2882 if ( charitable_is_debug( 'square' ) ) {
2883 // phpcs:disable
2884 error_log( '[Charitable Square Core] Checking if Square Core is active: ' . ( $is_active ? 'true' : 'false' ) );
2885 // phpcs:enable
2886 }
2887
2888 return $is_active;
2889 }
2890
2891 /**
2892 * Check if Square Legacy gateway is active.
2893 *
2894 * @since 1.8.7
2895 *
2896 * @return boolean
2897 */
2898 public static function is_square_legacy_active() {
2899 $active_gateways = charitable_get_helper( 'gateways' )->get_active_gateways();
2900 $is_active = isset( $active_gateways['square'] );
2901
2902 if ( charitable_is_debug( 'square' ) ) {
2903 // phpcs:disable
2904 error_log( '[Charitable Square Core] Checking if Square Legacy is active: ' . ( $is_active ? 'true' : 'false' ) );
2905 // phpcs:enable
2906 }
2907
2908 return $is_active;
2909 }
2910
2911 /**
2912 * Get the donation amount including fees if fee relief is enabled.
2913 *
2914 * @since 1.8.8.3
2915 *
2916 * @param Charitable_Donation $donation The donation object.
2917 * @return float
2918 */
2919 private function get_donation_amount_with_fees( $donation ) {
2920 // Check if fee relief is enabled and donor opted to cover fees
2921 if ( $donation->get( 'cover_fees' ) ) {
2922 // Use the total donation amount including fees
2923 return \Charitable_Currency::get_instance()->cast_to_decimal_format( $donation->get( 'total_donation_with_fees' ) );
2924 }
2925
2926 // Return the standard donation amount
2927 return $donation->get_total_donation_amount( true );
2928 }
2929 }
2930 endif;
2931