WooCommerce Square - Version 2.0.5

Version Description

  • 2019.10.16 =
  • Fix - Access token renewal by adding support for refresh tokens as per the new Square API
  • Fix - Variable pricing import and adding an alert when these type of products are ignored.
  • Fix - Line item discounts and other adjustments being ignored.
  • Tweak - Add a notice when a refresh token is not present to warn users to re-connect their accounts.
  • Feature - Added support for Sandbox accounts.
Download this release

Release Info

Developer automattic
Plugin Icon 128x128 WooCommerce Square
Version 2.0.5
Comparing to
See all releases

Code changes from version 2.0.4 to 2.0.5

includes/API.php CHANGED
@@ -55,10 +55,18 @@ class API extends Framework\SV_WC_API_Base {
55
  * @since 2.0.0
56
  *
57
  * @param string $access_token Square API access token
 
58
  */
59
- public function __construct( $access_token ) {
 
 
60
 
61
- $this->client = new SquareConnect\ApiClient( SquareConnect\Configuration::getDefaultConfiguration()->setAccessToken( $access_token ) );
 
 
 
 
 
62
  }
63
 
64
 
@@ -785,30 +793,44 @@ class API extends Framework\SV_WC_API_Base {
785
  */
786
  protected function do_post_parse_response_validation() {
787
 
788
- if ( $this->get_response()->has_errors() ) {
 
 
789
 
790
- $errors = [];
791
 
792
- foreach ( $this->get_response()->get_errors() as $error ) {
 
 
 
793
 
794
- $errors[] = trim( "[{$error->code}] {$error->detail}" );
795
 
796
- // if the error indicates that access token is bad, disconnect the plugin to prevent further attempts
797
- if ( ! empty( $error->code ) && in_array( $error->code, [ 'UNAUTHORIZED', 'ACCESS_TOKEN_EXPIRED', 'ACCESS_TOKEN_REVOKED' ], true ) ) {
 
 
798
 
799
- $this->get_plugin()->get_connection_handler()->disconnect();
800
 
801
- $this->get_plugin()->log( 'Disconnected due to invalid authorization' );
 
 
 
802
  }
803
  }
804
 
805
- throw new Framework\SV_WC_API_Exception( implode( ' | ', $errors ) );
 
 
 
 
806
  }
807
 
808
- return true;
 
809
  }
810
 
811
-
812
  /**
813
  * Performs a remote request with the Square API class.
814
  *
55
  * @since 2.0.0
56
  *
57
  * @param string $access_token Square API access token
58
+ * @param bool $is_sandbox If sandbox access is desired
59
  */
60
+ public function __construct( $access_token, $is_sandbox = null ) {
61
+ $api_config = SquareConnect\Configuration::getDefaultConfiguration();
62
+ $api_config->setAccessToken( $access_token );
63
 
64
+ // Set sandbox URL if enabled.
65
+ if ( $is_sandbox ) {
66
+ $api_config->setHost( 'https://connect.squareupsandbox.com' );
67
+ }
68
+
69
+ $this->client = new SquareConnect\ApiClient( $api_config );
70
  }
71
 
72
 
793
  */
794
  protected function do_post_parse_response_validation() {
795
 
796
+ if ( ! $this->get_response()->has_errors() ) {
797
+ return true;
798
+ }
799
 
800
+ $errors = [];
801
 
802
+ foreach ( $this->get_response()->get_errors() as $error ) {
803
+ if ( empty( $error->code ) ) {
804
+ continue;
805
+ }
806
 
807
+ $errors[] = trim( "[{$error->code}] {$error->detail}" );
808
 
809
+ // Last attempt to refresh access token.
810
+ if ( 'ACCESS_TOKEN_EXPIRED' == $error->code ) {
811
+ $this->get_plugin()->log( 'Access Token Expired, attempting a refresh.' );
812
+ $this->get_plugin()->get_connection_handler()->refresh_connection();
813
 
814
+ $failure_value = get_option( 'wc_' . $this->get_plugin()->get_id() . '_refresh_failed', 'yes' );
815
 
816
+ if ( empty( $failure_value ) ) {
817
+ // Successfully refreshed on the last attempt
818
+ $this->get_plugin()->log( 'Connection successfully refreshed.' );
819
+ return true;
820
  }
821
  }
822
 
823
+ // if the error indicates that access token is bad, disconnect the plugin to prevent further attempts
824
+ if ( in_array( $error->code, [ 'UNAUTHORIZED', 'ACCESS_TOKEN_EXPIRED', 'ACCESS_TOKEN_REVOKED' ], true ) ) {
825
+ $this->get_plugin()->get_connection_handler()->disconnect();
826
+ $this->get_plugin()->log( 'Disconnected due to invalid authorization. Please try connecting again.' );
827
+ }
828
  }
829
 
830
+ // At this point we could not validate the response and assume a failed attempt.
831
+ throw new Framework\SV_WC_API_Exception( implode( ' | ', $errors ) );
832
  }
833
 
 
834
  /**
835
  * Performs a remote request with the Square API class.
836
  *
includes/API/Responses/Connection_Refresh_Response.php CHANGED
@@ -49,6 +49,17 @@ class Connection_Refresh_Response extends Framework\SV_WC_API_JSON_Response {
49
  return $this->access_token;
50
  }
51
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  /**
54
  * Gets the error message, if any.
49
  return $this->access_token;
50
  }
51
 
52
+ /**
53
+ * Gets the refresh token, if any.
54
+ *
55
+ * @since 2.0.0
56
+ *
57
+ * @return string|null
58
+ */
59
+ public function get_refresh_token() {
60
+ return $this->refresh_token;
61
+ }
62
+
63
 
64
  /**
65
  * Gets the error message, if any.
includes/Gateway.php CHANGED
@@ -151,8 +151,13 @@ class Gateway extends Framework\SV_WC_Payment_Gateway_Direct {
151
  * @since 2.0.0
152
  */
153
  protected function enqueue_gateway_assets() {
 
 
 
 
 
154
 
155
- wp_enqueue_script( 'wc-' . $this->get_plugin()->get_id_dasherized() . '-payment-form', 'https://js.squareup.com/v2/paymentform', [], Plugin::VERSION );
156
 
157
  parent::enqueue_gateway_assets();
158
  }
@@ -757,6 +762,12 @@ class Gateway extends Framework\SV_WC_Payment_Gateway_Direct {
757
  */
758
  public function get_application_id() {
759
 
 
 
 
 
 
 
760
  /**
761
  * Filters the configured application ID.
762
  *
@@ -764,7 +775,7 @@ class Gateway extends Framework\SV_WC_Payment_Gateway_Direct {
764
  *
765
  * @param string $application_id application ID
766
  */
767
- return apply_filters( 'wc_square_application_id', 'sq0idp-wGVapF8sNt9PLrdj5znuKA' );
768
  }
769
 
770
 
151
  * @since 2.0.0
152
  */
153
  protected function enqueue_gateway_assets() {
154
+ if ( $this->get_plugin()->get_settings_handler()->is_sandbox() ) {
155
+ $url = 'https://js.squareupsandbox.com/v2/paymentform';
156
+ } else {
157
+ $url = 'https://js.squareup.com/v2/paymentform';
158
+ }
159
 
160
+ wp_enqueue_script( 'wc-' . $this->get_plugin()->get_id_dasherized() . '-payment-form', $url, [], Plugin::VERSION );
161
 
162
  parent::enqueue_gateway_assets();
163
  }
762
  */
763
  public function get_application_id() {
764
 
765
+ $square_application_id = 'sq0idp-wGVapF8sNt9PLrdj5znuKA';
766
+
767
+ if ( $this->get_plugin()->get_settings_handler()->is_sandbox() ) {
768
+ $square_application_id = $this->get_plugin()->get_settings_handler()->get_option( 'sandbox_application_id' );
769
+ }
770
+
771
  /**
772
  * Filters the configured application ID.
773
  *
775
  *
776
  * @param string $application_id application ID
777
  */
778
+ return apply_filters( 'wc_square_application_id', $square_application_id );
779
  }
780
 
781
 
includes/Gateway/API/Requests/Orders.php CHANGED
@@ -117,17 +117,14 @@ class Orders extends API\Request {
117
  $line_item = new SquareModel\OrderLineItem();
118
 
119
  $line_item->setQuantity( (string) $item->get_quantity() );
 
120
 
121
  $square_id = $item->get_meta( Product::SQUARE_VARIATION_ID_META_KEY );
122
 
123
  if ( $square_id ) {
124
-
125
  $line_item->setCatalogObjectId( $square_id );
126
-
127
  } else {
128
-
129
  $line_item->setName( $item->get_name() );
130
- $line_item->setBasePriceMoney( Money_Utility::amount_to_money( $order->get_item_subtotal( $item ), $order->get_currency() ) );
131
  }
132
 
133
  $line_items[] = $line_item;
117
  $line_item = new SquareModel\OrderLineItem();
118
 
119
  $line_item->setQuantity( (string) $item->get_quantity() );
120
+ $line_item->setBasePriceMoney( Money_Utility::amount_to_money( $order->get_item_subtotal( $item ), $order->get_currency() ) );
121
 
122
  $square_id = $item->get_meta( Product::SQUARE_VARIATION_ID_META_KEY );
123
 
124
  if ( $square_id ) {
 
125
  $line_item->setCatalogObjectId( $square_id );
 
126
  } else {
 
127
  $line_item->setName( $item->get_name() );
 
128
  }
129
 
130
  $line_items[] = $line_item;
includes/Handlers/Connection.php CHANGED
@@ -113,28 +113,46 @@ class Connection {
113
  wp_die( __( 'Sorry, you do not have permission to manage the Square connection.', 'woocommerce-square' ) );
114
  }
115
 
116
- $token = ! empty( $_GET['square_access_token'] ) ? sanitize_text_field( urldecode( $_GET['square_access_token'] ) ) : '';
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- if ( $token ) {
 
119
 
120
- $this->get_plugin()->get_settings_handler()->update_access_token( $token );
 
 
 
 
 
 
121
 
122
- $this->schedule_customer_index();
123
 
124
- // on connect after upgrading to v2.0 from v1.0, initiate a catalog sync to refresh the Square item IDs
125
- if ( get_option( 'wc_square_updated_to_2_0_0' ) ) {
126
 
127
- // delete any old access token from v1, as it will be invalidated
128
- delete_option( 'woocommerce_square_merchant_access_token' );
129
 
130
- if ( $this->get_plugin()->get_settings_handler()->is_system_of_record_square() ) {
131
- $this->get_plugin()->get_sync_handler()->start_manual_sync();
132
- }
133
  }
134
-
135
- delete_option( 'wc_square_updated_to_2_0_0' );
136
  }
137
 
 
 
138
  wp_safe_redirect( $this->get_plugin()->get_settings_url() );
139
  exit;
140
  }
@@ -208,6 +226,7 @@ class Connection {
208
 
209
  // fully clear the access token
210
  $this->get_plugin()->get_settings_handler()->clear_access_tokens();
 
211
 
212
  // clear all background jobs so further API requests aren't attempted
213
  $this->get_plugin()->get_background_job_handler()->clear_all_jobs();
@@ -256,9 +275,17 @@ class Connection {
256
  $this->get_plugin()->log( 'Refreshing connection...' );
257
  }
258
 
 
 
 
 
 
 
 
 
259
  $request = [
260
  'body' => [
261
- 'token' => $this->get_plugin()->get_settings_handler()->get_access_token(),
262
  ],
263
  'timeout' => 45,
264
  ];
@@ -286,6 +313,14 @@ class Connection {
286
  // store the new token
287
  $this->get_plugin()->get_settings_handler()->update_access_token( $response->get_token() );
288
 
 
 
 
 
 
 
 
 
289
  } catch ( Framework\SV_WC_Plugin_Exception $exception ) {
290
 
291
  $this->get_plugin()->log( 'Unable to refresh connection: ' . $exception->getMessage() );
113
  wp_die( __( 'Sorry, you do not have permission to manage the Square connection.', 'woocommerce-square' ) );
114
  }
115
 
116
+ $access_token = ! empty( $_GET['square_access_token'] ) ? sanitize_text_field( urldecode( $_GET['square_access_token'] ) ) : '';
117
+
118
+ if ( empty( $access_token ) ) {
119
+ $this->get_plugin()->log( 'Error: No access token was received.' );
120
+ add_action( 'admin_notices', function () {
121
+ ?>
122
+ <div class="notice notice-error is-dismissible">
123
+ <p><?php _e( 'Square Error: We could not connect to Square. No access token was given.!', 'woocommerce-square' ); ?></p>
124
+ </div>
125
+ <?php
126
+ });
127
+ return;
128
+ }
129
 
130
+ $this->get_plugin()->get_settings_handler()->update_access_token( $access_token );
131
+ $this->get_plugin()->log( 'Access token successfully received.' );
132
 
133
+ $refresh_token = ! empty( $_GET['square_refresh_token'] ) ? sanitize_text_field( urldecode( $_GET['square_refresh_token'] ) ) : '';
134
+ if ( empty( $refresh_token ) ) {
135
+ $this->get_plugin()->log( 'Failed to receive refresh token from connect server.' );
136
+ } else {
137
+ $this->get_plugin()->get_settings_handler()->update_refresh_token( $refresh_token );
138
+ $this->get_plugin()->log( 'Refresh token successfully received.' );
139
+ }
140
 
141
+ $this->schedule_customer_index();
142
 
143
+ // on connect after upgrading to v2.0 from v1.0, initiate a catalog sync to refresh the Square item IDs
144
+ if ( get_option( 'wc_square_updated_to_2_0_0' ) ) {
145
 
146
+ // delete any old access token from v1, as it will be invalidated
147
+ delete_option( 'woocommerce_square_merchant_access_token' );
148
 
149
+ if ( $this->get_plugin()->get_settings_handler()->is_system_of_record_square() ) {
150
+ $this->get_plugin()->get_sync_handler()->start_manual_sync();
 
151
  }
 
 
152
  }
153
 
154
+ delete_option( 'wc_square_updated_to_2_0_0' );
155
+
156
  wp_safe_redirect( $this->get_plugin()->get_settings_url() );
157
  exit;
158
  }
226
 
227
  // fully clear the access token
228
  $this->get_plugin()->get_settings_handler()->clear_access_tokens();
229
+ $this->get_plugin()->get_settings_handler()->clear_refresh_tokens();
230
 
231
  // clear all background jobs so further API requests aren't attempted
232
  $this->get_plugin()->get_background_job_handler()->clear_all_jobs();
275
  $this->get_plugin()->log( 'Refreshing connection...' );
276
  }
277
 
278
+ $refresh_token = $this->get_plugin()->get_settings_handler()->get_refresh_token();
279
+
280
+ if ( ! $refresh_token ) {
281
+ $this->get_plugin()->log( 'No refresh token stored, cannot refresh connection.' );
282
+ update_option( 'wc_' . $this->get_plugin()->get_id() . '_refresh_failed', 'yes' );
283
+ return;
284
+ }
285
+
286
  $request = [
287
  'body' => [
288
+ 'token' => $this->get_plugin()->get_settings_handler()->get_refresh_token(),
289
  ],
290
  'timeout' => 45,
291
  ];
313
  // store the new token
314
  $this->get_plugin()->get_settings_handler()->update_access_token( $response->get_token() );
315
 
316
+ // In case square updates the refresh token.
317
+ if( $response->get_refresh_token() ) {
318
+ $this->get_plugin()->get_settings_handler()->update_refresh_token( $response->get_refresh_token() );
319
+ $this->get_plugin()->log( 'Connection successfully refreshed.' );
320
+ }
321
+
322
+ // in case this option was set
323
+ delete_option( 'wc_' . $this->get_plugin()->get_id() . '_refresh_failed' );
324
  } catch ( Framework\SV_WC_Plugin_Exception $exception ) {
325
 
326
  $this->get_plugin()->log( 'Unable to refresh connection: ' . $exception->getMessage() );
includes/Handlers/Product.php CHANGED
@@ -121,7 +121,10 @@ class Product {
121
  $variation->update_meta_data( self::SQUARE_VARIATION_ID_META_KEY, $catalog_variation->getId() );
122
 
123
  $variation->set_name( $catalog_variation->getItemVariationData()->getName() );
124
- $variation->set_regular_price( Money_Utility::cents_to_float( $catalog_variation->getItemVariationData()->getPriceMoney()->getAmount() ) );
 
 
 
125
 
126
  if ( $with_inventory && wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
127
  self::update_stock_from_square( $variation, false );
@@ -153,7 +156,9 @@ class Product {
153
 
154
  $product->update_meta_data( self::SQUARE_VARIATION_ID_META_KEY, $catalog_variation->getId() );
155
 
156
- $product->set_regular_price( Money_Utility::cents_to_float( $catalog_variation->getItemVariationData()->getPriceMoney()->getAmount() ) );
 
 
157
 
158
  if ( $with_inventory && wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
159
  self::update_stock_from_square( $product, false );
121
  $variation->update_meta_data( self::SQUARE_VARIATION_ID_META_KEY, $catalog_variation->getId() );
122
 
123
  $variation->set_name( $catalog_variation->getItemVariationData()->getName() );
124
+
125
+ if ( $catalog_variation->getItemVariationData()->getPriceMoney() ) {
126
+ $variation->set_regular_price( Money_Utility::cents_to_float( $catalog_variation->getItemVariationData()->getPriceMoney()->getAmount() ) );
127
+ }
128
 
129
  if ( $with_inventory && wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
130
  self::update_stock_from_square( $variation, false );
156
 
157
  $product->update_meta_data( self::SQUARE_VARIATION_ID_META_KEY, $catalog_variation->getId() );
158
 
159
+ if ( $catalog_variation->getItemVariationData()->getPriceMoney() ) {
160
+ $product->set_regular_price( Money_Utility::cents_to_float( $catalog_variation->getItemVariationData()->getPriceMoney()->getAmount() ) );
161
+ }
162
 
163
  if ( $with_inventory && wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
164
  self::update_stock_from_square( $product, false );
includes/Plugin.php CHANGED
@@ -41,7 +41,7 @@ class Plugin extends Framework\SV_WC_Payment_Gateway_Plugin {
41
 
42
 
43
  /** plugin version number */
44
- const VERSION = '2.0.4';
45
 
46
  /** plugin ID */
47
  const PLUGIN_ID = 'square';
@@ -367,6 +367,9 @@ class Plugin extends Framework\SV_WC_Payment_Gateway_Plugin {
367
  // add a notice when background processing is not supported
368
  $this->add_background_processing_notice();
369
 
 
 
 
370
  // add a tax-inclusive warning to product pages
371
  $this->add_tax_inclusive_pricing_notice();
372
 
@@ -440,6 +443,44 @@ class Plugin extends Framework\SV_WC_Payment_Gateway_Plugin {
440
  }
441
  }
442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
 
444
  /**
445
  * Adds a tax-inclusive admin warning to product pages.
@@ -553,13 +594,17 @@ class Plugin extends Framework\SV_WC_Payment_Gateway_Plugin {
553
  * @param string|null $access_token API access token
554
  * @return API
555
  */
556
- public function get_api( $access_token = null ) {
557
 
558
  if ( ! $access_token ) {
559
  $access_token = $this->get_settings_handler()->get_access_token();
560
  }
561
 
562
- return new API( $access_token );
 
 
 
 
563
  }
564
 
565
 
41
 
42
 
43
  /** plugin version number */
44
+ const VERSION = '2.0.5';
45
 
46
  /** plugin ID */
47
  const PLUGIN_ID = 'square';
367
  // add a notice when background processing is not supported
368
  $this->add_background_processing_notice();
369
 
370
+ // add a notice when no refresh token is available
371
+ $this->add_missing_refresh_token_notice();
372
+
373
  // add a tax-inclusive warning to product pages
374
  $this->add_tax_inclusive_pricing_notice();
375
 
443
  }
444
  }
445
 
446
+ /**
447
+ * Adds a notice if no refresh token has been cached.
448
+ *
449
+ * @since 2.0.5
450
+ */
451
+ protected function add_missing_refresh_token_notice() {
452
+ $refresh_token = '';
453
+ $settings_handler = $this->get_settings_handler();
454
+
455
+ if ( method_exists( $settings_handler, 'get_access_token' ) ) {
456
+ $access_token = $settings_handler->get_access_token();
457
+ if ( empty( $access_token ) ) {
458
+ // We are already in a disconnected state, don't show the warning.
459
+ return;
460
+ }
461
+ }
462
+
463
+ if ( method_exists( $settings_handler, 'get_refresh_token' ) ) {
464
+ $refresh_token = $settings_handler->get_refresh_token();
465
+ }
466
+
467
+ if ( empty( $refresh_token ) ) {
468
+ $this->get_admin_notice_handler()->add_admin_notice(
469
+ sprintf(
470
+ /* translators: Placeholders: %1$s - <strong> tag, %2$s - </strong> tag, %3$s - <a> tag, %4$s - </a> tag */
471
+ __( '%1$sWooCommerce Square:%2$s Automatic refreshing of the connection to Square is inactive. Please disconnect and reconnect to resolve.', 'woocommerce-square' ),
472
+ '<strong>',
473
+ '</strong>'
474
+ ),
475
+ 'wc-square-missing-refresh-token',
476
+ [
477
+ 'dismissible' => false,
478
+ 'notice_class' => 'notice-error',
479
+ ]
480
+ );
481
+ }
482
+ }
483
+
484
 
485
  /**
486
  * Adds a tax-inclusive admin warning to product pages.
594
  * @param string|null $access_token API access token
595
  * @return API
596
  */
597
+ public function get_api( $access_token = null, $is_sandbox = null ) {
598
 
599
  if ( ! $access_token ) {
600
  $access_token = $this->get_settings_handler()->get_access_token();
601
  }
602
 
603
+ if ( is_null( $is_sandbox ) ) {
604
+ $is_sandbox = $this->get_settings_handler()->is_sandbox();
605
+ }
606
+
607
+ return new API( $access_token, $is_sandbox );
608
  }
609
 
610
 
includes/Settings.php CHANGED
@@ -49,6 +49,9 @@ class Settings extends \WC_Settings_API {
49
  const SYSTEM_OF_RECORD_DISABLED = 'disabled';
50
 
51
 
 
 
 
52
  /** @var string un-encrypted access token */
53
  protected $access_token;
54
 
@@ -83,6 +86,20 @@ class Settings extends \WC_Settings_API {
83
 
84
  return $fields;
85
  } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
87
 
88
 
@@ -113,6 +130,28 @@ class Settings extends \WC_Settings_API {
113
  ],
114
  ];
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  // display these fields only if connected
117
  if ( $this->is_connected() ) {
118
 
@@ -168,24 +207,35 @@ class Settings extends \WC_Settings_API {
168
  ];
169
  }
170
 
171
- // always display these fields
172
- $fields = array_merge( $fields, [
173
- 'connect' => [
174
- 'title' => __( 'Connection', 'woocommerce-square' ),
175
- 'type' => 'connect',
176
- 'desc_tip' => '',
177
- ],
178
- 'debug_logging_enabled' => [
179
- 'title' => __( 'Enable Logging', 'woocommerce-square' ),
180
- 'type' => 'checkbox',
181
- 'label' => sprintf(
182
- /* translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag */
183
- __( 'Log debug messages to the %1$sWooCommerce status log%2$s', 'woocommerce-square' ),
184
- '<a href="' . esc_url( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) . '">', '</a>'
185
- ),
186
- ],
187
 
188
- ] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
  $this->form_fields = $fields;
191
  }
@@ -278,6 +328,42 @@ class Settings extends \WC_Settings_API {
278
  }
279
 
280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  /**
282
  * Updates the stored access token.
283
  *
@@ -314,6 +400,16 @@ class Settings extends \WC_Settings_API {
314
  }
315
 
316
 
 
 
 
 
 
 
 
 
 
 
317
  /**
318
  * Clears any stored access tokens.
319
  *
@@ -509,7 +605,7 @@ class Settings extends \WC_Settings_API {
509
  try {
510
 
511
  // cache the locations returned so they can be used elsewhere
512
- $this->locations = $this->get_plugin()->get_api( $this->get_access_token() )->get_locations();
513
 
514
  // check the returned IDs against what's currently configured
515
  $stored_location_id = $this->get_location_id();
@@ -576,6 +672,51 @@ class Settings extends \WC_Settings_API {
576
  return $sor;
577
  }
578
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
 
580
  /**
581
  * Gets the access token.
@@ -634,11 +775,24 @@ class Settings extends \WC_Settings_API {
634
  * @return array
635
  */
636
  public function get_access_tokens() {
637
-
638
  return (array) get_option( 'wc_square_access_tokens', [] );
639
  }
640
 
641
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
  /**
643
  * Gets the configured environment.
644
  *
@@ -647,8 +801,7 @@ class Settings extends \WC_Settings_API {
647
  * @return string
648
  */
649
  public function get_environment() {
650
-
651
- return 'production';
652
  }
653
 
654
 
49
  const SYSTEM_OF_RECORD_DISABLED = 'disabled';
50
 
51
 
52
+ /** @var string un-encrypted refresh token */
53
+ protected $refresh_token;
54
+
55
  /** @var string un-encrypted access token */
56
  protected $access_token;
57
 
86
 
87
  return $fields;
88
  } );
89
+
90
+ // Save sandbox token.
91
+ if ( $this->is_sandbox() ) {
92
+ add_action(
93
+ 'woocommerce_settings_api_sanitized_fields_' . $this->id,
94
+ function( $fields ) {
95
+ $this->update_access_token( $fields['sandbox_token'] );
96
+ $this->access_token = false; // Remove encrypted token.
97
+ $this->refresh_token = false; // Remove encrypted token.
98
+ $this->init_form_fields(); // Reload form fields after saving token.
99
+ return $fields;
100
+ }
101
+ );
102
+ }
103
  }
104
 
105
 
130
  ],
131
  ];
132
 
133
+ if ( $this->is_sandbox() ) {
134
+ $fields['sandbox_settings'] = [
135
+ 'type' => 'title',
136
+ 'title' => __( 'Sandbox settings', 'woocommerce-square' ),
137
+ 'description' => sprintf(
138
+ // translators: Placeholders: %1$s - URL
139
+ __( 'Sandbox details can be created at: %s', 'woocommerce-square' ),
140
+ sprintf( '<a href="%1$s">%1$s</a>', 'https://developer.squareup.com/apps' )
141
+ ),
142
+ ];
143
+ $fields['sandbox_application_id'] = [
144
+ 'type' => 'input',
145
+ 'title' => __( 'Sandbox Application ID', 'woocommerce-square' ),
146
+ 'description' => __( 'Application ID for the Sandbox Application, see the details in the My Applications section.', 'woocommerce-square' ),
147
+ ];
148
+ $fields['sandbox_token'] = [
149
+ 'type' => 'input',
150
+ 'title' => __( 'Sandbox Access Token', 'woocommerce-square' ),
151
+ 'description' => __( 'Access Token for the Sandbox Test Account, see the details in the Sandbox Test Account section. Make sure you use the correct Sandbox Access Token for your application. For a given Sandbox Test Account, each Authorized Application is assigned a different Access Token.', 'woocommerce-square' ),
152
+ ];
153
+ }
154
+
155
  // display these fields only if connected
156
  if ( $this->is_connected() ) {
157
 
207
  ];
208
  }
209
 
210
+ // In sandbox mode we don't want to intially display the connect button, only disconnect.
211
+ if ( ! ( $this->is_sandbox() && ! $this->is_connected() ) ) {
212
+ $fields = array_merge(
213
+ $fields,
214
+ [
215
+ 'connect' => [
216
+ 'title' => __( 'Connection', 'woocommerce-square' ),
217
+ 'type' => 'connect',
218
+ 'desc_tip' => '',
219
+ ],
220
+ ]
221
+ );
222
+ }
 
 
 
223
 
224
+ // Always display these fields.
225
+ $fields = array_merge(
226
+ $fields,
227
+ [
228
+ 'debug_logging_enabled' => [
229
+ 'title' => __( 'Enable Logging', 'woocommerce-square' ),
230
+ 'type' => 'checkbox',
231
+ 'label' => sprintf(
232
+ /* translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag */
233
+ __( 'Log debug messages to the %1$sWooCommerce status log%2$s', 'woocommerce-square' ),
234
+ '<a href="' . esc_url( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) . '">', '</a>'
235
+ ),
236
+ ],
237
+ ]
238
+ );
239
 
240
  $this->form_fields = $fields;
241
  }
