PluginProbe ʕ •ᴥ•ʔ
WooCommerce Square / 5.4.3
WooCommerce Square v5.4.3
5.4.3 5.4.2 5.4.1 5.4.0 trunk 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.7.0 2.8.0 2.9.0 2.9.1 3.0.0 3.0.1 3.0.2 3.0.3 3.1.0 3.2.0 3.3.0 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.7.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 4.0.0 4.1.0 4.2.0 4.2.1 4.2.2 4.2.3 4.3.0 4.3.1 4.3.2 4.4.0 4.4.1 4.4.2 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.6.2 4.6.3 4.6.4 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.8.4 4.8.5 4.8.6 4.8.7 4.8.8 4.9.0 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 4.9.6 4.9.7 4.9.8 4.9.9 5.0.0 5.0.1 5.1.0 5.1.1 5.1.2 5.2.0 5.3.0 5.3.1 5.3.2 5.3.3
woocommerce-square / includes / Utilities / Token_Scope_Utility.php
woocommerce-square / includes / Utilities Last commit date
Array_Utility.php 3 years ago Coupon_Utility.php 5 months ago Encryption_Utility.php 2 years ago Helper.php 4 months ago Money_Utility.php 3 years ago Order_Ajax_Authorization.php 2 months ago Performance_Logger.php 1 year ago String_Utility.php 9 months ago Token_Scope_Utility.php 5 months ago
Token_Scope_Utility.php
157 lines
1 <?php
2 /**
3 * WooCommerce Square
4 *
5 * This source file is subject to the GNU General Public License v3.0
6 * that is bundled with this package in the file license.txt.
7 *
8 * @author WooCommerce
9 * @copyright Copyright: (c) 2019, Automattic, Inc.
10 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
11 */
12
13 namespace WooCommerce\Square\Utilities;
14
15 defined( 'ABSPATH' ) || exit;
16
17 /**
18 * Utility for Square OAuth token introspection (retrieve token status).
19 * Used to check merchant scopes before calling scope-gated APIs (e.g. discount codes).
20 *
21 * @see https://developer.squareup.com/reference/square/o-auth-api/retrieve-token-status
22 * @since 5.3.0
23 */
24 class Token_Scope_Utility {
25
26 /**
27 * OAuth scopes required for Square discount code APIs. Both must be present on the token.
28 *
29 * @var string[]
30 */
31 const DISCOUNT_CODES_SCOPES_REQUIRED = array( 'DISCOUNT_CODES_READ', 'DISCOUNT_CODES_WRITE' );
32
33 /**
34 * Transient key prefix for cached scopes. Suffix is environment (production or sandbox).
35 *
36 * @var string
37 */
38 const TRANSIENT_PREFIX = 'wc_square_token_scopes_';
39
40 /**
41 * Cache duration for token scopes in seconds (24 hours).
42 *
43 * @var int
44 */
45 const CACHE_DURATION = HOUR_IN_SECONDS * 24;
46
47 /**
48 * Retrieve token status from Square OAuth API and return the list of scopes.
49 * Results are cached per environment to avoid repeated API calls.
50 *
51 * @since 5.3.0
52 *
53 * @param string|null $access_token Access token (optional; uses plugin settings if null).
54 * @param bool|null $is_sandbox Whether sandbox (optional; uses plugin settings if null).
55 * @return array|WP_Error List of scope strings, or WP_Error on failure.
56 */
57 public static function get_token_scopes( $access_token = null, $is_sandbox = null ) {
58 if ( null === $access_token || null === $is_sandbox ) {
59 if ( ! function_exists( 'wc_square' ) ) {
60 return new \WP_Error( 'no_plugin', __( 'Square plugin not available.', 'woocommerce-square' ) );
61 }
62 $settings = wc_square()->get_settings_handler();
63 $access_token = $access_token ?? $settings->get_access_token();
64 $is_sandbox = $is_sandbox ?? $settings->is_sandbox();
65 }
66
67 if ( empty( $access_token ) ) {
68 return new \WP_Error( 'no_token', __( 'No access token available.', 'woocommerce-square' ) );
69 }
70
71 $env = $is_sandbox ? 'sandbox' : 'production';
72 $transient = self::TRANSIENT_PREFIX . $env;
73 $cached = get_transient( $transient );
74
75 if ( is_array( $cached ) && isset( $cached['scopes'] ) ) {
76 return $cached['scopes'];
77 }
78
79 // Direct HTTP call: the Square PHP SDK does not expose the OAuth2 token status endpoint (RetrieveTokenStatus).
80 $base = $is_sandbox ? 'https://connect.squareupsandbox.com' : 'https://connect.squareup.com';
81 $url = $base . '/oauth2/token/status';
82
83 $response = wp_remote_post(
84 $url,
85 array(
86 'headers' => array(
87 'Authorization' => 'Bearer ' . $access_token,
88 'Content-Type' => 'application/json',
89 'Square-Version' => '2026-01-22',
90 ),
91 'timeout' => 15,
92 )
93 );
94
95 if ( is_wp_error( $response ) ) {
96 return $response;
97 }
98
99 $code = wp_remote_retrieve_response_code( $response );
100 $body = wp_remote_retrieve_body( $response );
101 $data = json_decode( $body, true );
102
103 if ( 200 !== $code ) {
104 $message = isset( $data['errors'][0]['detail'] ) ? $data['errors'][0]['detail'] : wp_remote_retrieve_response_message( $response );
105 return new \WP_Error( 'token_status_error', $message, array( 'status' => $code ) );
106 }
107
108 $scopes = isset( $data['scopes'] ) && is_array( $data['scopes'] ) ? $data['scopes'] : array();
109 set_transient( $transient, array( 'scopes' => $scopes ), self::CACHE_DURATION );
110
111 return $scopes;
112 }
113
114 /**
115 * Whether the current merchant's token has both scopes required for discount code APIs.
116 * Both DISCOUNT_CODES_READ and DISCOUNT_CODES_WRITE must be present.
117 *
118 * @since 5.3.0
119 *
120 * @param string|null $access_token Access token (optional).
121 * @param bool|null $is_sandbox Whether sandbox (optional).
122 * @return bool True if token has both DISCOUNT_CODES_READ and DISCOUNT_CODES_WRITE, false otherwise.
123 */
124 public static function merchant_has_discount_codes_scope( $access_token = null, $is_sandbox = null ) {
125 $scopes = self::get_token_scopes( $access_token, $is_sandbox );
126
127 if ( is_wp_error( $scopes ) || ! is_array( $scopes ) ) {
128 return false;
129 }
130
131 $scopes_list = array_values( $scopes );
132 foreach ( self::DISCOUNT_CODES_SCOPES_REQUIRED as $required ) {
133 if ( ! in_array( $required, $scopes_list, true ) ) {
134 return false;
135 }
136 }
137
138 return true;
139 }
140
141 /**
142 * Clear cached token scopes for an environment (e.g. after token update or disconnect).
143 *
144 * @since 5.3.0
145 *
146 * @param string|null $environment 'production', 'sandbox', or null to clear both.
147 */
148 public static function clear_scope_cache( $environment = null ) {
149 if ( null === $environment ) {
150 delete_transient( self::TRANSIENT_PREFIX . 'production' );
151 delete_transient( self::TRANSIENT_PREFIX . 'sandbox' );
152 return;
153 }
154 delete_transient( self::TRANSIENT_PREFIX . $environment );
155 }
156 }
157