WooCommerce Square - Version 1.0.25

Version Description

  • 2018-01-29 =
  • Tweaks - Error handling.
  • Public release on .org
Download this release

Release Info

Developer royho
Plugin Icon 128x128 WooCommerce Square
Version 1.0.25
Comparing to
See all releases

Version 1.0.25

assets/css/wc-square-admin-styles.css ADDED
@@ -0,0 +1,46 @@
1
+ .wc-square-progress-bar {
2
+ background-color: #23282d;
3
+ height: 15px;
4
+ padding: 5px;
5
+ width: 100%;
6
+ margin: 50px 0;
7
+ border-radius: 3px;
8
+ box-shadow: 0 1px 3px #000 inset, 0 1px 0 #444;
9
+ }
10
+
11
+ .wc-square-progress-bar span {
12
+ display: inline-block;
13
+ height: 100%;
14
+ width:1%;
15
+ border-radius: 3px;
16
+ box-shadow: 0 1px 0 rgba(255, 255, 255, .3) inset;
17
+ transition: width .4s ease-in-out;
18
+ background-color:#00a0d2;
19
+ }
20
+
21
+ .sq-input {
22
+ height: 60px;
23
+ }
24
+
25
+ .wc-square-connect-button, .wc-square-connect-button:active, .wc-square-connect-button:visited {
26
+ text-decoration: none;
27
+ display: inline-block;
28
+ border-radius: 3px;
29
+ background-color: #333333;
30
+ padding: 7px 10px;
31
+ height: 30px;
32
+ color: #ffffff;
33
+ box-shadow: 1px 1px 1px 0px #999999;
34
+ }
35
+
36
+ .wc-square-connect-button:hover, .wc-square-connect-button:focus {
37
+ color: #ffffff;
38
+ background-color: #444444;
39
+ }
40
+
41
+ .wc-square-connect-button span {
42
+ vertical-align: top;
43
+ display: inline-block;
44
+ padding-top: 7px;
45
+ padding-left: 10px;
46
+ }
assets/css/wc-square-frontend-styles.css ADDED
@@ -0,0 +1,14 @@
1
+ .sq-input {
2
+ margin: 0 !important;
3
+ font-size: 1.387em;
4
+ background-color: #f2f2f2;
5
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.125);
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ @media only screen and (max-width: 500px) {
10
+ div.payment_box.payment_method_square .form-row.form-row-first, div.payment_box.payment_method_square .form-row.form-row-last {
11
+ float:none;
12
+ width:100%;
13
+ }
14
+ }
assets/js/wc-square-admin-scripts.js ADDED
@@ -0,0 +1,115 @@
1
+ jQuery( document ).ready( function( $ ) {
2
+ 'use strict';
3
+
4
+ // create namespace to avoid any possible conflicts
5
+ $.wc_square_admin = {
6
+ /**
7
+ * Loops through the sync process
8
+ *
9
+ * @since 1.0.0
10
+ * @version 1.0.14
11
+ * @param int process the current step in the loop
12
+ * @param string type the type of the AJAX call
13
+ */
14
+ sync: function( process, type ) {
15
+ var data = {
16
+ security: wc_square_local.ajaxSyncNonce,
17
+ process: parseInt( process, 10 ),
18
+ action: 'wc-to-square' === type ? 'wc_to_square' : 'square_to_wc'
19
+ };
20
+
21
+ $.ajax({
22
+ type: 'POST',
23
+ data: data,
24
+ url: wc_square_local.admin_ajax_url
25
+ }).done( function( response ) {
26
+ if ( 'done' === response.process ) {
27
+ // Triggers when all processing is done.
28
+ $( 'body' ).trigger( 'woocommerce_square_wc_to_square_sync_complete', [ response ] );
29
+
30
+ $( 'table.form-table' ).unblock();
31
+
32
+ $( '.wc-square-progress-bar span' ).css( { width: response.percentage + '%' } ).parent( '.wc-square-progress-bar' ).fadeOut( 'slow', function() {
33
+ alert( response.message );
34
+ });
35
+
36
+ } else {
37
+ $( '.wc-square-progress-bar span' ).css( { width: response.percentage + '%' } );
38
+
39
+ $.wc_square_admin.sync( parseInt( response.process, 10 ), response.type );
40
+ }
41
+ }).fail( function( jqXHR, textStatus, errorThrown ) {
42
+ $( 'table.form-table' ).unblock();
43
+ console.log( errorThrown );
44
+ alert( errorThrown );
45
+ });
46
+ },
47
+
48
+ init: function() {
49
+
50
+ $( '.woocommerce_page_wc-settings' ).on( 'click', '#wc-to-square, #square-to-wc', function( e ) {
51
+ e.preventDefault();
52
+
53
+ var confirmed = confirm( wc_square_local.i18n.confirm_sync );
54
+
55
+ if ( ! confirmed ) {
56
+ return;
57
+ }
58
+
59
+ var page = $( this ).parents( 'table.form-table' ),
60
+ progress_bar = $( '<div class="wc-square-progress-bar wc-square-stripes"><span class="step"></span></div>' );
61
+
62
+ // remove the progress bar on each trigger
63
+ $( '.wc-square-progress-bar' ).remove();
64
+
65
+ page.block({
66
+ message: null,
67
+ overlayCSS: {
68
+ background: '#fff',
69
+ opacity: 0.6
70
+ }
71
+ });
72
+
73
+ // add the progress bar
74
+ page.after( progress_bar );
75
+
76
+ $.wc_square_admin.sync( 0, $( this ).attr( 'id' ) );
77
+ });
78
+
79
+ $( document.body ).on( 'change', '#woocommerce_square_testmode', function() {
80
+ if ( $( this ).is( ':checked' ) ) {
81
+ $( '#woocommerce_square_application_id' ).parents( 'tr' ).eq(0).hide();
82
+ $( '#woocommerce_square_token' ).parents( 'tr' ).eq(0).hide();
83
+
84
+ $( '#woocommerce_square_sandbox_application_id' ).parents( 'tr' ).eq(0).show();
85
+ $( '#woocommerce_square_sandbox_token' ).parents( 'tr' ).eq(0).show();
86
+ } else {
87
+ $( '#woocommerce_square_application_id' ).parents( 'tr' ).eq(0).show();
88
+ $( '#woocommerce_square_token' ).parents( 'tr' ).eq(0).show();
89
+
90
+ $( '#woocommerce_square_sandbox_application_id' ).parents( 'tr' ).eq(0).hide();
91
+ $( '#woocommerce_square_sandbox_token' ).parents( 'tr' ).eq(0).hide();
92
+ }
93
+ });
94
+
95
+ $( '#woocommerce_square_testmode' ).trigger( 'change' );
96
+
97
+ $( document.body ).on( 'change', '#woocommerce_squareconnect_sync_products', function() {
98
+ if ( $( this ).is( ':checked' ) ) {
99
+ $( '#woocommerce_squareconnect_sync_categories' ).parents( 'tr' ).eq(0).show();
100
+ $( '#woocommerce_squareconnect_sync_inventory' ).parents( 'tr' ).eq(0).show();
101
+ $( '#woocommerce_squareconnect_sync_images' ).parents( 'tr' ).eq(0).show();
102
+ } else {
103
+ $( '#woocommerce_squareconnect_sync_categories' ).parents( 'tr' ).eq(0).hide();
104
+ $( '#woocommerce_squareconnect_sync_inventory' ).parents( 'tr' ).eq(0).hide();
105
+ $( '#woocommerce_squareconnect_sync_images' ).parents( 'tr' ).eq(0).hide();
106
+ }
107
+ });
108
+
109
+ $( '#woocommerce_squareconnect_sync_products' ).trigger( 'change' );
110
+ }
111
+ }; // close namespace
112
+
113
+ $.wc_square_admin.init();
114
+ // end document ready
115
+ });
assets/js/wc-square-admin-scripts.min.js ADDED
@@ -0,0 +1 @@
1
+ jQuery(document).ready(function(e){"use strict";e.wc_square_admin={sync:function(o,c){var r={security:wc_square_local.ajaxSyncNonce,process:parseInt(o,10),action:"wc-to-square"===c?"wc_to_square":"square_to_wc"};e.ajax({type:"POST",data:r,url:wc_square_local.admin_ajax_url}).done(function(o){"done"===o.process?(e("body").trigger("woocommerce_square_wc_to_square_sync_complete",[o]),e("table.form-table").unblock(),e(".wc-square-progress-bar span").css({width:o.percentage+"%"}).parent(".wc-square-progress-bar").fadeOut("slow",function(){alert(o.message)})):(e(".wc-square-progress-bar span").css({width:o.percentage+"%"}),e.wc_square_admin.sync(parseInt(o.process,10),o.type))}).fail(function(o,c,r){e("table.form-table").unblock(),console.log(r),alert(r)})},init:function(){e(".woocommerce_page_wc-settings").on("click","#wc-to-square, #square-to-wc",function(o){o.preventDefault();if(confirm(wc_square_local.i18n.confirm_sync)){var c=e(this).parents("table.form-table"),r=e('<div class="wc-square-progress-bar wc-square-stripes"><span class="step"></span></div>');e(".wc-square-progress-bar").remove(),c.block({message:null,overlayCSS:{background:"#fff",opacity:.6}}),c.after(r),e.wc_square_admin.sync(0,e(this).attr("id"))}}),e(document.body).on("change","#woocommerce_square_testmode",function(){e(this).is(":checked")?(e("#woocommerce_square_application_id").parents("tr").eq(0).hide(),e("#woocommerce_square_token").parents("tr").eq(0).hide(),e("#woocommerce_square_sandbox_application_id").parents("tr").eq(0).show(),e("#woocommerce_square_sandbox_token").parents("tr").eq(0).show()):(e("#woocommerce_square_application_id").parents("tr").eq(0).show(),e("#woocommerce_square_token").parents("tr").eq(0).show(),e("#woocommerce_square_sandbox_application_id").parents("tr").eq(0).hide(),e("#woocommerce_square_sandbox_token").parents("tr").eq(0).hide())}),e("#woocommerce_square_testmode").trigger("change"),e(document.body).on("change","#woocommerce_squareconnect_sync_products",function(){e(this).is(":checked")?(e("#woocommerce_squareconnect_sync_categories").parents("tr").eq(0).show(),e("#woocommerce_squareconnect_sync_inventory").parents("tr").eq(0).show(),e("#woocommerce_squareconnect_sync_images").parents("tr").eq(0).show()):(e("#woocommerce_squareconnect_sync_categories").parents("tr").eq(0).hide(),e("#woocommerce_squareconnect_sync_inventory").parents("tr").eq(0).hide(),e("#woocommerce_squareconnect_sync_images").parents("tr").eq(0).hide())}),e("#woocommerce_squareconnect_sync_products").trigger("change")}},e.wc_square_admin.init()});
assets/js/wc-square-payments.js ADDED
@@ -0,0 +1,155 @@
1
+ (function ( $ ) {
2
+ 'use strict';
3
+
4
+ var wcSquarePaymentForm;
5
+
6
+ // create namespace to avoid any possible conflicts
7
+ $.wc_square_payments = {
8
+ init: function() {
9
+ // Checkout page
10
+ $( document.body ).on( 'updated_checkout', function() {
11
+ $.wc_square_payments.loadForm();
12
+ });
13
+
14
+ // Pay order form page
15
+ if ( $( 'form#order_review' ).length ) {
16
+ $.wc_square_payments.loadForm();
17
+ }
18
+
19
+ var custom_element = square_params.custom_form_trigger_element;
20
+
21
+ // custom click trigger for 3rd party forms that initially hides the payment form
22
+ // such as multistep checkout plugins
23
+ if ( custom_element.length ) {
24
+ $( document.body ).on( 'click', custom_element, function() {
25
+ $.wc_square_payments.loadForm();
26
+ });
27
+ }
28
+
29
+ // work around for iFrame not loading if elements being replaced is hidden
30
+ $( document.body ).on( 'click', '#payment_method_square', function() {
31
+ $( '.payment_box.payment_method_square' ).css( { 'display': 'block', 'visibility': 'visible', 'height': 'auto' } );
32
+ });
33
+ },
34
+ loadForm: function() {
35
+ if ( $( '#payment_method_square' ).length ) {
36
+ // work around for iFrame not loading if elements being replaced is hidden
37
+ if ( ! $( '#payment_method_square' ).is( ':checked' ) ) {
38
+ $( '.payment_box.payment_method_square' ).css( { 'display': 'block', 'visibility': 'hidden', 'height': '0' } );
39
+ }
40
+
41
+ // destroy the form and rebuild on each init
42
+ if ( 'object' === $.type( wcSquarePaymentForm ) ) {
43
+ wcSquarePaymentForm.destroy();
44
+ }
45
+
46
+ wcSquarePaymentForm = new SqPaymentForm({
47
+ env: square_params.environment,
48
+ applicationId: square_params.application_id,
49
+ inputClass: 'sq-input',
50
+ cardNumber: {
51
+ elementId: 'sq-card-number',
52
+ placeholder: square_params.placeholder_card_number
53
+ },
54
+ cvv: {
55
+ elementId: 'sq-cvv',
56
+ placeholder: square_params.placeholder_card_cvv
57
+ },
58
+ expirationDate: {
59
+ elementId: 'sq-expiration-date',
60
+ placeholder: square_params.placeholder_card_expiration
61
+ },
62
+ postalCode: {
63
+ elementId: 'sq-postal-code',
64
+ placeholder: square_params.placeholder_card_postal_code
65
+ },
66
+ callbacks: {
67
+ cardNonceResponseReceived: function( errors, nonce, cardData ) {
68
+ if ( errors ) {
69
+ var html = '';
70
+
71
+ html += '<ul class="woocommerce_error woocommerce-error">';
72
+
73
+ // handle errors
74
+ $( errors ).each( function( index, error ) {
75
+ html += '<li>' + error.message + '</li>';
76
+ });
77
+
78
+ html += '</ul>';
79
+
80
+ // append it to DOM
81
+ $( '.payment_method_square fieldset' ).eq(0).prepend( html );
82
+ } else {
83
+ var $form = $( 'form.woocommerce-checkout, form#order_review' );
84
+
85
+ // inject nonce to a hidden field to be submitted
86
+ $form.append( '<input type="hidden" class="square-nonce" name="square_nonce" value="' + nonce + '" />' );
87
+
88
+ $form.submit();
89
+ }
90
+ },
91
+
92
+ paymentFormLoaded: function() {
93
+ wcSquarePaymentForm.setPostalCode( $( '#billing_postcode' ).val() );
94
+ },
95
+
96
+ unsupportedBrowserDetected: function() {
97
+ var html = '';
98
+
99
+ html += '<ul class="woocommerce_error woocommerce-error">';
100
+ html += '<li>' + square_params.unsupported_browser + '</li>';
101
+ html += '</ul>';
102
+
103
+ // append it to DOM
104
+ $( '.payment_method_square fieldset' ).eq(0).prepend( html );
105
+ }
106
+ },
107
+ inputStyles: $.parseJSON( square_params.payment_form_input_styles )
108
+ });
109
+
110
+ wcSquarePaymentForm.build();
111
+
112
+ // when checkout form is submitted on checkout page
113
+ $( 'form.woocommerce-checkout' ).on( 'checkout_place_order_square', function( event ) {
114
+ // remove any error messages first
115
+ $( '.payment_method_square .woocommerce-error' ).remove();
116
+
117
+ if ( $( '#payment_method_square' ).is( ':checked' ) && $( 'input.square-nonce' ).size() === 0 ) {
118
+ wcSquarePaymentForm.requestCardNonce();
119
+
120
+ return false;
121
+ }
122
+
123
+ return true;
124
+ });
125
+
126
+ // when checkout form is submitted on pay order page
127
+ $( 'form#order_review' ).on( 'submit', function( event ) {
128
+ // remove any error messages first
129
+ $( '.payment_method_square .woocommerce-error' ).remove();
130
+
131
+ if ( $( '#payment_method_square' ).is( ':checked' ) && $( 'input.square-nonce' ).size() === 0 ) {
132
+ wcSquarePaymentForm.requestCardNonce();
133
+
134
+ return false;
135
+ }
136
+
137
+ return true;
138
+ });
139
+
140
+ $( document.body ).on( 'checkout_error', function() {
141
+ $( 'input.square-nonce' ).remove();
142
+ });
143
+
144
+ // work around for iFrame not loading if elements being replaced is hidden
145
+ setTimeout( function() {
146
+ if ( ! $( '#payment_method_square' ).is( ':checked' ) ) {
147
+ $( '.payment_box.payment_method_square' ).css( { 'display': 'none', 'visibility': 'visible', 'height': 'auto' } );
148
+ }
149
+ }, 1000 );
150
+ }
151
+ }
152
+ }; // close namespace
153
+
154
+ $.wc_square_payments.init();
155
+ }( jQuery ) );
assets/js/wc-square-payments.min.js ADDED
@@ -0,0 +1 @@
1
+ !function(e){"use strict";var o;e.wc_square_payments={init:function(){e(document.body).on("updated_checkout",function(){e.wc_square_payments.loadForm()}),e("form#order_review").length&&e.wc_square_payments.loadForm();var o=square_params.custom_form_trigger_element;o.length&&e(document.body).on("click",o,function(){e.wc_square_payments.loadForm()}),e(document.body).on("click","#payment_method_square",function(){e(".payment_box.payment_method_square").css({display:"block",visibility:"visible",height:"auto"})})},loadForm:function(){e("#payment_method_square").length&&(e("#payment_method_square").is(":checked")||e(".payment_box.payment_method_square").css({display:"block",visibility:"hidden",height:"0"}),"object"===e.type(o)&&o.destroy(),(o=new SqPaymentForm({env:square_params.environment,applicationId:square_params.application_id,inputClass:"sq-input",cardNumber:{elementId:"sq-card-number",placeholder:square_params.placeholder_card_number},cvv:{elementId:"sq-cvv",placeholder:square_params.placeholder_card_cvv},expirationDate:{elementId:"sq-expiration-date",placeholder:square_params.placeholder_card_expiration},postalCode:{elementId:"sq-postal-code",placeholder:square_params.placeholder_card_postal_code},callbacks:{cardNonceResponseReceived:function(o,r,a){if(o){var t="";t+='<ul class="woocommerce_error woocommerce-error">',e(o).each(function(e,o){t+="<li>"+o.message+"</li>"}),t+="</ul>",e(".payment_method_square fieldset").eq(0).prepend(t)}else{var n=e("form.woocommerce-checkout, form#order_review");n.append('<input type="hidden" class="square-nonce" name="square_nonce" value="'+r+'" />'),n.submit()}},paymentFormLoaded:function(){o.setPostalCode(e("#billing_postcode").val())},unsupportedBrowserDetected:function(){var o="";o+='<ul class="woocommerce_error woocommerce-error">',o+="<li>"+square_params.unsupported_browser+"</li>",o+="</ul>",e(".payment_method_square fieldset").eq(0).prepend(o)}},inputStyles:e.parseJSON(square_params.payment_form_input_styles)})).build(),e("form.woocommerce-checkout").on("checkout_place_order_square",function(r){return e(".payment_method_square .woocommerce-error").remove(),!e("#payment_method_square").is(":checked")||0!==e("input.square-nonce").size()||(o.requestCardNonce(),!1)}),e("form#order_review").on("submit",function(r){return e(".payment_method_square .woocommerce-error").remove(),!e("#payment_method_square").is(":checked")||0!==e("input.square-nonce").size()||(o.requestCardNonce(),!1)}),e(document.body).on("checkout_error",function(){e("input.square-nonce").remove()}),setTimeout(function(){e("#payment_method_square").is(":checked")||e(".payment_box.payment_method_square").css({display:"none",visibility:"visible",height:"auto"})},1e3))}},e.wc_square_payments.init()}(jQuery);
changelog.txt ADDED
@@ -0,0 +1,106 @@
1
+ *** WooCommerce Square Changelog ***
2
+
3
+ 2018-01-29 - version 1.0.25
4
+ * Tweaks - Error handling.
5
+ * Add - Public Release on .org
6
+
7
+ 2017-12-13 - version 1.0.24
8
+ * Fix - In some cases rounding issues occur causing payment unable to process.
9
+ * Update - WC tested up to version.
10
+ * Add - Readme.txt.
11
+
12
+ 2017-11-10 - version 1.0.23
13
+ * Add - WC minimum requirements in header.
14
+ * Fix - WC Product sometimes is not a proper object preventing sync.
15
+ * Fix - PHP 7 notice with amount not cast an int.
16
+
17
+ 2017-09-06 - version 1.0.22
18
+ * Tweak - Increase inventory polling cache and add timeout limit.
19
+ * Fix - CRON scheduling time not using UTC causes delay in CRON job.
20
+
21
+ 2017-07-26 - version 1.0.21
22
+ * Fix - Non decimal place price such as Japanese Yen is not formatted properly when syncing Square to WC.
23
+
24
+ 2017-06-29 - version 1.0.20
25
+ * Fix - Schedule sale price not updating on Square when sale is over.
26
+ * Fix - Pass price to Square tax exclusive instead of inclusive.
27
+ * Fix - Create customer error on payments when phone number is not provided.
28
+ * Update - Settings location description to be more clear.
29
+
30
+ 2017-06-06 - version 1.0.19
31
+ * Fix - Sync currency amounts that do not contain decimals correctly.
32
+
33
+ 2017-05-19 - version 1.0.18
34
+ * Fix - Better logic of sale price on WC when syncing so it doesn't override regular price unintented.
35
+ * Fix - If image exists for a product, don't sync the image to prevent duplicated images being created.
36
+ * Fix - Enable status not showing for Square payments in gateway list.
37
+
38
+ 2017-05-08 - version 1.0.17
39
+ * Fix - WC 3.0 debug tools compatibility.
40
+ * Add - Support for Japan market.
41
+
42
+ 2017-03-31 - version 1.0.16
43
+ * Add - Confirmation message before bulk sync.
44
+ * Add - Cron schedules for when product stock changes instead of firing off immediately.
45
+
46
+ 2017-03-27 - version 1.0.15
47
+ * Update - Additional updates for WooCommerce 3.0.0 compatibility.
48
+ * Add - Support for UK market.
49
+
50
+ 2017-03-13 - version 1.0.14
51
+ * Fix - When multiple notify emails are set, causes 500 server error.
52
+ * Fix - Sync issues when syncing large datasets.
53
+ * Fix - Sync item with no sku sometimes gets corrupted with other item data.
54
+ * Update - WooCommerce 3.0.0 compatibility.
55
+
56
+ 2017-01-17 - version 1.0.13
57
+ * Add - Support for Australian markets.
58
+
59
+ 2017-01-09 - version 1.0.12
60
+ * Fix - When syncing inventory WC to Square, stock level becomes zero.
61
+
62
+ 2016-10-26 - version 1.0.11
63
+ * Fix - When WC API is used, it can cause duplicate WC API Exception.
64
+
65
+ 2016-10-14 - version 1.0.10
66
+ * Update - Make sure Square only works for US and CA merchants.
67
+
68
+ 2016-09-12 - version 1.0.9
69
+ * Fix - Normalize price when syncing Square to WC to prevent errors.
70
+ * Add - Option to disable sync per product.
71
+
72
+ 2016-09-08 - version 1.0.8
73
+ * Add - WC Products CRUD to replace REST API for creating products to prevent interference with wc-api.
74
+ * Add - Filter to add custom DOM elements for payments to render payment form "woocommerce_square_payment_form_trigger_element".
75
+ * Fix - Payments token expiration was not handled correctly due to using API v2.
76
+
77
+ 2016-08-30 - version 1.0.7
78
+ * Tweak - Replace all wp_remote_requests with raw cURL.
79
+ * Fix - In IE, the browser won't replace iFrame form fields when it is hidden.
80
+
81
+ 2016-08-27 - version 1.0.6
82
+ * Add - Clear cache/transient tool in system status->tool.
83
+ * Fix - Prevent infinite loop when polling Square inventory.
84
+
85
+ 2016-08-25 - version 1.0.5
86
+ * Fix - Image not syncing when using wp_remote_request. Changed to raw cURL.
87
+
88
+ 2016-08-19 - version 1.0.4
89
+ * Tweak - Payment form field styles.
90
+ * Add - woocommerce_square_payment_input_styles filter to allow manipulation of the form styles.
91
+ * Fix - When duplicating product in WC, it replaces the original product on Square.
92
+ * Fix - In FireFox, the browser won't replace iFrame form fields when it is hidden.
93
+
94
+ 2016-08-16 - version 1.0.3
95
+ * Fix - When duplicating product in WC, it replaces the original product on Square.
96
+ * Fix - oAuth tokens were not renewing properly.
97
+
98
+ 2016-08-04 - version 1.0.2
99
+ * Update - HTTP protocol to version 1.1 to prevent timeouts.
100
+
101
+ 2016-08-01 - version 1.0.1
102
+ * Tweak - Make credit card form fields more responsive in mobile devices.
103
+ * Fix - Square list endpoints may return duplicates, so put in place a check for that.
104
+
105
+ 2016-07-26 - version 1.0.0
106
+ * First Release
includes/admin/class-wc-square-admin-integration.php ADDED
@@ -0,0 +1,333 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit; // Exit if accessed directly
4
+ }
5
+
6
+ /**
7
+ * Class WC_Square_Integration
8
+ *
9
+ * Settings CRUD for the extension.
10
+ */
11
+ class WC_Square_Integration extends WC_Integration {
12
+ private $oauth_connect_url;
13
+ private $merchant_access_token;
14
+
15
+ /**
16
+ * Constructor
17
+ */
18
+ public function __construct() {
19
+ $this->id = 'squareconnect';
20
+ $this->method_title = __( 'Square', 'woocommerce-square' );
21
+ $this->method_description = __( 'Connect with Square to start syncing your products and inventory and also accept credit card and debit card payments in your checkout.', 'woocommerce-square' );
22
+ $this->merchant_access_token = get_option( 'woocommerce_square_merchant_access_token' );
23
+
24
+ $this->maybe_save_token();
25
+ $this->maybe_delete_token();
26
+
27
+ $this->init_form_fields();
28
+ $this->init_settings();
29
+
30
+ $this->oauth_connect_url = 'https://connect.woocommerce.com/login/square';
31
+
32
+ if ( WC_SQUARE_ENABLE_STAGING ) {
33
+ $this->oauth_connect_url = 'https://connect.woocommerce.com/login/squaresandbox';
34
+ }
35
+
36
+ add_action( 'woocommerce_update_options_integration_' . $this->id, array( $this, 'process_admin_options' ) );
37
+ add_filter( 'woocommerce_settings_api_form_fields_' . $this->id, array( $this, 'maybe_render_locations' ) );
38
+ }
39
+
40
+ /**
41
+ * Adds the form fields for the settings
42
+ *
43
+ * @access public
44
+ * @since 1.0.0
45
+ * @version 1.0.0
46
+ * @return bool
47
+ */
48
+ public function init_form_fields() {
49
+ $application_dashboard_url = 'https://connect.squareup.com/apps';
50
+
51
+ $form_fields = array(
52
+ 'location' => array(
53
+ 'title' => __( 'Business Location', 'woocommerce-square' ),
54
+ 'type' => 'select',
55
+ 'description' => __( 'Select the location you wish to link to this site. You must have <a href="https://squareup.com/dashboard/locations" target="_blank">locations</a> set in Square and approved.', 'woocommerce-square' ),
56
+ 'desc_tip' => false,
57
+ 'options' => array( '' => __( 'Select a Location', 'woocommerce-square' ) ),
58
+ 'disabled' => true,
59
+ ),
60
+ 'sync_email' => array(
61
+ 'title' => __( 'Notification Email', 'woocommerce-square' ),
62
+ 'type' => 'text',
63
+ 'description' => __( 'Enter the email(s) to be notified when a sync has ended. Separate each email with a comma.', 'woocommerce-square' ),
64
+ 'desc_tip' => false,
65
+ 'default' => '',
66
+ 'placeholder' => get_option( 'admin_email' ),
67
+ ),
68
+ 'logging' => array(
69
+ 'title' => __( 'Logging', 'woocommerce-square' ),
70
+ 'label' => __( 'Log debug messages', 'woocommerce-square' ),
71
+ 'type' => 'checkbox',
72
+ 'description' => __( 'Save debug messages to the WooCommerce System Status log.', 'woocommerce-square' ),
73
+ 'default' => 'no',
74
+ ),
75
+ 'sync_title' => array(
76
+ 'title' => __( 'Synchronization', 'woocommerce-square' ),
77
+ 'type' => 'title',
78
+ 'description' => __( 'Determine which aspects of your product catalog to synchronize between WooCommerce and Square. Products need to have SKUs set for each variation.', 'woocommerce-square' ),
79
+ ),
80
+ 'sync_products' => array(
81
+ 'title' => __( 'Enabled', 'woocommerce-square' ),
82
+ 'type' => 'checkbox',
83
+ 'label' => __( 'Products', 'woocommerce-square' ),
84
+ 'description' => __( 'Basic Product information will be synced, excluding Categories and Inventory.', 'woocommerce-square' ),
85
+ 'default' => 'yes',
86
+ ),
87
+ 'sync_categories' => array(
88
+ 'title' => __( 'Include Categories', 'woocommerce-square' ),
89
+ 'type' => 'checkbox',
90
+ 'label' => __( 'Sync Categories', 'woocommerce-square' ),
91
+ 'description' => __( 'Categories will sync on creation or update, and Products will have their Categories synced.', 'woocommerce-square' ),
92
+ 'default' => 'yes',
93
+ ),
94
+ 'sync_inventory' => array(
95
+ 'title' => __( 'Include Inventory', 'woocommerce-square' ),
96
+ 'type' => 'checkbox',
97
+ 'label' => __( 'Sync Inventory', 'woocommerce-square' ),
98
+ 'description' => __( 'Inventory will sync on manual update or after a Product is ordered.', 'woocommerce-square' ),
99
+ 'default' => 'yes',
100
+ ),
101
+ 'sync_images' => array(
102
+ 'title' => __( 'Include Images', 'woocommerce-square' ),
103
+ 'type' => 'checkbox',
104
+ 'label' => __( 'Sync Images', 'woocommerce-square' ),
105
+ 'description' => __( 'Product Image will be synced.', 'woocommerce-square' ),
106
+ 'default' => 'yes',
107
+ ),
108
+ 'inventory_polling' => array(
109
+ 'title' => __( 'Square Inventory Sync', 'woocommerce-square' ),
110
+ 'type' => 'checkbox',
111
+ 'label' => __( 'Enable', 'woocommerce-square' ),
112
+ 'description' => __( 'For automatic inventory syncing from Square to WooCommerce, this needs to be enabled. It will poll the inventory from Square on an hourly basis.', 'woocommerce-square' ),
113
+ 'default' => 'no',
114
+ ),
115
+ );
116
+
117
+ $this->form_fields = apply_filters( 'woocommerce_square_integration_settings_args', $form_fields );
118
+
119
+ return true;
120
+ }
121
+
122
+ /**
123
+ * Add in our own custom options
124
+ *
125
+ * @access public
126
+ * @since 1.0.0
127
+ * @version 1.0.0
128
+ * @return bool
129
+ */
130
+ public function admin_options() {
131
+ $current_user = wp_get_current_user();
132
+
133
+ $redirect_url = add_query_arg(
134
+ array(
135
+ 'page' => 'wc-settings',
136
+ 'tab' => 'integration',
137
+ 'section' => $this->id,
138
+ ),
139
+ admin_url( 'admin.php' )
140
+ );
141
+
142
+ $redirect_url = wp_nonce_url( $redirect_url, 'connect_square', 'wc_square_token_nonce' );
143
+
144
+ $query_args = array(
145
+ 'redirect' => urlencode( urlencode( $redirect_url ) ),
146
+ 'scopes' => 'MERCHANT_PROFILE_READ,PAYMENTS_READ,PAYMENTS_WRITE,CUSTOMERS_READ,CUSTOMERS_WRITE,SETTLEMENTS_READ,ITEMS_READ,ITEMS_WRITE',
147
+ );
148
+
149
+ $production_connect_url = add_query_arg( $query_args, $this->oauth_connect_url );
150
+
151
+ $disconnect_url = add_query_arg(
152
+ array(
153
+ 'page' => 'wc-settings',
154
+ 'tab' => 'integration',
155
+ 'section' => $this->id,
156
+ 'disconnect_square' => 1,
157
+ ),
158
+ admin_url( 'admin.php' )
159
+ );
160
+
161
+ $disconnect_url = wp_nonce_url( $disconnect_url, 'disconnect_square', 'wc_square_token_nonce' );
162
+
163
+ echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>';
164
+ echo wp_kses_post( wpautop( $this->get_method_description() ) );
165
+ echo '<div><input type="hidden" name="section" value="' . esc_attr( $this->id ) . '" /></div>';
166
+ ?>
167
+
168
+ <table class="form-table">
169
+ <tbody>
170
+ <tr>
171
+ <th>
172
+ <?php esc_html_e( 'Connect/Disconnect', 'woocommerce-square' ); ?>
173
+ </th>
174
+ <td>
175
+ <?php if ( ! empty( $this->merchant_access_token ) ) { ?>
176
+ <a href="<?php echo esc_attr( $disconnect_url ); ?>" class='button-primary'>
177
+ <?php echo esc_html__( 'Disconnect from Square', 'woocommerce-square' ); ?>
178
+ </a>
179
+ <?php } else { ?>
180
+ <a href="<?php echo esc_attr( $production_connect_url ); ?>" class="wc-square-connect-button">
181
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44" width="30" height="30">
182
+ <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" />
183
+ <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" />
184
+ </svg>
185
+ <span><?php esc_html_e( 'Connect with Square', 'woocommerce-square' ); ?></span>
186
+ </a>
187
+ <?php } ?>
188
+ </td>
189
+ </tr>
190
+ </tbody>
191
+ </table>
192
+
193
+ <?php
194
+ if ( ! empty( $this->merchant_access_token ) ) { ?>
195
+ <?php echo '<table class="form-table">' . $this->generate_settings_html( $this->get_form_fields(), false ) . '</table>'; ?>
196
+ <?php
197
+
198
+ // only show rest of the settings if a location is selected
199
+ if ( $this->get_option( 'location' ) ) :
200
+ ?>
201
+ <h3 class="wc-settings-sub-title"><?php esc_html_e( 'Initiate a Manual Sync', 'woocommerce-square' ); ?></h3>
202
+ <table class="form-table">
203
+ <tr valign="top">
204
+ <th scope="row" class="titledesc">
205
+ <label for="wc-to-square"><?php esc_html_e( 'WC &rarr; Square Sync', 'woocommerce-square' ); ?></label>
206
+ <?php echo wc_help_tip( __( 'Click button to perform a one time sync from WooCommerce to Square.', 'woocommerce-square' ) ); ?>
207
+ </th>
208
+ <td class="forminp">
209
+ <a href="#" id="wc-to-square" class="button button-secondary"><?php esc_html_e( 'WC &rarr; Square', 'woocommerce-square' ); ?></a>
210
+ </td>
211
+ </tr>
212
+ <tr valign="top">
213
+ <th scope="row" class="titledesc">
214
+ <label for="square-to-wc"><?php esc_html_e( 'Square &rarr; WC Sync', 'woocommerce-square' ); ?></label>
215
+ <?php echo wc_help_tip( __( 'Click button to perform a one time sync from Square to WooCommerce.', 'woocommerce-square' ) ); ?>
216
+ </th>
217
+ <td class="forminp">
218
+ <a href="#" id="square-to-wc" class="button button-secondary"><?php esc_html_e( 'Square &rarr; WC', 'woocommerce-square' ); ?></a>
219
+ </td>
220
+ </tr>
221
+ </table>
222
+ <?php endif;
223
+
224
+ do_action( 'woocommerce_square_integration_custom_settings' );
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Maybe save the token from authentication.
230
+ *
231
+ * @access public
232
+ * @since 1.0.0
233
+ * @version 1.0.0
234
+ * @return bool
235
+ */
236
+ public function maybe_save_token() {
237
+ if ( empty( $_GET['square_access_token'] ) ) {
238
+ return false;
239
+ }
240
+
241
+ if ( function_exists( 'wp_verify_nonce' ) && ! wp_verify_nonce( $_GET['wc_square_token_nonce'], 'connect_square' ) ) {
242
+ wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-square' ) );
243
+ }
244
+
245
+ $existing_token = get_option( 'woocommerce_square_merchant_access_token' );
246
+
247
+ // if token already exists, don't continue
248
+ if ( ! empty( $existing_token ) ) {
249
+ return false;
250
+ }
251
+
252
+ update_option( 'woocommerce_square_merchant_access_token', sanitize_text_field( urldecode( $_GET['square_access_token'] ) ) );
253
+
254
+ // let's set the token instance again so settings option is refreshed
255
+ $this->merchant_access_token = get_option( 'woocommerce_square_merchant_access_token' );
256
+
257
+ delete_transient( WC_Square_Connect::LOCATIONS_CACHE_KEY );
258
+
259
+ return true;
260
+ }
261
+
262
+ /**
263
+ * Maybe delete the token for merchant.
264
+ *
265
+ * @todo perhaps we should also revoke the token fromm connect.woocommerce.com??
266
+ * @access public
267
+ * @since 1.0.0
268
+ * @version 1.0.0
269
+ * @return bool
270
+ */
271
+ public function maybe_delete_token() {
272
+ if ( empty( $_GET['disconnect_square'] ) ) {
273
+ return false;
274
+ }
275
+
276
+ if ( function_exists( 'wp_verify_nonce' ) && ! wp_verify_nonce( $_GET['wc_square_token_nonce'], 'disconnect_square' ) ) {
277
+ wp_die( __( 'Cheatin&#8217; huh?', 'woocommerce-square' ) );
278
+ }
279
+
280
+ $existing_token = get_option( 'woocommerce_square_merchant_access_token' );
281
+
282
+ // if token does not exist, don't continue
283
+ if ( empty( $existing_token ) ) {
284
+ return false;
285
+ }
286
+
287
+ delete_option( 'woocommerce_square_merchant_access_token' );
288
+
289
+ // let's set the token instance again so settings option is refreshed
290
+ $this->merchant_access_token = get_option( 'woocommerce_square_merchant_access_token' );
291
+
292
+ delete_transient( WC_Square_Connect::LOCATIONS_CACHE_KEY );
293
+
294
+ return true;
295
+ }
296
+
297
+ /**
298
+ * Validates location field.
299
+ *
300
+ * @access public
301
+ * @since 1.0.0
302
+ * @version 1.0.0
303
+ * @return bool
304
+ */
305
+ public function validate_location_field( $key ) {
306
+ $field = $this->get_field_key( $key );
307
+ $value = ! empty( $_POST[ $field ] ) ? $_POST[ $field ] : '';
308
+
309
+ $token = $this->get_option( 'token' );
310
+ if ( empty( $token ) && empty( $value ) ) {
311
+ delete_transient( WC_Square_Connect::LOCATIONS_CACHE_KEY );
312
+ }
313
+
314
+ return $this->validate_select_field( $key, $value );
315
+ }
316
+
317
+ /**
318
+ * Maybe render list of locations.
319
+ *
320
+ * @param array $form_fields Form fields
321
+ *
322
+ * @return array Form fields
323
+ */
324
+ public function maybe_render_locations( $form_fields ) {
325
+ $locations = Woocommerce_Square::instance()->square_connect->get_square_business_locations();
326
+ if ( ! empty( $locations ) && ! empty( $form_fields ) ) {
327
+ $form_fields['location']['options'] = array_merge( $form_fields['location']['options'], $locations );
328
+ $form_fields['location']['disabled'] = false;
329
+ }
330
+
331
+ return $form_fields;
332
+ }
333
+ }
includes/admin/class-wc-square-admin-product-meta-box.php ADDED
@@ -0,0 +1,155 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
+ /**
7
+ * Class WC_Square_Admin_Product_Meta_Box
8
+ *
9
+ * Adds product specific sync options via meta box.
10
+ *
11
+ */
12
+ class WC_Square_Admin_Product_Meta_Box {
13
+ /**
14
+ * Constructor
15
+ *
16
+ * @version 1.0.9
17
+ * @since 1.0.9
18
+ */
19
+ public function __construct() {
20
+ // add a sync field to the product general tab
21
+ add_action( 'woocommerce_product_options_general_product_data', array( $this, 'add_product_sync_checkbox_general' ) );
22
+
23
+ // save sync field for the product general tab
24
+ add_action( 'woocommerce_process_product_meta_simple', array( $this, 'save_product_sync_checkbox_general' ) );
25
+ add_action( 'woocommerce_process_product_meta_booking', array( $this, 'save_product_sync_checkbox_general' ) );
26
+
27
+ // save sync field for variable product general tab
28
+ add_action( 'woocommerce_process_product_meta_variable', array( $this, 'save_product_sync_checkbox_general' ) );
29
+
30
+ // add sync to product bulk edit menu
31
+ add_action( 'woocommerce_product_bulk_edit_end', array( $this, 'add_product_bulk_edit_sync' ) );
32
+
33
+ // save sync to product bulk edit
34
+ add_action( 'woocommerce_product_bulk_edit_save', array( $this, 'save_product_bulk_edit_sync' ) );
35
+ }
36
+
37
+ /**
38
+ * Add a sync field to the product general tab
39
+ *
40
+ * @access public
41
+ * @since 1.0.9
42
+ * @version 1.0.9
43
+ * @return bool
44
+ */
45
+ public function add_product_sync_checkbox_general() {
46
+ global $post;
47
+
48
+ $sync = get_post_meta( $post->ID, '_wcsquare_disable_sync', true );
49
+
50
+ // set default to no if nothing is set
51
+ if ( empty( $sync ) ) {
52
+ $sync = 'no';
53
+ }
54
+
55
+ $output = '';
56
+
57
+ $output .= '<div class="options_group show_if_simple show_if_variable show_if_booking">' . PHP_EOL;
58
+
59
+ $output .= '<p class="form-field wcsquare_product_default_sync_field"><label for="wcsquare_product_default_sync">' . wp_kses_post( __( '(Square) Disable Sync', 'woocommerce-square' ) ) . '</label><input type="checkbox" name="_wcsquare_disable_sync" id="wcsquare_product_default_sync" value="yes" ' . checked( 'yes', $sync, false ) . '/>' . PHP_EOL;
60
+
61
+ $output .= '<span class="description">' . wp_kses_post( __( 'Check box to disable this product from syncing.', 'woocommerce-square' ) ) . '</span>' . PHP_EOL;
62
+
63
+ $output .= '</p>' . PHP_EOL;
64
+
65
+ $output .= '</div>';
66
+
67
+ echo $output;
68
+
69
+ return true;
70
+ }
71
+
72
+ /**
73
+ * Save the sync field for the product general tab
74
+ *
75
+ * @access public
76
+ * @since 1.0.9
77
+ * @version 1.0.9
78
+ * @param int $post_id
79
+ * @return bool
80
+ */
81
+ public function save_product_sync_checkbox_general( $post_id ) {
82
+ if ( empty( $post_id ) ) {
83
+ return;
84
+ }
85
+
86
+ if ( ! empty( $_POST['_wcsquare_disable_sync'] ) ) {
87
+ update_post_meta( $post_id, '_wcsquare_disable_sync', 'yes' );
88
+
89
+ } else {
90
+
91
+ update_post_meta( $post_id, '_wcsquare_disable_sync', 'no' );
92
+ }
93
+
94
+ return true;
95
+ }
96
+
97
+ /**
98
+ * Add sync setting to product bulk edit menu
99
+ *
100
+ * @access public
101
+ * @since 1.0.9
102
+ * @version 1.0.9
103
+ * @return bool
104
+ */
105
+ public function add_product_bulk_edit_sync() {
106
+ ?>
107
+ <label>
108
+ <span class="title"><?php esc_html_e( 'Disable Sync', 'woocommerce-square' ); ?></span>
109
+ <span class="input-text-wrap">
110
+ <select class="square-sync-product" name="_wcsquare_disable_sync">
111
+ <?php
112
+ $options = array(
113
+ '' => __( '— No Change —', 'woocommerce-square' ),
114
+ 'yes' => __( 'Yes', 'woocommerce-square' ),
115
+ 'no' => __( 'No', 'woocommerce-square' )
116
+ );
117
+
118
+ foreach ( $options as $key => $value ) {
119
+ echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
120
+ }
121
+ ?>
122
+ </select>
123
+ </span>
124
+ </label>
125
+ <?php
126
+ }
127
+
128
+ /**
129
+ * Save sync setting to product bulk edit
130
+ *
131
+ * @access public
132
+ * @since 1.0.9
133
+ * @version 1.0.9
134
+ * @param object $product
135
+ * @return bool
136
+ */
137
+ public function save_product_bulk_edit_sync( $product ) {
138
+ if ( empty( $product ) ) {
139
+ return;
140
+ }
141
+
142
+ if ( ! empty( $_GET['_wcsquare_disable_sync'] ) && 'yes' === $_GET['_wcsquare_disable_sync'] ) {
143
+ update_post_meta( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id(), '_wcsquare_disable_sync', 'yes' );
144
+
145
+ } else {
146
+ update_post_meta( version_compare( WC_VERSION, '3.0.0', '<' ) ? $product->id : $product->get_id(), '_wcsquare_disable_sync', 'no' );
147
+ }
148
+
149
+ return true;
150
+ }
151
+
152
+ }
153
+
154
+ new WC_Square_Admin_Product_Meta_Box();
155
+
includes/admin/class-wc-square-bulk-sync-handler.php ADDED
@@ -0,0 +1,444 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
+ /**
7
+ * Class WC_Square_Bulk_Sync_Handler
8
+ *
9
+ * Facilitates Bulk syncing to/from Square/WC. Handles AJAX initiation of
10
+ * sync, progress updates, actual sync method calls, and sync completion emails.
11
+ */
12
+ class WC_Square_Bulk_Sync_Handler {
13
+
14
+ public $connect;
15
+ public $wc_to_square;
16
+ public $square_to_wc;
17
+
18
+ public function __construct( WC_Square_Connect $connect, WC_Square_Sync_To_Square $to_square, WC_Square_Sync_From_Square $from_square ) {
19
+
20
+ $this->connect = $connect;
21
+ $this->wc_to_square = $to_square;
22
+ $this->square_to_wc = $from_square;
23
+
24
+ add_action( 'wp_ajax_square_to_wc', array( $this, 'square_to_wc_ajax' ) );
25
+ add_action( 'wp_ajax_wc_to_square', array( $this, 'wc_to_square_ajax' ) );
26
+ }
27
+
28
+ /**
29
+ * Process Square to WC ajax
30
+ *
31
+ * @since 1.0.0
32
+ * @version 1.0.14
33
+ * @return bool
34
+ */
35
+ public function square_to_wc_ajax() {
36
+ check_ajax_referer( 'square-sync', 'security' );
37
+
38
+ /**
39
+ * Fires if a valid bulk Square to WC sync is being processed.
40
+ *
41
+ * @since 1.0.0
42
+ */
43
+ do_action( 'woocommerce_square_bulk_syncing_square_to_wc' );
44
+
45
+ $settings = get_option( 'woocommerce_squareconnect_settings' );
46
+ $emails = ! empty( $settings['sync_email'] ) ? explode( ',', str_replace( ' ', '', $settings['sync_email'] ) ) : '';
47
+
48
+ $sync_products = ( 'yes' === $settings['sync_products'] );
49
+ $sync_categories = ( 'yes' === $settings['sync_categories'] );
50
+ $sync_inventory = ( 'yes' === $settings['sync_inventory'] );
51
+ $sync_images = ( 'yes' === $settings['sync_images'] );
52
+ $cache_age = apply_filters( 'woocommerce_square_syncing_square_ids_cache', DAY_IN_SECONDS );
53
+ $message = '';
54
+
55
+ if ( ! $sync_products ) {
56
+ wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'square-to-wc', 'message' => __( 'Product Sync is disabled. Sync aborted.', 'woocommerce-square' ) ) );
57
+ }
58
+
59
+ // we need to check for cURL
60
+ if ( ! function_exists( 'curl_init' ) ) {
61
+ wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'square-to-wc', 'message' => __( 'cURL is not available. Sync aborted. Please contact your host to install cURL.', 'woocommerce-square' ) ) );
62
+ }
63
+
64
+ // if a WC to Square process still needs to be completed reset the caches
65
+ // as the two processes ( WC -> Square and Square -> WC ) use the same cache
66
+ if ( 'wc_to_square' === get_transient( 'sq_wc_sync_current_process' ) ) {
67
+
68
+ $this->connect->delete_all_caches();
69
+
70
+ }
71
+
72
+ // set Square->WC as the current active process
73
+ set_transient( 'sq_wc_sync_current_process', 'square_to_wc', $cache_age );
74
+
75
+ // index for the current item in the process
76
+ $process = $this->get_process_index();
77
+
78
+ // only sync categories on the first pass
79
+ if ( ( 0 === $process ) && $sync_categories ) {
80
+
81
+ $this->square_to_wc->sync_categories();
82
+
83
+ }
84
+
85
+ if ( ( 0 === $process ) && $sync_inventory ) {
86
+ // ensure this manual update gets the freshest item counts
87
+ delete_transient( 'wc_square_inventory' );
88
+
89
+ $this->connect->get_square_inventory();
90
+
91
+ }
92
+
93
+ // products
94
+ // get all product ids
95
+ $square_item_ids = $this->get_processing_ids();
96
+
97
+ // run this only on first process
98
+ if ( $process === 0 ) {
99
+ $square_items = $this->connect->get_square_products();
100
+ $square_item_ids = ! empty( $square_items ) ? array_unique( wp_list_pluck( $square_items, 'id' ) ) : array();
101
+
102
+ // cache it
103
+ set_transient( 'wc_square_processing_total_count', count( $square_item_ids ), $cache_age );
104
+ set_transient( 'wc_square_processing_ids', $square_item_ids, $cache_age );
105
+
106
+ }
107
+
108
+ if ( $square_item_ids && $sync_products ) {
109
+
110
+ $square_item_id = array_pop( $square_item_ids );
111
+ $square_item = $this->connect->get_square_product( $square_item_id );
112
+
113
+ if ( $square_item ) {
114
+
115
+ $this->square_to_wc->sync_product( $square_item, $sync_categories, $sync_inventory, $sync_images );
116
+
117
+ } else {
118
+
119
+ WC_Square_Sync_Logger::log( sprintf( '[Square -> WC] Bulk Sync: Error retrieving Square Item with ID %s.', $square_item_id ) );
120
+
121
+ }
122
+
123
+ $process++;
124
+
125
+ $percentage = $this->get_process_percentage( $process );
126
+
127
+ $this->delete_processed_id( $square_item_id );
128
+
129
+ $remaining_ids = $this->get_processing_ids();
130
+
131
+ // run this only on last process
132
+ if ( empty( $remaining_ids ) ) {
133
+ $process = 'done';
134
+
135
+ $percentage = 100;
136
+
137
+ // send sync email
138
+ $this->send_sync_email( $emails, __( 'Sync Completed', 'woocommerce-square' ) );
139
+
140
+ // reset the processed ids
141
+ $this->connect->delete_all_caches();
142
+
143
+ $message = __( 'Sync completed', 'woocommerce-square' );
144
+ }
145
+
146
+ wp_send_json( array( 'process' => $process, 'percentage' => $percentage, 'type' => 'square-to-wc', 'message' => $message ) );
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Process WC to Square ajax
152
+ *
153
+ * @since 1.0.0
154
+ * @version 1.0.0
155
+ * @return bool
156
+ */
157
+ public function wc_to_square_ajax() {
158
+ check_ajax_referer( 'square-sync', 'security' );
159
+
160
+ $settings = get_option( 'woocommerce_squareconnect_settings' );
161
+ $emails = ! empty( $settings['sync_email'] ) ? $settings['sync_email'] : '';
162
+
163
+ $sync_products = ( 'yes' === $settings['sync_products'] );
164
+ $sync_categories = ( 'yes' === $settings['sync_categories'] );
165
+ $sync_inventory = ( 'yes' === $settings['sync_inventory'] );
166
+ $sync_images = ( 'yes' === $settings['sync_images'] );
167
+ $cache_age = apply_filters( 'woocommerce_square_syncing_wc_product_ids_cache', DAY_IN_SECONDS );
168
+ $message = '';
169
+
170
+ if ( ! $sync_products ) {
171
+ wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'wc-to-square', 'message' => __( 'Product Sync is disabled. Sync aborted.', 'woocommerce-square' ) ) );
172
+ }
173
+
174
+ // we need to check for cURL
175
+ if ( ! function_exists( 'curl_init' ) ) {
176
+ wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'wc-to-square', 'message' => __( 'cURL is not available. Sync aborted. Please contact your host to install cURL.', 'woocommerce-square' ) ) );
177
+ }
178
+
179
+ // if a Square to WC process still needs to be completed reset the caches
180
+ // as the two processes ( WC -> Square and Square -> WC ) use the same cache
181
+ if ( 'square_to_wc' === get_transient( 'sq_wc_sync_current_process' ) ) {
182
+
183
+ $this->connect->delete_all_caches();
184
+
185
+ }
186
+
187
+ // set WC->Square as the current active process
188
+ set_transient( 'sq_wc_sync_current_process', 'wc_to_square', $cache_age );
189
+
190
+ $process = $this->get_process_index();
191
+
192
+ // only sync categories on the first pass
193
+ if ( ( 0 === $process ) && $sync_categories ) {
194
+
195
+ $this->wc_to_square->sync_categories();
196
+
197
+ }
198
+
199
+ // products
200
+ // get all product ids
201
+ $wc_product_ids = $this->get_processing_ids();
202
+
203
+ // run the following only on first process and cache it
204
+ if ( ( 0 === $process ) && $sync_products ) {
205
+
206
+ $wc_product_ids = $this->get_all_product_ids();
207
+
208
+ // cache it
209
+ set_transient( 'wc_square_processing_total_count', count( $wc_product_ids ), $cache_age );
210
+ set_transient( 'wc_square_processing_ids', $wc_product_ids, $cache_age );
211
+ }
212
+
213
+ if ( $sync_products && ! empty( $wc_product_ids ) ) {
214
+
215
+ $wc_product_id = array_pop( $wc_product_ids );
216
+
217
+ $wc_product = wc_get_product( $wc_product_id );
218
+
219
+ if ( is_object( $wc_product ) && is_a( $wc_product, 'WC_Product' ) ) {
220
+ $this->wc_to_square->sync_product( $wc_product, $sync_categories, $sync_inventory, $sync_images );
221
+ }
222
+
223
+ $process++;
224
+
225
+ $percentage = $this->get_process_percentage( $process );
226
+
227
+ if ( is_object( $wc_product ) ) {
228
+ $this->delete_processed_id( version_compare( WC_VERSION, '3.0.0', '<' ) ? $wc_product->id : $wc_product->get_id() );
229
+ } else {
230
+ $this->delete_processed_id( $wc_product_id );
231
+ }
232
+
233
+ $remaining_ids = $this->get_processing_ids();
234
+
235
+ // run this only on last process
236
+ if ( empty( $remaining_ids ) ) {
237
+ $process = 'done';
238
+
239
+ $percentage = 100;
240
+
241
+ // send sync email
242
+ $this->send_sync_email( $emails, __( 'Sync Completed', 'woocommerce-square' ) );
243
+
244
+ // reset the processed ids
245
+ $this->connect->delete_all_caches();
246
+
247
+ $message = __( 'Sync completed', 'woocommerce-square' );
248
+ }
249
+
250
+ wp_send_json( array( 'process' => $process, 'percentage' => $percentage, 'type' => 'wc-to-square', 'message' => $message ) );
251
+
252
+ }
253
+
254
+ wp_send_json( array( 'process' => 'done', 'percentage' => 100, 'type' => 'wc-to-square', 'message' => __( 'No Products to Sync.', 'woocommerce-square' ) ) );
255
+ }
256
+
257
+ /**
258
+ * Figure out at which product index we are
259
+ * at using the total count and the remaining item.
260
+ * The index stats at 0 .
261
+ *
262
+ * @since 1.0.0
263
+ *
264
+ * @return int $process_index
265
+ */
266
+ public function get_process_index() {
267
+
268
+ $total_items = (int) get_transient( 'wc_square_processing_total_count' );
269
+ $remaining_ids_count = count( $this->get_processing_ids() );
270
+ $process_index = $total_items - $remaining_ids_count;
271
+
272
+ if ( empty( $process_index ) ) {
273
+ $process_index = 0;
274
+ }
275
+
276
+ return $process_index;
277
+ }
278
+
279
+ /**
280
+ * Gets all product ids that are sync-eligible (they have SKUs).
281
+ *
282
+ * This looks for products as well as variations, if a variant has a SKU, the
283
+ * parent product will be included in the result set.
284
+ *
285
+ * @access public
286
+ * @since 1.0.0
287
+ * @version 1.0.14
288
+ * @return array $ids
289
+ */
290
+ public function get_all_product_ids() {
291
+
292
+ $args = apply_filters( 'woocommerce_square_get_all_product_ids_args', array(
293
+ 'posts_per_page' => -1,
294
+ 'post_type' => array( 'product', 'product_variation' ),
295
+ 'post_status' => 'publish',
296
+ 'fields' => 'id=>parent',
297
+ 'meta_query' => array(
298
+ array(
299
+ 'key' => '_sku',
300
+ 'compare' => '!=',
301
+ 'value' => ''
302
+ )
303
+ )
304
+ ) );
305
+
306
+ $products_with_skus = get_posts( $args );
307
+ $product_ids = array();
308
+
309
+ /*
310
+ * Our result set contains products and variations. We're only concerned with
311
+ * returning top-level products, so favor the parent ID if present (denotes a variation)
312
+ */
313
+ foreach ( $products_with_skus as $product_id => $parent_id ) {
314
+ $post_id = 0 == $parent_id ? $product_id : $parent_id;
315
+
316
+ // check if product sync is disable, if so skip
317
+ if ( WC_Square_Utils::skip_product_sync( $post_id ) ) {
318
+ WC_Square_Sync_Logger::log( sprintf( '[WC -> Square] Syncing disabled for this WC Product %d', $post_id ) );
319
+
320
+ continue;
321
+ }
322
+
323
+ // when it is a variation, we need to check its parent for publish
324
+ // post status.
325
+ if ( 0 == $parent_id ) {
326
+ $product_ids[] = $product_id;
327
+ } else {
328
+ $post = get_post( $parent_id );
329
+
330
+ if ( is_object( $post ) && 'publish' === $post->post_status ) {
331
+ $product_ids[] = $parent_id;
332
+ }
333
+ }
334
+ }
335
+
336
+ /*
337
+ * Products can have multiple variants, so we might end up with
338
+ * duplicate parent product IDs in our list.
339
+ */
340
+ $unique_product_ids = array_unique( $product_ids );
341
+
342
+ return $unique_product_ids;
343
+ }
344
+
345
+ /**
346
+ * Deletes the product ID from the list so we can continue if sync is terminated early.
347
+ * This function can take both the WC product id or Square product ID
348
+ *
349
+ * @access public
350
+ * @since 1.0.0
351
+ * @version 1.0.0
352
+ * @param string $product_id
353
+ * @return bool
354
+ */
355
+ public function delete_processed_id( $product_id = null ) {
356
+
357
+ if ( null === $product_id ) {
358
+ return false;
359
+ }
360
+
361
+ $ids = $this->get_processing_ids();
362
+
363
+ if ( ( $key = array_search( $product_id, $ids ) ) !== false ) {
364
+ unset( $ids[ $key ] );
365
+ }
366
+
367
+ set_transient( 'wc_square_processing_ids', $ids, apply_filters( 'woocommerce_square_sync_processing_ids_cache', DAY_IN_SECONDS ) );
368
+
369
+ return true;
370
+ }
371
+
372
+ /**
373
+ * Gets the already processed product IDs ( both Square and WC )
374
+ *
375
+ * @access public
376
+ * @since 1.0.0
377
+ * @version 1.0.0
378
+ * @return array $ids
379
+ */
380
+ public function get_processing_ids() {
381
+ if ( $ids = get_transient( 'wc_square_processing_ids' ) ) {
382
+ return $ids;
383
+ }
384
+
385
+ return array();
386
+ }
387
+
388
+ /**
389
+ * Get process percentage
390
+ *
391
+ * @access public
392
+ * @since 1.0.0
393
+ * @version 1.0.0
394
+ * @param int $process the current process step
395
+ * @return string $percentage
396
+ */
397
+ public function get_process_percentage( $process ) {
398
+
399
+ $total_count = (int) get_transient( 'wc_square_processing_total_count' );
400
+ $percentage = 0;
401
+
402
+ if ( $total_count > 0 ) {
403
+ $percentage = ( $process / $total_count );
404
+ }
405
+
406
+ if ( 0 === $process ) {
407
+ // 10% is added to offset the category process
408
+ $percentage = $percentage + 0.10;
409
+ }
410
+
411
+ return round( $percentage, 2 ) * 100;
412
+ }
413
+
414
+ /**
415
+ * Sends the sync notification email when operation ends
416
+ *
417
+ * @access public
418
+ * @since 1.0.0
419
+ * @version 1.0.14
420
+ * @param string $emails
421
+ * @param string $message
422
+ * @return bool
423
+ */
424
+ public function send_sync_email( $emails, $message = '' ) {
425
+ // default to admin's email
426
+ if ( empty( $emails ) ) {
427
+ $emails = array();
428
+ $emails[] = get_option( 'admin_email' );
429
+ }
430
+
431
+ $subject = sprintf( __( '%s - WooCommerce Square Sync Operation', 'woocommerce-square' ), wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) );
432
+
433
+ $headers = array();
434
+
435
+ foreach ( $emails as $email ) {
436
+ $headers[] = sprintf( __( '%s', 'woocommerce-square' ) . ' ' . wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) . ' <' . $email . '>', 'From:' ) . PHP_EOL;
437
+
438
+ wp_mail( $email, $subject, $message, $headers );
439
+ }
440
+
441
+ return true;
442
+ }
443
+
444
+ }
includes/class-wc-square-client.php ADDED
@@ -0,0 +1,379 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit; // Exit if accessed directly
4
+ }
5
+
6
+ /**
7
+ * Class WC_Square_Client
8
+ *
9
+ * Makes actual HTTP requests to the Square API.
10
+ * Handles:
11
+ * - Authentication
12
+ * - Endpoint selection (API version, Merchant ID in path)
13
+ * - Request retries
14
+ * - Paginated results
15
+ * - Content-Type negotiation (JSON)
16
+ */
17
+ class WC_Square_Client {
18
+
19
+ /**
20
+ * @var
21
+ */
22
+ protected $access_token;
23
+
24
+ /**
25
+ * @var
26
+ */
27
+ protected $merchant_id;
28
+
29
+ /**
30
+ * @var string
31
+ */
32
+ protected $api_version = 'v1';
33
+
34
+ /**
35
+ * @return mixed
36
+ */
37
+ public function get_access_token() {
38
+
39
+ return $this->access_token;
40
+
41
+ }
42
+
43
+ /**
44
+ * @param $token
45
+ */
46
+ public function set_access_token( $token ) {
47
+
48
+ $this->access_token = $token;
49
+
50
+ }
51
+
52
+ /**
53
+ * @return mixed
54
+ */
55
+ public function get_merchant_id() {
56
+
57
+ return $this->merchant_id;
58
+
59
+ }
60
+
61
+ /**
62
+ * @param $merchant_id
63
+ */
64
+ public function set_merchant_id( $merchant_id ) {
65
+
66
+ $this->merchant_id = $merchant_id;
67
+
68
+ }
69
+
70
+ /**
71
+ * @return string
72
+ */
73
+ public function get_api_version() {
74
+
75
+ return $this->api_version;
76
+
77
+ }
78
+
79
+ /**
80
+ * @param $version
81
+ */
82
+ public function set_api_version( $version ) {
83
+
84
+ $this->api_version = $version;
85
+
86
+ }
87
+
88
+ /**
89
+ * @return int|mixed|void
90
+ */
91
+ public function get_api_url_base() {
92
+ if ( WC_SQUARE_ENABLE_STAGING ) {
93
+ return apply_filters( 'woocommerce_square_api_url', 'https://connect.squareupstaging.com/' );
94
+ }
95
+
96
+ return apply_filters( 'woocommerce_square_api_url', 'https://connect.squareup.com/' );
97
+ }
98
+
99
+ /**
100
+ * @return string
101
+ */
102
+ public function get_api_url() {
103
+
104
+ $url = trailingslashit( $this->get_api_url_base() );
105
+ $url .= trailingslashit( $this->get_api_version() );
106
+
107
+ return $url;
108
+
109
+ }
110
+
111
+ /**
112
+ * Initializes the header arguments.
113
+ *
114
+ * @since 1.0.0
115
+ * @version 1.0.14
116
+ * @return int|mixed|void
117
+ */
118
+ public function get_request_args() {
119
+
120
+ $args = array(
121
+ 'headers' => array(
122
+ 'Authorization' => 'Bearer ' . sanitize_text_field( $this->get_access_token() ),
123
+ 'Accept' => 'application/json',
124
+ 'Content-Type' => 'application/json',
125
+ ),
126
+ 'user-agent' => 'WooCommerceSquare/' . WC_SQUARE_VERSION . '; ' . get_bloginfo( 'url' ),
127
+ 'timeout' => 0,
128
+ 'httpversion' => '1.1',
129
+ );
130
+
131
+ return apply_filters( 'woocommerce_square_request_args', $args );
132
+ }
133
+
134
+ /**
135
+ * @param $path
136
+ *
137
+ * @return string
138
+ */
139
+ protected function get_request_url( $path ) {
140
+
141
+ $api_url_base = trailingslashit( $this->get_api_url() );
142
+ $merchant_id = '';
143
+
144
+ // Add merchant ID to the request URL if we aren't hitting /me/*
145
+ if ( strpos( trim( $path, '/' ), 'me' ) !== 0 ) {
146
+
147
+ $merchant_id = trailingslashit( $this->get_merchant_id() );
148
+
149
+ }
150
+
151
+ $request_path = ltrim( $path, '/' );
152
+ $request_url = trailingslashit( $api_url_base . $merchant_id . $request_path );
153
+
154
+ return $request_url;
155
+
156
+ }
157
+
158
+ /**
159
+ * Gets the number of retries per request
160
+ *
161
+ * @access public
162
+ * @since 1.0.0
163
+ * @version 1.0.0
164
+ * @param int $count
165
+ * @return int
166
+ */
167
+ public function request_retries( $count = 5 ) {
168
+
169
+ return apply_filters( 'woocommerce_square_request_retries', $count );
170
+
171
+ }
172
+
173
+ /**
174
+ * Wrapper around http_request() that handles pagination for List endpoints.
175
+ *
176
+ * @since 1.0.25 Switch to use WP native remote requests.
177
+ * @param string $debug_label Description of the request, for logging.
178
+ * @param string $path API endpoint path to hit. E.g. /items/
179
+ * @param string $method HTTP method to use. Defaults to 'GET'.
180
+ * @param mixed $body Optional. Request payload - will be JSON encoded if non-scalar.
181
+ *
182
+ * @return bool|object|WP_Error
183
+ */
184
+ public function request( $debug_label, $path, $method = 'GET', $body = null ) {
185
+ // we need to check for cURL
186
+ if ( ! function_exists( 'curl_init' ) ) {
187
+ WC_Square_Sync_Logger::log( 'cURL is not available. Sync aborted. Please contact your host to install cURL.' );
188
+
189
+ return false;
190
+ }
191
+
192
+ // The access token is required for all requests
193
+ $access_token = $this->get_access_token();
194
+
195
+ if ( empty( $access_token ) ) {
196
+
197
+ return false;
198
+
199
+ }
200
+
201
+ $request_url = $this->get_request_url( $path );
202
+ $return_data = array();
203
+
204
+ while ( true ) {
205
+
206
+ $response = $this->http_request( $debug_label, $request_url, $method, $body );
207
+
208
+ if ( ! $response ) {
209
+
210
+ return $response;
211
+
212
+ }
213
+
214
+ $response_data = json_decode( wp_remote_retrieve_body( $response ) );
215
+
216
+ // A paged list result will be an array, so let's merge if we're already returning an array
217
+ if ( ( 'GET' === $method ) && is_array( $return_data ) && is_array( $response_data ) ) {
218
+
219
+ $return_data = array_merge( $return_data, $response_data );
220
+
221
+ } else {
222
+
223
+ $return_data = $response_data;
224
+
225
+ }
226
+
227
+ $link_header = wp_remote_retrieve_header( $response, 'Link' );
228
+
229
+ // Look for the next page, if specified
230
+ if ( ! preg_match( '/Link:( |)<(.+)>;rel=("|\')next("|\')/i', $link_header ) ) {
231
+ return $return_data;
232
+ }
233
+
234
+ $rel_link_matches = array();
235
+
236
+ // Set up the next page URL for the following loop
237
+ if ( ( 'GET' === $method ) && preg_match( '/Link:( |)<(.+)>;rel=("|\')next("|\')/i', $link_header, $rel_link_matches ) ) {
238
+
239
+ $request_url = $rel_link_matches[2];
240
+ $body = null;
241
+
242
+ } else {
243
+
244
+ return $return_data;
245
+
246
+ }
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Helper method to make HTTP requests to the Square API, with retries.
252
+ *
253
+ * @since 1.0.25 Switch to use WP native remote requests.
254
+ * @param string $debug_label Description of the request, for logging.
255
+ * @param string $request_url URL to request.
256
+ * @param string $method HTTP method to use. Defaults to 'GET'.
257
+ * @param mixed $body Optional. Request payload - will be JSON encoded if non-scalar.
258
+ *
259
+ * @return bool|object|WP_Error
260
+ */
261
+ private function http_request( $debug_label, $request_url, $method = 'GET', $body = null ) {
262
+ $request_args = $this->get_request_args();
263
+
264
+ if ( ! is_null( $body ) ) {
265
+ if ( ! empty( $request_args['headers']['Content-Type'] ) && ( 'application/json' === $request_args['headers']['Content-Type'] ) ) {
266
+ $request_args['body'] = json_encode( $body );
267
+ } else {
268
+ $request_args['body'] = $body;
269
+ }
270
+ }
271
+
272
+ $request_args['method'] = $method;
273
+
274
+ // Make actual request in a retry loop
275
+ $try_count = 1;
276
+ $max_retries = $this->request_retries();
277
+
278
+ while ( true ) {
279
+ $start_time = current_time( 'timestamp' );
280
+ $response = wp_remote_request( untrailingslashit( $request_url ), $request_args );
281
+ $end_time = current_time( 'timestamp' );
282
+
283
+ WC_Square_Sync_Logger::log( sprintf( '%s', $debug_label ), $start_time, $end_time );
284
+
285
+ $decoded_response = json_decode( wp_remote_retrieve_body( $response ) );
286
+
287
+ if ( is_object( $decoded_response ) && ! empty( $decoded_response->type ) ) {
288
+ if ( preg_match( '/bad_request/', $decoded_response->type ) || preg_match( '/not_found/', $decoded_response->type ) ) {
289
+ WC_Square_Sync_Logger::log( sprintf( '%s - %s', $decoded_response->type, $decoded_response->message ), $start_time, $end_time );
290
+
291
+ return false;
292
+ }
293
+ }
294
+
295
+ // handle expired tokens
296
+ if ( is_object( $decoded_response ) &&
297
+ ( ! empty( $decoded_response->type ) && 'oauth.expired' === $decoded_response->type ) ||
298
+ ( ! empty( $decoded_response->errors ) && 'ACCESS_TOKEN_EXPIRED' === $decoded_response->errors[0]->code ) ) {
299
+
300
+ $oauth_connect_url = 'https://connect.woocommerce.com/renew/square';
301
+
302
+ if ( WC_SQUARE_ENABLE_STAGING ) {
303
+ $oauth_connect_url = 'https://connect.woocommerce.com/renew/squaresandbox';
304
+ }
305
+
306
+ $args = array(
307
+ 'body' => array(
308
+ 'token' => $this->access_token,
309
+ ),
310
+ 'timeout' => 45,
311
+ );
312
+
313
+ $start_time = current_time( 'timestamp' );
314
+ $oauth_response = wp_remote_post( $oauth_connect_url, $args );
315
+ $end_time = current_time( 'timestamp' );
316
+
317
+ $decoded_oauth_response = json_decode( wp_remote_retrieve_body( $oauth_response ) );
318
+
319
+ if ( is_wp_error( $oauth_response ) ) {
320
+
321
+ WC_Square_Sync_Logger::log( sprintf( 'Renewing expired token error - %s', $parsed_oauth_response['curl_error'] ), $start_time, $end_time );
322
+
323
+ return false;
324
+
325
+ } elseif ( is_object( $decoded_oauth_response ) && ! empty( $decoded_oauth_response->error ) ) {
326
+
327
+ WC_Square_Sync_Logger::log( sprintf( 'Renewing expired token error - %s', $decoded_oauth_response->type ), $start_time, $end_time );
328
+
329
+ return false;
330
+
331
+ } elseif ( is_object( $decoded_oauth_response ) && ! empty( $decoded_oauth_response->access_token ) ) {
332
+ update_option( 'woocommerce_square_merchant_access_token', sanitize_text_field( urldecode( $decoded_oauth_response->access_token ) ) );
333
+
334
+ // let's set the token instance again so settings option is refreshed
335
+ $this->set_access_token( sanitize_text_field( urldecode( $decoded_oauth_response->access_token ) ) );
336
+ $request_args['headers']['Authorization'] = 'Bearer ' . sanitize_text_field( $this->get_access_token() );
337
+
338
+ WC_Square_Sync_Logger::log( sprintf( 'Retrying with new refreshed token' ), $start_time, $end_time );
339
+
340
+ // start at the beginning again
341
+ continue;
342
+ } else {
343
+ WC_Square_Sync_Logger::log( sprintf( 'Renewing expired token error - Unknown Error' ), $start_time, $end_time );
344
+
345
+ return false;
346
+ }
347
+ }
348
+
349
+ // handle revoked tokens
350
+ if ( is_object( $decoded_response ) && ! empty( $decoded_response->type ) && 'oauth.revoked' === $decoded_response->type ) {
351
+ WC_Square_Sync_Logger::log( sprintf( 'Token is revoked!' ), $start_time, $end_time );
352
+
353
+ return false;
354
+ }
355
+
356
+ if ( is_wp_error( $response ) ) {
357
+
358
+ WC_Square_Sync_Logger::log( sprintf( '(%s) Try #%d - %s', $debug_label, $try_count, $response->get_error_message() ), $start_time, $end_time );
359
+
360
+ } else {
361
+
362
+ return $response;
363
+
364
+ }
365
+
366
+ $try_count++;
367
+
368
+ if ( $try_count > $max_retries ) {
369
+ break;
370
+ }
371
+
372
+ sleep( 1 );
373
+
374
+ }
375
+
376
+ return false;
377
+
378
+ }
379
+ }
includes/class-wc-square-connect.php ADDED
@@ -0,0 +1,678 @@