328
  }
329
 
330
 
331
+ /**
332
+ * Updates the stored refresh token.
333
+ *
334
+ * @since 2.0.0
335
+ *
336
+ * @param string $token refresh token
337
+ */
338
+ public function update_refresh_token( $token ) {
339
+
340
+ $refresh_tokens = $this->get_refresh_tokens();
341
+ $environment = $this->get_environment();
342
+
343
+ if ( ! empty( $token ) ) {
344
+
345
+ if ( Utilities\Encryption_Utility::is_encryption_supported() ) {
346
+
347
+ $encryption = new Utilities\Encryption_Utility();
348
+
349
+ try {
350
+
351
+ $token = $encryption->encrypt_data( $token );
352
+
353
+ } catch ( Framework\SV_WC_Plugin_Exception $exception ) {
354
+
355
+ // log the event, but don't halt the process
356
+ $this->get_plugin()->log( 'Could not encrypt refresh token. ' . $exception->getMessage() );
357
+ }
358
+ }
359
+
360
+ $refresh_tokens[ $environment ] = $this->refresh_token = $token;
361
+ }
362
+
363
+ update_option( 'wc_square_refresh_tokens', $refresh_tokens );
364
+ }
365
+
366
+
367
  /**
368
  * Updates the stored access token.
369
  *
400
  }
401
 
402
 
403
+ /**
404
+ * Clears any stored refresh tokens.
405
+ *
406
+ * @since 2.0.0
407
+ */
408
+ public function clear_refresh_tokens() {
409
+ delete_option( 'wc_square_refresh_tokens' );
410
+ }
411
+
412
+
413
  /**
414
  * Clears any stored access tokens.
415
  *
605
  try {
606
 
607
  // cache the locations returned so they can be used elsewhere
608
+ $this->locations = $this->get_plugin()->get_api( $this->get_access_token(), $this->is_sandbox() )->get_locations();
609
 
610
  // check the returned IDs against what's currently configured
611
  $stored_location_id = $this->get_location_id();
672
  return $sor;
673
  }
674
 
675
+ /**
676
+ * Gets the refresh token.
677
+ *
678
+ * @since 2.0.0
679
+ *
680
+ * @return string|null
681
+ */
682
+ public function get_refresh_token() {
683
+
684
+ if ( empty( $this->refresh_token ) ) {
685
+
686
+ $tokens = $this->get_refresh_tokens();
687
+ $token = null;
688
+
689
+ if ( ! empty( $tokens[ $this->get_environment() ] ) ) {
690
+ $token = $tokens[ $this->get_environment() ];
691
+ }
692
+
693
+ if ( $token && Utilities\Encryption_Utility::is_encryption_supported() ) {
694
+
695
+ $encryption = new Utilities\Encryption_Utility();
696
+
697
+ try {
698
+
699
+ $token = $encryption->decrypt_data( $token );
700
+
701
+ } catch ( Framework\SV_WC_Plugin_Exception $exception ) {
702
+
703
+ // log the event, but don't halt the process
704
+ $this->get_plugin()->log( 'Could not decrypt refresh token. ' . $exception->getMessage() );
705
+ }
706
+ }
707
+
708
+ $this->refresh_token = $token;
709
+ }
710
+
711
+ /**
712
+ * Filters the configured refresh token.
713
+ *
714
+ * @since 2.0.0
715
+ *
716
+ * @param string $refresh_token
717
+ */
718
+ return apply_filters( 'wc_square_refresh_token', $this->refresh_token );
719
+ }
720
 
