PluginProbe ʕ •ᴥ•ʔ
WooCommerce Square / 5.4.3
WooCommerce Square v5.4.3
5.4.3 5.4.2 5.4.1 5.4.0 trunk 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.7.0 2.8.0 2.9.0 2.9.1 3.0.0 3.0.1 3.0.2 3.0.3 3.1.0 3.2.0 3.3.0 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.7.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 4.0.0 4.1.0 4.2.0 4.2.1 4.2.2 4.2.3 4.3.0 4.3.1 4.3.2 4.4.0 4.4.1 4.4.2 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.6.2 4.6.3 4.6.4 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.8.4 4.8.5 4.8.6 4.8.7 4.8.8 4.9.0 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 4.9.6 4.9.7 4.9.8 4.9.9 5.0.0 5.0.1 5.1.0 5.1.1 5.1.2 5.2.0 5.3.0 5.3.1 5.3.2 5.3.3
woocommerce-square / includes / Sync / Order_Polling.php
woocommerce-square / includes / Sync Last commit date
Records 3 months ago Catalog_Item.php 9 months ago Helper.php 1 year ago Interval_Polling.php 6 months ago Job.php 2 years ago Manual_Synchronization.php 1 month ago Order_Importer.php 11 months ago Order_Mapper.php 11 months ago Order_Polling.php 11 months ago Product_Import.php 2 months ago Records.php 2 years ago Stepped_Job.php 2 years ago
Order_Polling.php
335 lines
1 <?php
2 /**
3 * Square Order Polling.
4 *
5 * Handles scheduled polling to sync orders from Square to WooCommerce.
6 *
7 * @package WooCommerce\Square\Sync
8 * @since 5.0.0
9 */
10
11 namespace WooCommerce\Square\Sync;
12
13 defined( 'ABSPATH' ) || exit;
14
15 /**
16 * Order Polling Class.
17 *
18 * @since 5.0.0
19 */
20 class Order_Polling {
21 /**
22 * Initialize the polling system.
23 *
24 * @since 5.0.0
25 */
26 public function __construct() {
27 // Add action to schedule polling.
28 add_action( 'init', array( $this, 'maybe_schedule_polling' ) );
29
30 // Add action to poll square orders via Action Scheduler.
31 add_action( WC_SQUARE_SYNC_ORDERS_EVENT_HOOK, array( $this, 'poll_square_orders' ) );
32 }
33
34 /**
35 * Maybe schedule the polling Action Scheduler job.
36 *
37 * @since 5.0.0
38 */
39 public function maybe_schedule_polling() {
40 if ( false === as_next_scheduled_action( WC_SQUARE_SYNC_ORDERS_EVENT_HOOK, array(), wc_square()->get_id() ) ) {
41 $this->schedule_polling();
42 }
43 }
44
45 /**
46 * Schedule the polling Action Scheduler job.
47 *
48 * @since 5.0.0
49 */
50 public function schedule_polling() {
51 $interval = $this->get_polling_interval_seconds();
52
53 as_schedule_recurring_action( time() + $interval, $interval, WC_SQUARE_SYNC_ORDERS_EVENT_HOOK, array(), wc_square()->get_id() );
54
55 wc_square()->log( "Scheduled Square order polling with interval: {$interval} seconds", 'sync' );
56 }
57
58 /**
59 * Poll Square for new orders.
60 *
61 * @since 5.0.0
62 */
63 public function poll_square_orders() {
64 // Capture sync start time to avoid missing orders updated during sync.
65 $sync_start_time = gmdate( 'c' );
66
67 wc_square()->log( 'Starting Square order polling', 'sync' );
68
69 try {
70 $orders = $this->fetch_recent_square_orders( $sync_start_time );
71
72 if ( empty( $orders ) ) {
73 wc_square()->log( 'No new Square orders found during polling', 'sync' );
74 // Update polling time to sync start time to avoid missing orders.
75 $this->update_last_polling_time( $sync_start_time );
76 return;
77 }
78
79 $this->process_square_orders( $orders );
80
81 // Update polling time to sync start time to avoid missing orders.
82 $this->update_last_polling_time( $sync_start_time );
83
84 wc_square()->log( 'Square order polling completed.', 'sync' );
85
86 } catch ( \Exception $e ) {
87 wc_square()->log( 'Square order polling failed: ' . $e->getMessage(), 'sync' );
88 // Don't update polling time on failure.
89 }
90 }
91
92 /**
93 * Fetch recent Square orders.
94 *
95 * @since 5.0.0
96 * @param string $sync_start_time Optional sync start time for bounded time window.
97 * @return array Array of Square order objects.
98 */
99 private function fetch_recent_square_orders( $sync_start_time = null ) {
100 $settings_handler = wc_square()->get_settings_handler();
101 $access_token = $settings_handler->get_access_token();
102 $location_id = $settings_handler->get_location_id();
103 $is_sandbox = $settings_handler->is_sandbox();
104
105 if ( empty( $access_token ) || empty( $location_id ) ) {
106 wc_square()->log( 'Square API credentials not configured for order polling', 'sync' );
107 return array();
108 }
109
110 $api = new \WooCommerce\Square\Gateway\API( $access_token, $location_id, $is_sandbox );
111
112 // Get orders since last polling time.
113 $last_polling_time = $this->get_last_polling_time();
114 $adjusted_start_time = gmdate( 'c', strtotime( $last_polling_time ) + 1 ); // To avoid re-processing the same orders.
115 $orders = $this->search_square_orders_since( $api, $adjusted_start_time, $sync_start_time );
116
117 return $orders;
118 }
119
120 /**
121 * Search Square orders since a specific time with bounded time window.
122 *
123 * @since 5.0.0
124 * @param \WooCommerce\Square\Gateway\API $api API instance.
125 * @param string $since_time ISO 8601 timestamp.
126 * @param string $sync_start_time Optional sync start time for bounded time window.
127 * @return array Array of Square order objects.
128 */
129 private function search_square_orders_since( $api, $since_time, $sync_start_time = null ) {
130 $end_time = isset( $sync_start_time ) ? $sync_start_time : gmdate( 'c' );
131 wc_square()->log( "Searching Square orders with bounded time window: {$since_time} to {$end_time}", 'sync' );
132
133 try {
134 $settings_handler = wc_square()->get_settings_handler();
135 $location_id = $settings_handler->get_location_id();
136
137 $all_orders = array();
138 $cursor = '';
139 $batch_count = 0;
140 $max_batches = 10; // Prevent infinite loops.
141
142 do {
143 // Use the API's search_orders method with bounded time window.
144 $response = $api->search_orders( array( $location_id ), $since_time, 100, $cursor, $end_time );
145
146 if ( ! empty( $response['orders'] ) ) {
147 $all_orders = array_merge( $all_orders, $response['orders'] );
148
149 wc_square()->log(
150 sprintf(
151 'Batch %d: Found %d orders',
152 $batch_count + 1,
153 count( $response['orders'] ),
154 ),
155 'sync'
156 );
157 }
158
159 // Update cursor for next iteration.
160 $cursor = $response['cursor'] ?? '';
161 ++$batch_count;
162
163 } while ( ! empty( $cursor ) && $batch_count < $max_batches );
164
165 wc_square()->log(
166 sprintf(
167 'Total found: %d Square orders',
168 count( $all_orders )
169 ),
170 'sync'
171 );
172
173 return $all_orders;
174
175 } catch ( \Exception $e ) {
176 wc_square()->log( 'Error searching Square orders: ' . $e->getMessage(), 'error' );
177 return array();
178 }
179 }
180
181 /**
182 * Process Square orders to create or update WooCommerce orders.
183 *
184 * @since 5.0.0
185 * @param array $square_orders Array of Square order objects.
186 */
187 private function process_square_orders( $square_orders ) {
188 if ( empty( $square_orders ) ) {
189 wc_square()->log( 'No Square orders to process', 'sync' );
190 return;
191 }
192
193 $updated_count = 0;
194 $skipped_count = 0;
195 $error_count = 0;
196
197 $importer = new Order_Importer();
198
199 foreach ( $square_orders as $square_order ) {
200 $order_id = $square_order->getId();
201
202 try {
203 // Check if order already exists in WooCommerce.
204 $existing_order = $importer->find_existing_wc_order_by_square_order_id( $order_id );
205
206 if ( $existing_order ) {
207 // Update existing order.
208 $update_result = $importer->update_existing_woocommerce_order( $existing_order, $square_order );
209
210 if ( $update_result['updated'] ) {
211 wc_square()->log(
212 sprintf(
213 'Successfully updated WooCommerce order: Square ID %s -> WC ID %d (%s)',
214 $order_id,
215 $existing_order->get_id(),
216 $update_result['message']
217 ),
218 'sync'
219 );
220 ++$updated_count;
221 } else {
222 wc_square()->log(
223 sprintf(
224 'No updates needed for order: Square ID %s -> WC ID %d (%s)',
225 $order_id,
226 $existing_order->get_id(),
227 $update_result['message']
228 ),
229 'sync'
230 );
231 ++$skipped_count;
232 }
233 } else {
234 // Order doesn't exist in WooCommerce - skip for now.
235 wc_square()->log(
236 sprintf(
237 'Skipping Square order %s - no corresponding WooCommerce order found',
238 $order_id
239 ),
240 'sync'
241 );
242 ++$skipped_count;
243 }
244 } catch ( \Exception $e ) {
245 wc_square()->log(
246 sprintf(
247 'Error processing Square order %s: %s',
248 $order_id,
249 $e->getMessage()
250 ),
251 'error'
252 );
253 ++$error_count;
254
255 // Add order note and meta tag if order exists.
256 if ( isset( $existing_order ) && $existing_order instanceof \WC_Order ) {
257 $existing_order->add_order_note(
258 sprintf(
259 'Error processing Square order %s: %s',
260 $order_id,
261 $e->getMessage()
262 )
263 );
264 $existing_order->update_meta_data( '_square_sync_status', 'error' );
265 }
266 }
267 }
268
269 wc_square()->log(
270 sprintf(
271 'Order processing complete: %d updated, %d skipped, %d errors',
272 $updated_count,
273 $skipped_count,
274 $error_count
275 ),
276 'sync'
277 );
278 }
279
280 /**
281 * Get polling interval in seconds.
282 *
283 * @since 5.0.0
284 * @return int
285 */
286 private function get_polling_interval_seconds() {
287 /**
288 * Filters the polling interval in seconds for Square orders.
289 *
290 * @since 5.0.0
291 * @param int $interval The polling interval in seconds.
292 * @return int The polling interval in seconds.
293 */
294 return apply_filters( 'wc_square_order_polling_interval_seconds', 15 * MINUTE_IN_SECONDS );
295 }
296
297 /**
298 * Get last polling time.
299 *
300 * @since 5.0.0
301 * @return string ISO 8601 timestamp.
302 */
303 private function get_last_polling_time() {
304 $last_time = get_option( 'wc_square_last_order_polling_time' );
305 $seconds_past = filter_input( INPUT_GET, 'seconds_past', FILTER_VALIDATE_INT );
306
307 // If the secondsPast parameter is set, reset the last polling time to the number of seconds past.
308 if ( $seconds_past ) {
309 $last_time = gmdate( 'c', time() - intval( $seconds_past ) );
310 }
311
312 if ( ! $last_time ) {
313 // Default to 24 hours ago for first run.
314 $last_time = gmdate( 'c', time() - DAY_IN_SECONDS );
315 }
316
317 return $last_time;
318 }
319
320 /**
321 * Update last polling time.
322 *
323 * @since 5.0.0
324 * @param string $timestamp Optional timestamp to set. Defaults to current time.
325 */
326 private function update_last_polling_time( $timestamp = null ) {
327 if ( null === $timestamp ) {
328 $timestamp = gmdate( 'c' );
329 }
330
331 update_option( 'wc_square_last_order_polling_time', $timestamp );
332 wc_square()->log( "Updated last polling time to: {$timestamp}", 'sync' );
333 }
334 }
335