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

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

2,939 lines 99.4 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 // Convert to cents — skip for zero-decimal currencies (JPY, KRW,
1724 // VND, etc.) where Square expects the amount in the full unit, not
1725 // 1/100ths. Without this guard, e.g. a ¥1000 donation would be
1726 // submitted as 100000 and the donor charged 100x the intended
1727 // amount.
1728 if ( ! Charitable_Square_Gateway_Processor::is_zero_decimal_currency( $donation->get_currency() ) ) {
1729 $donation_amount = $donation_amount * 100;
1730 }
1731 $args_name = 'Donation ID: ' . $donation_id . ' | ' . ( 'custom' === ! empty( $_POST['donation_amount'] ) ? 'Custom Amount' : 'Suggested Amount' ); // phpcs:ignore
1732
1733 // Set tokens provided by Web Payments SDK.
1734 $this->api->set_payment_tokens( $square_token ); // $entry
1735
1736 if ( charitable_is_debug( 'square' ) ) {
1737 // phpcs:disable
1738 error_log( 'process_donation mode is ' . $mode );
1739 // phpcs:enable
1740 }
1741
1742 $location_id = $this->api->get_location_id( $mode );
1743 $note = get_bloginfo( 'name' ) . ': ' . $campaign_name;
1744
1745 $args_payment_single = array(
1746 'amount' => $donation_amount,
1747 'currency' => charitable_get_currency(),
1748 'location_id' => $location_id,
1749 'note' => $note,
1750 'order_items' => array(
1751 array(
1752 'name' => 'Donations',
1753 'quantity' => 1,
1754 'variations' => array(
1755 array(
1756 'name' => 'Donation',
1757 'quantity' => 1,
1758 'variation_name' => $args_name,
1759 'amount' => $donation_amount,
1760 ),
1761 ),
1762 ),
1763 ),
1764 );
1765
1766 $subscription_post_id = $donation->get( 'post_parent' );
1767
1768 if ( $subscription_post_id ) {
1769
1770 if ( charitable_is_debug( 'square' ) ) {
1771 // phpcs:disable
1772 error_log( 'process_donation - subscription_post_id is ' . $subscription_post_id );
1773 // phpcs:enable
1774 }
1775
1776 $subscription = charitable_get_donation( $subscription_post_id );
1777
1778 if ( charitable_is_debug( 'square' ) ) {
1779 // phpcs:disable
1780 // error_log( 'donation is ' . print_r( $donation, true ) );
1781 error_log( 'subscription is ' . print_r( $subscription, true ) );
1782 error_log( 'POST is ' . print_r( $_POST, true ) );
1783 // phpcs:enable
1784 }
1785
1786 $plan = $_POST; //phpcs:ignore
1787
1788 $args_subscription = $this->get_payment_args_subscription( $plan, $campaign_name, $donation_amount );
1789
1790 $args_subscription_single = array(
1791 'amount' => $donation_amount,
1792 'currency' => charitable_get_currency(),
1793 'location_id' => $location_id,
1794 'subscription' => $args_subscription,
1795 );
1796
1797 $result = $this->api->process_subscription_transaction( $args_subscription_single );
1798
1799 if ( charitable_is_debug( 'square' ) ) {
1800 // phpcs:disable
1801 error_log( 'process_donation - result process subscription transaction' );
1802 error_log( print_r( $result, true ) );
1803 // phpcs:enable
1804 }
1805
1806 $subscription_id = $result->getId();
1807 $plan_variation_id = $result->getPlanVariationId();
1808 $customer_id = $result->getCustomerId();
1809 $start_date = $result->getStartDate();
1810 $status = $result->getStatus();
1811
1812 if ( charitable_is_debug( 'square' ) ) {
1813 // phpcs:disable
1814 error_log( 'process_donation - subscription_id is ' . print_r( $subscription_id, true ) );
1815 error_log( 'process_donation - plan_variation_id is ' . print_r( $plan_variation_id, true ) );
1816 error_log( 'process_donation - customer_id is ' . print_r( $customer_id, true ) );
1817 error_log( 'process_donation - start_date is ' . print_r( $start_date, true ) );
1818 error_log( 'process_donation - status is ' . print_r( $status, true ) );
1819 // phpcs:enable
1820 }
1821
1822 $subscription->set_gateway_subscription_id( $subscription_id );
1823 $message = sprintf( '%s: %s', __( 'Square Subscription ID', 'charitable' ), $subscription_id );
1824 $subscription->log()->add( $message );
1825
1826 if ( 'ACTIVE' === $status ) {
1827 $subscription->update_status( 'charitable-active' );
1828 } else {
1829 $subscription->update_status( 'charitable-pending' );
1830 }
1831
1832 if ( charitable_is_debug( 'webhook' ) ) {
1833 // Create a single array of meta data with the remaining items.
1834 $meta_data = array(
1835 'subscription_id' => $subscription_id,
1836 'plan_variation_id' => $plan_variation_id,
1837 'customer_id' => $customer_id,
1838 'start_date' => $start_date,
1839 'status' => $status,
1840 );
1841 update_post_meta( $subscription_post_id, '_square_subscription_meta_data', $meta_data );
1842 }
1843
1844 // We need to update the donation so that we can find it again when the invoice is paid.
1845 update_post_meta( $donation_id, '_square_subscription_donation_first_donation', $subscription_post_id );
1846
1847 if ( charitable_is_debug( 'square' ) ) {
1848 // phpcs:disable
1849 error_log( 'process_donation - donation_id is ' . $donation_id );
1850 error_log( 'process_donation - subscription_post_id is ' . $subscription_post_id );
1851 // phpcs:enable
1852 }
1853 } else {
1854
1855 $result = $this->api->process_single_transaction( $args_payment_single );
1856
1857 if ( charitable_is_debug( 'square' ) ) {
1858 // phpcs:disable
1859 error_log( 'process_donation - result process single transaction' );
1860 error_log( print_r( $result, true ) );
1861 // phpcs:enable
1862 }
1863
1864 // Set payment processing flag.
1865 $this->is_payment_processed = true;
1866 $order = null;
1867 $order_id = '';
1868
1869 $errors = $this->process_api_errors( 'single' );
1870
1871 if ( ! empty( $errors ) ) {
1872 // Process errors and handle categories.
1873 $error_messages = array();
1874 foreach ( $errors as $error ) {
1875 if ( is_array( $error ) && isset( $error['category'] ) ) {
1876 // Handle specific error categories.
1877 switch ( $error['category'] ) {
1878 case 'AUTHENTICATION_ERROR':
1879 $error_messages[] = __( 'Authentication error with Square. Please check your Square Gateway settings.', 'charitable' );
1880 break;
1881 default:
1882 $error_messages[] = $error['message'];
1883 break;
1884 }
1885 } else {
1886 $error_messages[] = $error;
1887 }
1888 }
1889
1890 // If an array is returned, we need to implode it.
1891 $error_message = implode( ', ', $error_messages );
1892 $message = sprintf( '%s: %s', __( 'Errors', 'charitable' ), $error_message );
1893 $donation->log()->add( $message );
1894
1895 // Add a general admin notice to alert the user that there was an error.
1896 $admin_message = sprintf(
1897 '%s' . "\n" . 'Details: %s',
1898 __( 'Errors have occurred while processing Square donations. You may need to check the connection to your Square account.', 'charitable' ),
1899 $error_message
1900 );
1901
1902 if ( charitable_is_debug( 'square' ) ) {
1903 error_log( 'Adding Square admin notice: ' . $admin_message ); //phpcs:ignore
1904 }
1905
1906 // Get current notices.
1907 $current_notices = charitable_get_admin_notices()->get_notices();
1908 $notice_exists = false;
1909
1910 // Check if this exact error message already exists.
1911 if ( ! empty( $current_notices['error'] ) ) {
1912 foreach ( $current_notices['error'] as $notice ) {
1913 if ( $notice['message'] === $admin_message ) {
1914 $notice_exists = true;
1915 break;
1916 }
1917 }
1918 }
1919
1920 // Only add the notice if it doesn't already exist.
1921 if ( ! $notice_exists ) {
1922 charitable_get_admin_notices()->add_notice( $admin_message, 'error', false, true );
1923 }
1924
1925 if ( charitable_is_debug( 'square' ) ) {
1926 error_log( 'Current admin notices: ' . print_r( charitable_get_admin_notices()->get_notices(), true ) ); //phpcs:ignore
1927 }
1928
1929 $donation->update_status( 'charitable-failed' );
1930
1931 wp_send_json_error( $admin_message );
1932 exit;
1933 }
1934
1935 if ( isset( $result['order'] ) ) {
1936 $order = $result['order'];
1937 $order_id = $order->getId();
1938 }
1939
1940 if ( $order_id ) {
1941 // This Square Order / Transaction ID is used by the webhook to update the donation status.
1942 $donation->set_gateway_transaction_id( $order_id );
1943 $message = sprintf( '%s: %s', __( 'Square Order / Transaction ID', 'charitable' ), $order_id );
1944 $donation->log()->add( $message );
1945 }
1946 }
1947
1948 if ( charitable_is_debug( 'square' ) ) {
1949 // phpcs:disable
1950 error_log( 'process_donation - end' );
1951 // phpcs:enable
1952 }
1953
1954 $donation->update_status( 'charitable-pending' );
1955
1956 return true;
1957 }
1958
1959 /**
1960 * Retrieve subscription payment args.
1961 *
1962 * @since 1.8.7
1963 *
1964 * @param array $plan Plan settings.
1965 * @param string $campaign_name Campaign name.
1966 * @param int $donation_amount Donation amount.
1967 *
1968 * @return array
1969 */
1970 private function get_payment_args_subscription( array $plan, string $campaign_name, int $donation_amount ): array {
1971
1972 $args_sub = array();
1973 $currency = charitable_get_currency();
1974
1975 $recurring_donation_plan = ! empty( $plan['recurring_donation_plan'] ) ? $plan['recurring_donation_plan'] : '';
1976 $recurring_donation_plan = empty( $recurring_donation_plan ) && ! empty( $plan['recurring_donation'] ) ? sanitize_text_field( $plan['recurring_donation'] ) : $recurring_donation_plan;
1977
1978 $recurring_donation_plan = apply_filters( 'charitable_square_recurring_donation_plan', $recurring_donation_plan, $plan );
1979
1980 switch ( $recurring_donation_plan ) {
1981 case 'day':
1982 case 'daily':
1983 $phase_cadence_key = 'daily';
1984 break;
1985 case 'week':
1986 case 'weekly':
1987 $phase_cadence_key = 'weekly';
1988 break;
1989 case 'month':
1990 case 'monthly':
1991 $phase_cadence_key = 'monthly';
1992 break;
1993 case 'quarter':
1994 case 'quarterly':
1995 $phase_cadence_key = 'quarterly';
1996 break;
1997 case 'semiyear':
1998 case 'semiyearly':
1999 $phase_cadence_key = 'semiyearly';
2000 break;
2001 case 'year':
2002 case 'yearly':
2003 case 'annually':
2004 case 'annual':
2005 $phase_cadence_key = 'yearly';
2006 break;
2007 }
2008 $plan['name'] = get_bloginfo( 'name' );
2009
2010 $args_sub['customer']['email'] = sanitize_email( $_POST['email'] ); //phpcs:ignore
2011 $args_sub['customer']['first_name'] = sanitize_text_field( $_POST['first_name'] ); //phpcs:ignore
2012 $args_sub['customer']['last_name'] = sanitize_text_field( $_POST['last_name'] ); //phpcs:ignore
2013
2014 // Customer address.
2015 // There's address_2, city, state, postcode, country.
2016 $args_sub['customer']['address'] = ! empty( $_POST['address'] ) ? sanitize_text_field( $_POST['address'] ) : ''; //phpcs:ignore
2017 $args_sub['customer']['address_2'] = ! empty( $_POST['address_2'] ) ? sanitize_text_field( $_POST['address_2'] ) : ''; //phpcs:ignore
2018 $args_sub['customer']['city'] = ! empty( $_POST['city'] ) ? sanitize_text_field( $_POST['city'] ) : ''; //phpcs:ignore
2019 $args_sub['customer']['state'] = ! empty( $_POST['state'] ) ? sanitize_text_field( $_POST['state'] ) : ''; //phpcs:ignore
2020 $args_sub['customer']['postcode'] = ! empty( $_POST['postcode'] ) ? sanitize_text_field( $_POST['postcode'] ) : ''; //phpcs:ignore
2021 $args_sub['customer']['country'] = ! empty( $_POST['country'] ) ? sanitize_text_field( $_POST['country'] ) : ''; //phpcs:ignore
2022 $args_sub['customer']['phone'] = ! empty( $_POST['phone'] ) ? sanitize_text_field( $_POST['phone'] ) : ''; //phpcs:ignore
2023
2024 $cadences_list = Charitable_Square_Helpers::get_subscription_cadences();
2025
2026 if ( charitable_is_debug( 'square' ) ) {
2027 // phpcs:disable
2028 error_log( 'cadences_list is ' . print_r( $cadences_list, true ) );
2029 // phpcs:enable
2030 }
2031
2032 $phase_cadence = $cadences_list[ $phase_cadence_key ] ?? $cadences_list['yearly'];
2033
2034 // Subscription cadence.
2035 $args_sub['phase_cadence'] = $phase_cadence;
2036
2037 $plan_name = $campaign_name;
2038 $plan_name .= empty( $plan['name'] ) ? '' : ': ' . $plan['name'];
2039
2040 $args_sub['plan_name'] = sprintf( '%s (%s)', $plan_name, $phase_cadence['name'] );
2041 $args_sub['plan_variation_name'] = sprintf( '%s (%s %s %s)', $plan['name'], $donation_amount, $currency, $phase_cadence['name'] );
2042
2043 // Card holder.
2044 $args_sub['card_name'] = sanitize_text_field( $_POST['first_name'] ) . ' ' . sanitize_text_field( $_POST['last_name'] ); // phpcs:ignore
2045
2046 if ( charitable_is_debug( 'square' ) ) {
2047 // phpcs:disable
2048 error_log( 'get_payment_args_subscription - args_sub is ' . print_r( $args_sub, true ) );
2049 // phpcs:enable
2050 }
2051
2052 /**
2053 * Filter subscription payment arguments.
2054 *
2055 * @since 1.8.7
2056 *
2057 * @param array $args The subscription payment arguments.
2058 * @param Process $process The Process instance.
2059 */
2060 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.
2061 }
2062
2063 /**
2064 * Collect errors from API and turn it into form errors.
2065 *
2066 * @since 1.8.7
2067 *
2068 * @param string $type Payment type (e.g. 'single').
2069 */
2070 private function process_api_errors( string $type ) {
2071
2072 $errors = $this->api->get_errors();
2073
2074 if ( empty( $errors ) || ! is_array( $errors ) ) {
2075 return;
2076 }
2077
2078 $this->log_errors( $errors );
2079
2080 if ( $type === 'subscription' ) {
2081 $title = esc_html__( 'Square subscription payment stopped', 'charitable' );
2082 } else {
2083 $title = esc_html__( 'Square payment stopped', 'charitable' );
2084 }
2085
2086 $_errors = $this->api->get_response_errors();
2087
2088 if ( ! empty( $_errors ) ) {
2089 $this->process_api_errors_codes( $_errors );
2090 $errors[] = $_errors;
2091 }
2092
2093 // Process errors and extract categories.
2094 $processed_errors = array();
2095 foreach ( $errors as $error ) {
2096 if ( is_array( $error ) && isset( $error['category'] ) ) {
2097 // Handle error with category.
2098 $processed_errors[] = array(
2099 'message' => $error['message'],
2100 'category' => $error['category'],
2101 );
2102 } else {
2103 // Handle regular error string.
2104 $processed_errors[] = $error;
2105 }
2106 }
2107
2108 if ( ! empty( $processed_errors ) && charitable_is_debug( 'square' ) ) {
2109 // phpcs:disable
2110 error_log( 'Charitable_Gateway_Square. Errors reported in API. ' . print_r( $processed_errors, true ) );
2111 // phpcs:enable
2112 }
2113
2114 return $processed_errors;
2115 }
2116
2117 /**
2118 * Display form errors.
2119 *
2120 * @since 1.8.7
2121 *
2122 * @param array $errors Errors to display.
2123 */
2124 private function log_errors( array $errors = array() ) {
2125
2126 if ( ! $errors ) {
2127 $errors = $this->errors;
2128 }
2129
2130 if ( ! $errors || ! is_array( $errors ) ) {
2131 return;
2132 }
2133
2134 if ( charitable_is_debug( 'square' ) ) {
2135 // phpcs:disable
2136 error_log( 'Charitable_Gateway_Square. Errors replorted in API. ' . print_r( $errors, true ) );
2137 // phpcs:enable
2138 }
2139 }
2140
2141 /**
2142 * Check specific error codes.
2143 *
2144 * @since 1.8.7
2145 *
2146 * @param array $errors The last API call errors.
2147 */
2148 private function process_api_errors_codes( array $errors ) {
2149
2150 $codes = $this->get_oauth_error_codes();
2151
2152 foreach ( $errors as $error ) {
2153
2154 if (
2155 empty( $error['code'] ) ||
2156 ! in_array( $error['code'], $codes, true )
2157 ) {
2158 continue;
2159 }
2160
2161 // If the error indicates that access token is bad, set a connection as invalid.
2162 $this->connection
2163 ->set_status( Charitable_Square_Connection::STATUS_INVALID )
2164 ->save();
2165 }
2166 }
2167
2168 /**
2169 * Retrieve OAuth-related errors.
2170 * used to be return [ ErrorCode::ACCESS_TOKEN_EXPIRED, ErrorCode::ACCESS_TOKEN_REVOKED, ErrorCode::UNAUTHORIZED ];
2171 *
2172 * @since 1.8.7
2173 *
2174 * @link https://developer.squareup.com/docs/oauth-api/best-practices#ensure-api-calls-made-with-oauth-tokens-handle-token-based-errors-appropriately
2175 *
2176 * @return array
2177 */
2178 private function get_oauth_error_codes(): array {
2179 return array( 'ACCESS_TOKEN_EXPIRED', 'ACCESS_TOKEN_REVOKED', 'UNAUTHORIZED' );
2180 }
2181
2182 /**
2183 * Validate the submitted credit card details.
2184 *
2185 * @since 1.8.7
2186 * @since 1.4.0 Deprecated.
2187 * @since 1.4.7 Restored. No longer deprecated.
2188 *
2189 * @param boolean $valid Whether the donation is valid.
2190 * @param string $gateway The chosen gateway.
2191 * @param mixed[] $values The filtered values from the donation form submission.
2192 * @return boolean
2193 */
2194 public static function validate_donation( $valid, $gateway, $values ) {
2195 if ( 'square_core' !== $gateway ) {
2196 return $valid;
2197 }
2198
2199 if ( charitable_is_debug( 'square' ) ) {
2200 // phpcs:disable
2201 error_log( 'validate_donation : Charitable_Gateway_Square is making an attempt to validate the donation' );
2202 error_log( '$valid: ' . $valid );
2203 error_log( '$gateway: ' . $gateway );
2204 error_log( '$values: ' . print_r( $values, true ) );
2205 // phpcs:enable
2206 }
2207
2208 $gateway = new Charitable_Gateway_Square();
2209 $test_mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2210
2211 if ( empty( charitable_square_get_access_token( $test_mode ) ) ) {
2212 charitable_get_notices()->add_error( __( 'Missing access token for Square payment gateway. Unable to proceed with payment.', 'charitable' ) );
2213 return false;
2214 }
2215 return $valid;
2216 }
2217
2218 /**
2219 * Check whether a particular donation can be refunded automatically in Square.
2220 *
2221 * @since 1.8.7
2222 *
2223 * @param Charitable_Donation $donation The donation object.
2224 * @return boolean
2225 */
2226 public function is_donation_refundable( Charitable_Donation $donation ) {
2227
2228 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2229 $connection = Charitable_Square_Connection::get( $mode );
2230
2231 if ( ! $connection || ! $connection->is_configured() ) {
2232 $this->errors[] = esc_html__( 'Square account connection is missing.', 'charitable' );
2233 return false;
2234 }
2235
2236 if ( ! $connection->is_valid() ) {
2237 $this->errors[] = esc_html__( 'Square account connection is invalid.', 'charitable' );
2238 return false;
2239 }
2240
2241 if ( ! $connection->is_currency_matched() ) {
2242 $this->errors[] = esc_html__( 'The currency associated with the payment is not valid for the provided business location.', 'charitable' );
2243 return false;
2244 }
2245
2246 // We need to get the payment id from the donation.
2247 $payment_id = $donation->get_gateway_payment_id();
2248 if ( ! $payment_id ) {
2249 return false;
2250 }
2251
2252 return true;
2253 }
2254
2255 /**
2256 * Process a refund initiated in the WordPress dashboard.
2257 *
2258 * @since 1.8.7
2259 * @since 1.8.8.4
2260 *
2261 * @param int $donation_id The donation ID.
2262 * @return boolean
2263 */
2264 public static function refund_donation_from_dashboard( $donation_id ) {
2265 // Debug: Log that the Square Core refund method is being called.
2266 if ( charitable_is_debug( 'square' ) ) {
2267 // phpcs:disable
2268 error_log( '[Square Core] Refund method called for donation ID: ' . $donation_id );
2269 // phpcs:enable
2270 }
2271
2272 $donation = charitable_get_donation( $donation_id );
2273
2274 if ( ! $donation ) {
2275 if ( charitable_is_debug( 'square' ) ) {
2276 // phpcs:disable
2277 error_log( '[Square Core] Refund failed - donation not found for ID: ' . $donation_id );
2278 // phpcs:enable
2279 }
2280 return false;
2281 }
2282
2283 try {
2284
2285 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2286 $connection = Charitable_Square_Connection::get( $mode );
2287 $api = new Charitable_Square_API( $connection );
2288
2289 $payment_id = $donation->get_gateway_payment_id();
2290
2291 if ( ! $payment_id ) {
2292 return false;
2293 }
2294
2295 $currency = $donation->get_currency();
2296 $amount = self::get_donation_amount_with_fees_static( $donation );
2297 // Convert amount to cents — skip for zero-decimal currencies
2298 // (JPY, KRW, VND, etc.).
2299 $amount_cents = Charitable_Square_Gateway_Processor::is_zero_decimal_currency( $currency )
2300 ? $amount
2301 : $amount * 100;
2302
2303 $args = array(
2304 'reason' => 'Refunded from the WordPress dashboard',
2305 'amount' => $amount_cents,
2306 'currency' => $currency,
2307 );
2308
2309 $refund = $api->refund_payment( $payment_id, $args );
2310
2311 if ( ! $refund ) {
2312 // Log the failed refund.
2313 $donation->log()->add(
2314 sprintf(
2315 /* translators: %s: error message. */
2316 __( 'Square refund failed: %s', 'charitable' ),
2317 $api->get_errors()
2318 )
2319 );
2320 return false;
2321 }
2322 // Log the refund.
2323 $donation->log()->add(
2324 sprintf(
2325 /* translators: %s: transaction reference. */
2326 __( 'Square refund successful. Refund transaction ID: %s', 'charitable' ),
2327 $refund->getId()
2328 )
2329 );
2330
2331 } catch ( Exception $e ) {
2332 $donation->log()->add(
2333 sprintf(
2334 /* translators: %s: error message. */
2335 __( 'Square refund failed: %s', 'charitable' ),
2336 $e->getMessage()
2337 )
2338 );
2339 return false;
2340 }
2341 }
2342
2343 /**
2344 * Get the donation amount including fees if fee relief is enabled.
2345 *
2346 * @since 1.8.8.4
2347 *
2348 * @param Charitable_Donation $donation The donation object.
2349 * @return float
2350 */
2351 private static function get_donation_amount_with_fees_static( $donation ) {
2352 if ( ! $donation ) {
2353 return 0.0;
2354 }
2355
2356 // Check if fee relief is enabled and donor opted to cover fees.
2357 if ( $donation->get( 'cover_fees' ) ) {
2358 return \Charitable_Currency::get_instance()->cast_to_decimal_format( $donation->get( 'total_donation_with_fees' ) );
2359 }
2360
2361 // Return the standard donation amount.
2362 return (float) $donation->get_total_donation_amount( true );
2363 }
2364
2365 /**
2366 * Cancel a subscription.
2367 *
2368 * To cancel a subscription, you need to:
2369 * Create a CancelSubscriptionsRequest object
2370 * Pass it to the subscriptions->cancel() method
2371 * Handle the response to determine success or failure
2372 *
2373 * @since 1.8.7
2374 *
2375 * @param boolean $cancelled Whether the subscription was cancelled successfully in the gateway.
2376 * @param Charitable_Recurring_Donation $donation The recurring donation object.
2377 * @return boolean
2378 */
2379 public static function cancel_subscription( $cancelled, $donation ) {
2380
2381 $subscription_id = $donation->get_gateway_subscription_id();
2382
2383 if ( charitable_is_debug( 'square' ) ) {
2384 // phpcs:disable
2385 error_log( 'cancel_subscription - subscription_id: ' . $subscription_id );
2386 // phpcs:enable
2387 }
2388
2389 if ( ! $subscription_id ) {
2390 return false;
2391 }
2392
2393 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2394 $connection = Charitable_Square_Connection::get( $mode );
2395 $api = new Charitable_Square_API( $connection );
2396
2397 $result = $api->process_subscription_cancellation( $subscription_id );
2398
2399 if ( charitable_is_debug( 'square' ) ) {
2400 // phpcs:disable
2401 error_log( 'cancel_subscription - result: ' . $result );
2402 // phpcs:enable
2403 }
2404
2405 if ( $result ) {
2406 $donation->log()->add( __( 'Subscription initialised for cancellation in Square.', 'charitable' ) );
2407 update_post_meta( $donation->ID, '_square_cancelled_to_be_confirmed', 'true' );
2408 return true;
2409 } else {
2410 $errors = $api->get_errors();
2411 $message = ! empty( $errors ) ? implode( ', ', $errors ) : __( 'Unknown error.', 'charitable' );
2412
2413 $donation->log()->add(
2414 sprintf(
2415 /* translators: %s: error message. */
2416 __( 'Square cancellation failed: %s', 'charitable' ),
2417 $message
2418 )
2419 );
2420
2421 return false;
2422 }
2423 }
2424
2425 /**
2426 * Sets up the API.
2427 *
2428 * This sets the API key, specifies an API version to use, and also
2429 * sets the App info.
2430 *
2431 * This function is deprecated.
2432 *
2433 * @since 1.8.7
2434 *
2435 * @param string|null $api_key The API key. Might be the access token. (Maybe later: If null, will use the secret key).
2436 * @return boolean
2437 */
2438 public function setup_api( $api_key = null ) {
2439 return true;
2440 }
2441
2442
2443 /**
2444 * Determine if the Square addon should be shown on the frontend if no keys are found
2445 *
2446 * @param array[] $active_gateways The list of registered gateways.
2447 * @return string[]
2448 * @since 1.8.7
2449 * @version 1.8.7 - not currently used.
2450 */
2451 public function maybe_active_public_gateway( $active_gateways ) {
2452
2453 if ( ! isset( $active_gateways['square_core'] ) ) {
2454 return $active_gateways;
2455 }
2456
2457 if ( is_admin() ) {
2458 // if this is the admin backend don't mess with this list.
2459 return $active_gateways;
2460 }
2461
2462 global $post;
2463
2464 if ( false === $post || ! isset( $post->ID ) ) {
2465 return $active_gateways;
2466 }
2467
2468 // ok, this is being loaded as an active gateway - but do we have any keys?
2469 $keys = $this->get_keys();
2470
2471 /* 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. */
2472 if ( empty( $keys['secret_key'] ) || empty( $keys['public_key'] ) ) {
2473 charitable_get_notices()->add_error( __( 'Missing keys for Square payment gateway. Unable to proceed with payment.', 'charitable' ) );
2474 unset( $active_gateways['square_core'] );
2475 }
2476
2477 return $active_gateways;
2478 }
2479
2480 /**
2481 * Enqueue Square Web Payments SDK scripts.
2482 * Only load on donation forms.
2483 *
2484 * @since 1.8.7
2485 *
2486 * @return void
2487 */
2488 public function enqueue_square_scripts() {
2489 // Check if Square Core is active before loading scripts.
2490 if ( ! self::is_square_core_active() ) {
2491 if ( charitable_is_debug( 'square' ) ) {
2492 // phpcs:disable
2493 error_log( '[Charitable Square Core] Scripts not loaded - Square Core is not active' );
2494 // phpcs:enable
2495 }
2496 return;
2497 }
2498
2499 if ( charitable_is_debug( 'square' ) ) {
2500 // phpcs:disable
2501 error_log( '[Charitable Square Core] Loading Square Core scripts - gateway is active' );
2502 // phpcs:enable
2503 }
2504
2505 if ( ! is_admin() ) {
2506
2507 $connection = Charitable_Square_Connection::get();
2508
2509 if ( ! $connection || ! $connection->is_usable() ) {
2510 return;
2511 }
2512
2513 // Get current mode and debug information.
2514 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2515
2516 // Ensure the JavaScript reference in your HTML is pointing to the correct domain.
2517
2518 $script_url = $mode === 'test' ? 'https://sandbox.web.squarecdn.com/v1/square.js' : 'https://web.squarecdn.com/v1/square.js';
2519
2520 /* Register Square JS script. */
2521 wp_register_script(
2522 'square-payments-sdk',
2523 $script_url,
2524 array(),
2525 '1.0.0',
2526 true
2527 );
2528
2529 $dependencies = array( 'charitable-script', 'square-payments-sdk', 'jquery' );
2530
2531 wp_register_script(
2532 'charitable',
2533 charitable()->get_path( 'assets', false ) . 'js/square/charitable-square.js',
2534 $dependencies,
2535 '1.0.0',
2536 true
2537 );
2538
2539 /**
2540 * This filter allows to set a card configuration and styles.
2541 *
2542 * @since 1.8.7.
2543 * @version 1.8.7 removed 'billing_details' => $this->get_mapped_contact_fields( $forms ),
2544 *
2545 * @param array $card_config Configuration and style options.
2546 */
2547 $card_config = (array) apply_filters( 'charitable_square_frontend_enqueues_card_config', array() );
2548
2549 wp_localize_script(
2550 'charitable',
2551 'CHARITABLE_SQUARE_VARS',
2552 array(
2553 'client_id' => $connection->get_client_id(),
2554 'location_id' => Charitable_Square_Helpers::get_location_id(),
2555 'card_config' => $card_config,
2556 'enable_apple_pay' => false,
2557 'enable_google_pay' => false,
2558 'i18n' => array(
2559 'missing_sdk_script' => esc_html__( 'Square.js failed to load properly.', 'charitable' ),
2560 'general_error' => esc_html__( 'An unexpected Square SDK error has occurred.', 'charitable' ),
2561 'missing_creds' => esc_html__( 'Client ID and/or Location ID is incorrect.', 'charitable' ),
2562 'card_init_error' => esc_html__( 'Initializing Card failed.', 'charitable' ),
2563 'token_process_fail' => esc_html__( 'Tokenization of the payment card failed.', 'charitable' ),
2564 'token_status_error' => esc_html__( 'Tokenization failed with status:', 'charitable' ),
2565 'buyer_verify_error' => esc_html__( 'The verification was not successful. An issue occurred while verifying the buyer.', 'charitable' ),
2566 'empty_details' => esc_html__( 'Please fill out payment details to continue.', 'charitable' ),
2567 'wallet_not_supported' => esc_html__( 'Recurring payments can\'t be processed using Google Pay or Apple Pay. Please enter your card details.', 'charitable' ),
2568 ),
2569 )
2570 );
2571
2572 wp_enqueue_script( 'charitable' );
2573
2574 // Debug information.
2575 if ( charitable_is_debug( 'square' ) ) {
2576 // phpcs:disable
2577 error_log( 'Test / Square Mode: ' . $mode );
2578 error_log( 'Application ID: ' . $this->get_square_application_id( $mode ) );
2579 // phpcs:enable
2580 }
2581 }
2582 }
2583
2584 /**
2585 * Get the Square application ID.
2586 *
2587 * @since 1.8.7
2588 *
2589 * @param string $mode The mode to get the application ID for (test or live).
2590 * @return string
2591 */
2592 private function get_square_application_id( $mode = 'test' ) {
2593 if ( 'sandbox' === $mode ) {
2594 $mode = 'test';
2595 }
2596
2597 $option_value = charitable_get_option( array( 'gateways_square', $mode, 'application_id' ) );
2598
2599 return $option_value;
2600 }
2601
2602 /**
2603 * Get the Square location ID.
2604 * Might be deprecated.
2605 *
2606 * @since 1.8.7
2607 *
2608 * @param string $mode The mode to get the location ID for (test or live).
2609 * @return string
2610 */
2611 private function get_square_location_id( $mode = 'test' ) {
2612 if ( empty( $mode ) ) {
2613 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2614 }
2615 if ( 'sandbox' === $mode ) {
2616 $mode = 'test';
2617 }
2618
2619 $option_value = charitable_get_option( array( 'gateways_square', $mode, 'location' ) );
2620 return $option_value;
2621 }
2622
2623 /**
2624 * Retrieve the Disconnect button.
2625 *
2626 * @since 1.8.7
2627 *
2628 * @param string $mode Square mode.
2629 * @param bool $wrap Optional. Wrap a button HTML element or not.
2630 *
2631 * @return string
2632 */
2633 private function get_disconnect_button( string $mode, bool $wrap = true ): string {
2634
2635 $button = sprintf(
2636 '<a class="charitable-btn charitable-btn-md charitable-btn-light-grey" href="%1$s" title="%2$s">%3$s</a>',
2637 esc_url( $this->connect->get_disconnect_url( $mode ) ),
2638 esc_attr__( 'Disconnect Square account', 'charitable' ),
2639 esc_html__( 'Disconnect', 'charitable' )
2640 );
2641
2642 return $wrap ? '<p class="charitable-square-disconnect-btn">' . $button . '</p>' : $button;
2643 }
2644
2645 /**
2646 * Retrieve the Refresh tokens button.
2647 *
2648 * @since 1.8.7
2649 *
2650 * @param string $mode Square mode.
2651 *
2652 * @return string
2653 */
2654 private function get_refresh_button( string $mode ): string {
2655
2656 return sprintf(
2657 '<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>',
2658 esc_attr( $mode ),
2659 esc_url( $this->get_settings_page_url() ),
2660 esc_attr__( 'Refresh connection tokens', 'charitable' ),
2661 esc_html__( 'Refresh tokens', 'charitable' )
2662 );
2663 }
2664
2665 /**
2666 * Retrieve the Error icon emoji.
2667 *
2668 * @since 1.8.7
2669 *
2670 * @return string
2671 */
2672 private function get_error_icon(): string {
2673
2674 return '<span class="charitable-error-icon"></span>';
2675 }
2676
2677 /**
2678 * Get the settings page URL.
2679 *
2680 * @since 1.8.7
2681 *
2682 * @return string
2683 */
2684 public function get_settings_page_url() {
2685
2686 return add_query_arg(
2687 array(
2688 'page' => 'charitable-settings',
2689 'tab' => 'gateways',
2690 'group' => 'gateways_square_core',
2691 ),
2692 admin_url( 'admin.php' )
2693 );
2694 }
2695
2696 /**
2697 * Create instance of the class.
2698 *
2699 * @since 1.8.7
2700 *
2701 * @return Charitable_Gateway_Square
2702 */
2703 public static function get_instance() {
2704 if ( null === self::$instance ) {
2705 self::$instance = new self();
2706 }
2707 return self::$instance;
2708 }
2709
2710 /**
2711 * Check the Square token status and display admin notice if needed.
2712 *
2713 * @since 1.8.7
2714 */
2715 public function check_token_status() {
2716 global $wpdb;
2717
2718 // Only run on Square settings page.
2719 if ( ! is_admin() ) {
2720 return;
2721 }
2722
2723 // If the error notice is already set, do not run this again.
2724 $option = get_option( 'charitable_square_connection_error_notice' );
2725 if ( ! empty( $option ) ) {
2726 // If the error notice is already set, do not run this again.
2727 if ( charitable_is_debug( 'square' ) ) {
2728 // phpcs:disable
2729 error_log( 'Square token status check already performed. Skipping.' );
2730 // phpcs:enable
2731 }
2732 return;
2733 }
2734
2735 $mode = charitable_get_option( 'test_mode' ) ? 'test' : 'live';
2736 $connection = Charitable_Square_Connection::get( $mode );
2737 $api = new Charitable_Square_API( $connection );
2738
2739 if ( isset( $_GET['test_delete'] ) ) { //phpcs:ignore
2740 $status['is_valid'] = '';
2741
2742 $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' );
2743 }
2744
2745 // Only check if we have a configured connection.
2746 if ( ! $connection || ! $connection->is_configured() ) {
2747 return;
2748 }
2749
2750 $status = $api->check_token_status();
2751
2752 if ( charitable_is_debug( 'square' ) ) {
2753 error_log( 'Square token status check: ' . print_r( $status, true ) ); //phpcs:ignore
2754 }
2755
2756 if ( ! $status['is_valid'] ) {
2757
2758 if ( charitable_is_debug( 'square' ) ) {
2759 error_log( 'Square token status check failed: ' . $status['message'] ); //phpcs:ignore
2760 }
2761
2762 if ( $connection ) {
2763 // Delete the transient to ensure we can recreate the error message.
2764 $connections = (array) get_option( 'charitable_square_connections', array() );
2765 unset( $connections[ $mode ] );
2766
2767 empty( $connections ) ? delete_option( 'charitable_square_connections' ) : update_option( 'charitable_square_connections', $connections );
2768
2769 $settings = get_option( 'charitable_settings' );
2770
2771 $settings['gateways_square'][ $mode ] = array();
2772
2773 $data = array(
2774 'option_value' => maybe_serialize( $settings ),
2775 );
2776
2777 $where = array(
2778 'option_name' => 'charitable_settings',
2779 );
2780
2781 $ret = $wpdb->update( //phpcs:ignore
2782 $wpdb->options,
2783 $data,
2784 $where,
2785 array( '%s' ),
2786 array( '%s' )
2787 );
2788
2789 }
2790
2791 // Are in live mode (production)?
2792 if ( ! charitable_get_option( 'test_mode' ) ) {
2793 Charitable_Square_Connect::get_instance()->unschedule_refresh();
2794 }
2795
2796 // Webhooks are now manually configured in Square dashboard - no need to reset.
2797 // Charitable_Square_Helpers::reset_webhook_configuration( true );.
2798
2799 // Not needed because this as stored in the "connection" that was wiped above.
2800 $api->set_location_id( '', $mode );
2801 $api->set_location_currency( '', $mode );
2802 $api->detete_transients( $mode );
2803
2804 // Use a regex to extract the value between the quotes after "detail".
2805 preg_match( '/"detail":"([^"]+)"/', $status['message'], $matches );
2806 $message = isset( $matches[1] ) ? $matches[1] : '';
2807 // Get the Status code.
2808 preg_match( '/Status code: (\d+)/', $status['message'], $matches );
2809 $status_code = isset( $matches[1] ) ? $matches[1] : '';
2810
2811 $this->generate_admin_notice_for_square_connection_error( $message, $status_code, 'square_connection_error' );
2812
2813 // Do not force-redirect the admin from here. This method runs on
2814 // every admin_init, so redirecting hijacked unrelated pages and
2815 // looped whenever the dismissible notice flag was cleared. The
2816 // admin notice already prompts the reconnect.
2817 }
2818 }
2819
2820 /**
2821 * Generate admin notice for Square connection error.
2822 *
2823 * @since 1.8.7
2824 *
2825 * @param string $message The error message.
2826 * @param string $status_code The status code.
2827 * @param string $notice_key The notice key.
2828 */
2829 private function generate_admin_notice_for_square_connection_error( $message = '', $status_code = '', $notice_key = '' ) {
2830 // Store the notice in the database.
2831 $admin_notice = array(
2832 'message' => $message,
2833 'status_code' => $status_code,
2834 'time_added' => time(),
2835 );
2836
2837 update_option( 'charitable_square_connection_error_notice', $admin_notice );
2838 }
2839
2840 /**
2841 * Check if this is the Square settings page.
2842 *
2843 * @since 1.8.7
2844 *
2845 * @return boolean
2846 */
2847 private function is_square_settings_page() {
2848 return charitable_is_admin_screen( 'settings' ) && isset( $_GET['group'] ) && 'gateways_square_core' === $_GET['group']; //phpcs:ignore
2849 }
2850
2851 /**
2852 * Check if we should clear the mode connection warning.
2853 *
2854 * @since 1.8.5
2855 *
2856 * @param array $values The values being saved.
2857 * @return void
2858 */
2859 private function maybe_clear_mode_connection_warning( $values ) {
2860 $warning = get_option( 'charitable_square_mode_connection_warning' );
2861
2862 if ( ! $warning ) {
2863 return;
2864 }
2865
2866 $mode = $warning['mode'];
2867
2868 // Check if there's now a valid connection for the mode that had the warning.
2869 if ( ! empty( $values['gateways_square'][ $mode ]['access_token'] ) ) {
2870 $connection = Charitable_Square_Connection::get( $mode );
2871
2872 if ( $connection && $connection->is_configured() && $connection->is_valid() ) {
2873 // Clear the warning since we now have a valid connection.
2874 delete_option( 'charitable_square_mode_connection_warning' );
2875 }
2876 }
2877 }
2878
2879 /**
2880 * Check if Square Core gateway is active.
2881 *
2882 * @since 1.8.7
2883 *
2884 * @return boolean
2885 */
2886 public static function is_square_core_active() {
2887 $active_gateways = charitable_get_helper( 'gateways' )->get_active_gateways();
2888 $is_active = isset( $active_gateways['square_core'] );
2889
2890 if ( charitable_is_debug( 'square' ) ) {
2891 // phpcs:disable
2892 error_log( '[Charitable Square Core] Checking if Square Core is active: ' . ( $is_active ? 'true' : 'false' ) );
2893 // phpcs:enable
2894 }
2895
2896 return $is_active;
2897 }
2898
2899 /**
2900 * Check if Square Legacy gateway is active.
2901 *
2902 * @since 1.8.7
2903 *
2904 * @return boolean
2905 */
2906 public static function is_square_legacy_active() {
2907 $active_gateways = charitable_get_helper( 'gateways' )->get_active_gateways();
2908 $is_active = isset( $active_gateways['square'] );
2909
2910 if ( charitable_is_debug( 'square' ) ) {
2911 // phpcs:disable
2912 error_log( '[Charitable Square Core] Checking if Square Legacy is active: ' . ( $is_active ? 'true' : 'false' ) );
2913 // phpcs:enable
2914 }
2915
2916 return $is_active;
2917 }
2918
2919 /**
2920 * Get the donation amount including fees if fee relief is enabled.
2921 *
2922 * @since 1.8.8.3
2923 *
2924 * @param Charitable_Donation $donation The donation object.
2925 * @return float
2926 */
2927 private function get_donation_amount_with_fees( $donation ) {
2928 // Check if fee relief is enabled and donor opted to cover fees
2929 if ( $donation->get( 'cover_fees' ) ) {
2930 // Use the total donation amount including fees
2931 return \Charitable_Currency::get_instance()->cast_to_decimal_format( $donation->get( 'total_donation_with_fees' ) );
2932 }
2933
2934 // Return the standard donation amount
2935 return $donation->get_total_donation_amount( true );
2936 }
2937 }
2938 endif;
2939