721
  /**
722
  * Gets the access token.
775
  * @return array
776
  */
777
  public function get_access_tokens() {
 
778
  return (array) get_option( 'wc_square_access_tokens', [] );
779
  }
780
 
781
 
782
+ /**
783
+ * Gets the stored refresh tokens.
784
+ *
785
+ * Each environment may have its own token.
786
+ *
787
+ * @since 2.0.0
788
+ *
789
+ * @return array
790
+ */
791
+ public function get_refresh_tokens() {
792
+ return (array) get_option( 'wc_square_refresh_tokens', [] );
793
+ }
794
+
795
+
796
  /**
797
  * Gets the configured environment.
798
  *
801
  * @return string
802
  */
803
  public function get_environment() {
804
+ return defined( 'WC_SQUARE_SANDBOX' ) && WC_SQUARE_SANDBOX ? 'sandbox' : 'production';
 
805
  }
806
 
807
 
includes/Sync/Product_Import.php CHANGED
@@ -27,6 +27,7 @@ use SkyVerge\WooCommerce\PluginFramework\v5_4_0 as Framework;
27
  use SquareConnect\Model\SearchCatalogObjectsResponse;
28
  use WooCommerce\Square\Handlers\Category;
29
  use WooCommerce\Square\Handlers\Product;
 
30
  use WooCommerce\Square\Utilities\Money_Utility;
31
 
32
  defined( 'ABSPATH' ) or exit;
@@ -255,7 +256,6 @@ class Product_Import extends Stepped_Job {
255
  private function import_product( $catalog_object ) {
256
 
257
  $product_id = 0;
258
- $data = $this->extract_product_data( $catalog_object );
259
 
260
  try {
261
 
@@ -264,6 +264,13 @@ class Product_Import extends Stepped_Job {
264
  throw new Framework\SV_WC_Plugin_Exception( __( 'You do not have permission to create products', 'woocommerce-square' ) );
265
  }
266
 
 
 
 
 
 
 
 
267
  /**
268
  * Filters the data that is used to create a new WooCommerce product during import.
269
  *
@@ -351,6 +358,31 @@ class Product_Import extends Stepped_Job {
351
 
352
  } catch ( Framework\SV_WC_Plugin_Exception $e ) {
353
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  wc_square()->log( 'Error creating product during import: ' . $e->getMessage() );
355
 
356
  // remove the product when creation fails
@@ -369,6 +401,7 @@ class Product_Import extends Stepped_Job {
369
  *
370
  * @param \SquareConnect\Model\CatalogObject $catalog_object the catalog object
371
  * @return array|null
 
372
  */
373
  protected function extract_product_data( $catalog_object ) {
374
 
@@ -400,7 +433,30 @@ class Product_Import extends Stepped_Job {
400
  $data['variations'] = [];
401
 
402
  foreach ( $variations as $variation ) {
403
- $data['variations'][] = $this->extract_square_item_variation_data( $variation );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  }
405
 
406
  $data['attributes'] = [ [
@@ -438,13 +494,20 @@ class Product_Import extends Stepped_Job {
438
  *
439
  * @param \SquareConnect\Model\CatalogObject $variation the variation object
440
  * @return array
 
441
  */
442
  protected function extract_square_item_variation_data( $variation ) {
443
 
 
 
 
 
 
 
444
  $data = [
445
- 'name' => $variation->getItemVariationData()->getName() ?: '',
446
- 'sku' => $variation->getItemVariationData()->getSku() ?: '',
447
- 'regular_price' => Money_Utility::cents_to_float( $variation->getItemVariationData()->getPriceMoney()->getAmount() ),
448
  'stock_quantity' => null,
449
  'managing_stock' => true,
450
  'square_meta' => [
@@ -455,7 +518,7 @@ class Product_Import extends Stepped_Job {
455
  [
456
  'name' => 'Attribute',
457
  'is_variation' => true,
458
- 'option' => $variation->getItemVariationData()->getName() ?: '',
459
  ],
460
  ],
461
  ];
27
  use SquareConnect\Model\SearchCatalogObjectsResponse;
28
  use WooCommerce\Square\Handlers\Category;
29
  use WooCommerce\Square\Handlers\Product;
30
+ use WooCommerce\Square\Sync\Records\Record;
31
  use WooCommerce\Square\Utilities\Money_Utility;
32
 
33
  defined( 'ABSPATH' ) or exit;
256
  private function import_product( $catalog_object ) {
257
 
258
  $product_id = 0;
 
259
 
260
  try {
261
 
264
  throw new Framework\SV_WC_Plugin_Exception( __( 'You do not have permission to create products', 'woocommerce-square' ) );
265
  }
266
 
267
+ // sanity check for valid API data
268
+ if ( ! $catalog_object instanceof \SquareConnect\Model\CatalogObject || ! $catalog_object->getItemData() instanceof \SquareConnect\Model\CatalogItem ) {
269
+ throw new Framework\SV_WC_Plugin_Exception( __( 'Invalid data', 'woocommerce-square' ) );
270
+ }
271
+
272
+ $data = $this->extract_product_data( $catalog_object );
273
+
274
  /**
275
  * Filters the data that is used to create a new WooCommerce product during import.
276
  *
358
 
359
  } catch ( Framework\SV_WC_Plugin_Exception $e ) {
360
 
361
+ if ( $catalog_object instanceof \SquareConnect\Model\CatalogObject && $catalog_object->getItemData() instanceof \SquareConnect\Model\CatalogItem ) {
362
+
363
+ $message = sprintf(
364
+ /* translators: Placeholders: %1$s - Square item name, %2$s - failure reason */
365
+ __( 'Could not import "%1$s" from Square. %2$s', 'woocommerce-square' ),
366
+ $catalog_object->getItemData()->getName(),
367
+ $e->getMessage()
368
+ );
369
+
370
+ // use a generic alert for invalid data
371
+ } else {
372
+
373
+ $message = sprintf(
374
+ /* translators: Placeholders: %s - failure reason */
375
+ __( 'Could not import item from Square. %s', 'woocommerce-square' ),
376
+ $e->getMessage()
377
+ );
378
+ }
379
+
380
+ // alert for failed product imports
381
+ Records::set_record( [
382
+ 'type' => 'alert',
383
+ 'message' => $message,
384
+ ] );
385
+
386
  wc_square()->log( 'Error creating product during import: ' . $e->getMessage() );
387
 
388
  // remove the product when creation fails
401
  *
402
  * @param \SquareConnect\Model\CatalogObject $catalog_object the catalog object
403
  * @return array|null
404
+ * @throws Framework\SV_WC_Plugin_Exception
405
  */
406
  protected function extract_product_data( $catalog_object ) {
407
 
433
  $data['variations'] = [];
434
 
435
  foreach ( $variations as $variation ) {
436
+
437
+ // sanity check for valid API data
438
+ if ( ! $variation instanceof \SquareConnect\Model\CatalogObject || ! $variation->getItemVariationData() instanceof \SquareConnect\Model\CatalogItemVariation ) {
439
+ continue;
440
+ }
441
+
442
+ try {
443
+
444
+ $data['variations'][] = $this->extract_square_item_variation_data( $variation );
445
+
446
+ } catch ( Framework\SV_WC_Plugin_Exception $exception ) {
447
+
448
+ // alert for failed variation imports
449
+ Records::set_record( [
450
+ 'type' => 'alert',
451
+ 'message' => sprintf(
452
+ /* translators: Placeholders: %1$s - Square item name, %2$s - Square item variation name, %3$s - failure reason */
453
+ __( 'Could not import "%1$s - %2$s" from Square. %3$s', 'woocommerce-square' ),
454
+ $catalog_object->getItemData()->getName(),
455
+ $variation->getItemVariationData()->getName(),
456
+ $exception->getMessage()
457
+ ),
458
+ ] );
459
+ }
460
  }
461
 
462
  $data['attributes'] = [ [
494
  *
495
  * @param \SquareConnect\Model\CatalogObject $variation the variation object
496
  * @return array
497
+ * @throws Framework\SV_WC_Plugin_Exception
498
  */
499
  protected function extract_square_item_variation_data( $variation ) {
500
 
501
+ $variation_data = $variation->getItemVariationData();
502
+
503
+ if ( 'VARIABLE_PRICING' === $variation_data->getPricingType() ) {
504
+ throw new Framework\SV_WC_Plugin_Exception( __( 'Items with variable pricing cannot be imported.', 'woocommerce-square' ) );
505
+ }
506
+
507
  $data = [
508
+ 'name' => $variation_data->getName() ?: '',
509
+ 'sku' => $variation_data->getSku() ?: '',
510
+ 'regular_price' => $variation_data->getPriceMoney() && $variation_data->getPriceMoney()->getAmount() ? Money_Utility::cents_to_float( $variation->getItemVariationData()->getPriceMoney()->getAmount() ) : null,
511
  'stock_quantity' => null,
512
  'managing_stock' => true,
513
  'square_meta' => [
518
  [
519
  'name' => 'Attribute',
520
  'is_variation' => true,
521
+ 'option' => $variation_data->getName() ?: '',
522
  ],
523
  ],
524
  ];
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: automattic, royho, woothemes, bor0
3
  Tags: credit card, square, woocommerce, inventory sync
4
  Requires at least: 4.6
5
- Tested up to: 5.2.0
6
  Requires PHP: 5.6
7
- Stable tag: 2.0.4
8
  License: GPLv3
9
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
10
 
@@ -72,6 +72,13 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
72
 
73
  == Changelog ==
74
 
 
 
 
 
 
 
 
75
  = 2.0.4 - 2019.09.03 =
76
  * Fix - Add adjustments to Square order in the event of discrepancy with WooCommerce total
77
 
2
  Contributors: automattic, royho, woothemes, bor0
3
  Tags: credit card, square, woocommerce, inventory sync
4
  Requires at least: 4.6
5
+ Tested up to: 5.2.3
6
  Requires PHP: 5.6
7
+ Stable tag: 2.0.5
8
  License: GPLv3
9
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
10
 
72
 
73
  == Changelog ==
74
 
75
+ = 2.0.5 - 2019.10.16 =
76
+ * Fix - Access token renewal by adding support for refresh tokens as per the new Square API
77
+ * Fix - Variable pricing import and adding an alert when these type of products are ignored.
78
+ * Fix - Line item discounts and other adjustments being ignored.
79
+ * Tweak - Add a notice when a refresh token is not present to warn users to re-connect their accounts.
80
+ * Feature - Added support for Sandbox accounts.
81
+
82
  = 2.0.4 - 2019.09.03 =
83
  * Fix - Add adjustments to Square order in the event of discrepancy with WooCommerce total
84
 
woocommerce-square.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  /**
3
  * Plugin Name: WooCommerce Square
4
- * Version: 2.0.4
5
  * Plugin URI: https://woocommerce.com/products/square/
6
  * Description: Adds ability to sync inventory between WooCommerce and Square POS. In addition, you can also make purchases through the Square payment gateway.
7
  * Author: WooCommerce
@@ -19,7 +19,7 @@
19
  * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
20
  *
21
  * WC requires at least: 3.0
22
- * WC tested up to: 3.7.0
23
  */
24
 
25
  defined( 'ABSPATH' ) or exit;
1
  <?php
2
  /**
3
  * Plugin Name: WooCommerce Square
4
+ * Version: 2.0.5
5
  * Plugin URI: https://woocommerce.com/products/square/
6
  * Description: Adds ability to sync inventory between WooCommerce and Square POS. In addition, you can also make purchases through the Square payment gateway.
7
  * Author: WooCommerce
19
  * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
20
  *
21
  * WC requires at least: 3.0
22
+ * WC tested up to: 3.7.1
23
  */
24
 
25
  defined( 'ABSPATH' ) or exit;