PluginProbe ʕ •ᴥ•ʔ
WooCommerce Square / 5.5.0
WooCommerce Square v5.5.0
5.5.0 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 / Handlers / Order.php
woocommerce-square / includes / Handlers Last commit date
Product 2 days ago Async_Request.php 2 years ago Background_Job.php 2 days ago Category.php 3 months ago Connection.php 5 months ago Email.php 1 year ago Order.php 2 days ago Order_Sync.php 11 months ago Product.php 2 days ago Products.php 1 month ago Sync.php 3 years ago
Order.php
1301 lines
1 <?php
2 /**
3 * WooCommerce Square
4 *
5 * This source file is subject to the GNU General Public License v3.0
6 * that is bundled with this package in the file license.txt.
7 * It is also available through the world-wide-web at this URL:
8 * http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
9 * If you did not receive a copy of the license and are unable to
10 * obtain it through the world-wide-web, please send an email
11 * to license@woocommerce.com so we can send you a copy immediately.
12 *
13 * DISCLAIMER
14 *
15 * Do not edit or add to this file if you wish to upgrade WooCommerce Square to newer
16 * versions in the future. If you wish to customize WooCommerce Square for your
17 * needs please refer to https://docs.woocommerce.com/document/woocommerce-square/
18 *
19 * @author WooCommerce
20 * @copyright Copyright: (c) 2019, Automattic, Inc.
21 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
22 */
23
24 namespace WooCommerce\Square\Handlers;
25
26 use WooCommerce\Square\Framework\PaymentGateway\Payment_Gateway;
27 use WooCommerce\Square\Framework\Square_Helper;
28 use WooCommerce\Square\Plugin;
29 use WooCommerce\Square\Handlers\Product;
30 use WooCommerce\Square\Sync\Helper;
31
32 defined( 'ABSPATH' ) || exit;
33
34 /**
35 * Order handler class.
36 *
37 * @since 2.0.0
38 */
39 class Order {
40
41 /**
42 * Array of previous stock values.
43 *
44 * @var array
45 */
46 private $previous_stock = array();
47
48 /**
49 * Array of product IDs that have been scheduled for sync in this request.
50 *
51 * @var array
52 */
53 private $products_to_sync = array();
54
55 /**
56 * Array of payment gateways that are Square payment gateways.
57 *
58 * @var array
59 */
60 private $square_payment_gateways = array(
61 Plugin::GATEWAY_ID,
62 Plugin::CASH_APP_PAY_GATEWAY_ID,
63 );
64
65 /**
66 * Sets up Square order handler.
67 *
68 * @since 2.0.0
69 */
70 public function __construct() {
71 add_action( 'wp', array( $this, 'save_guest_details' ) );
72 // remove Square variation IDs from order item meta
73 add_action( 'woocommerce_hidden_order_itemmeta', array( $this, 'hide_square_order_item_meta' ) );
74
75 // Add hooks for stock sync.
76 add_action( 'woocommerce_reduce_order_item_stock', array( $this, 'maybe_stage_stock_updates_for_product' ), 10, 3 );
77 add_action( 'woocommerce_reduce_order_stock', array( $this, 'maybe_sync_staged_inventory_updates' ) );
78
79 // ADD hooks to restore stock for pending and cancelled order status.
80 add_action( 'woocommerce_order_status_cancelled', array( $this, 'maybe_sync_inventory_for_stock_increase' ), 1 );
81 add_action( 'woocommerce_order_status_pending', array( $this, 'maybe_sync_inventory_for_stock_increase' ), 1 );
82
83 // ADD hooks to listen to refunds on orders from other gateways.
84 add_action( 'woocommerce_order_refunded', array( $this, 'maybe_sync_stock_for_refund_from_other_gateway' ), 10, 2 );
85
86 // Add gift card order item to the order edit screen.
87 add_action( 'woocommerce_admin_order_items_after_fees', array( $this, 'add_admin_order_items' ) );
88
89 // Include gift card information in payment method info.
90 add_filter( 'woocommerce_order_get_payment_method_title', array( $this, 'filter_payment_method_title' ), 10, 2 );
91 add_filter( 'woocommerce_gateway_title', array( $this, 'filter_gateway_title' ), 10, 2 );
92
93 // Add Gift Card "send-to" email address to the order meta.
94 add_action( 'woocommerce_checkout_create_order_line_item', array( $this, 'add_gift_card_add_to_cart_details_to_order' ), 10, 4 );
95 add_action( 'woocommerce_add_order_again_cart_item', array( $this, 'reorder_gift_card' ) );
96 add_filter( 'woocommerce_order_item_display_meta_key', array( $this, 'modify_gift_card_line_item_key' ), 10, 3 );
97 add_action( 'wc_square_gift_card_activated', array( $this, 'trigger_email_for_gift_card_sent' ) );
98 add_filter( 'woocommerce_hidden_order_itemmeta', array( $this, 'hide_gift_card_line_item_meta' ) );
99 add_filter( 'woocommerce_get_order_item_totals', array( $this, 'filter_order_item_totals' ), 10, 2 );
100 add_action( 'woocommerce_admin_order_data_after_billing_address', array( $this, 'render_admin_missing_billing_details_notice' ) );
101 add_action( 'before_woocommerce_pay_form', array( $this, 'render_missing_billing_details_notice' ), 10, 1 );
102 add_filter( 'woocommerce_order_email_verification_required', array( $this, 'no_verification_on_guest_save' ), 10, 3 );
103 }
104
105 /**
106 * Ensures the Square order item meta is hidden.
107 *
108 * @since 2.0.0
109 *
110 * @param string[] $hidden the hidden order item meta
111 * @return string[] updated meta
112 */
113 public function hide_square_order_item_meta( $hidden ) {
114
115 $hidden[] = '_square_item_variation_id';
116
117 return $hidden;
118 }
119
120 /**
121 * Add hooks to ensure PayPal IPN callbacks are added caches and considered for inventory changes
122 * when the sync happens. This also adds the shutdown hook to ensure sync happens if needed at
123 * a later stage.
124 *
125 * @since 2.1.1
126 * @deprecated 5.4.2 No longer used; stock sync is handled via woocommerce_reduce_order_stock on WC 7.6+.
127 *
128 * @param array $posted values returned from PayPal Standard IPN callback.
129 */
130 public function maybe_sync_stock_for_order_via_paypal( $posted ) {
131 wc_deprecated_function( __METHOD__, '5.4.2' );
132 if ( empty( $posted['custom'] ) ) {
133 return;
134 }
135
136 $raw_order = json_decode( $posted['custom'] );
137 if ( empty( $raw_order->order_id ) ) {
138 return;
139 }
140
141 $order = wc_get_order( $raw_order->order_id );
142
143 if ( ! $order || ! $order instanceof \WC_Order ) {
144 return;
145 }
146
147 $this->sync_stock_for_order( $order );
148 }
149
150 /**
151 * Checks if we should sync stock for this order.
152 * We only sync for other gateways that Square will not be aware of.
153 *
154 * This functions sets a process in motion that gathers products that will be processed on shutdown.
155 *
156 * @since 2.0.8
157 * @deprecated 5.4.2 No longer used; stock sync is handled via woocommerce_reduce_order_stock on WC 7.6+.
158 *
159 * @param int $order_id Order ID number.
160 * @param array $posted_data Submitted order data.
161 * @param WC_Order $order Order object.
162 */
163 public function maybe_sync_stock_for_order_via_other_gateway( $order_id, $posted_data, $order ) {
164 wc_deprecated_function( __METHOD__, '5.4.2' );
165 // Confirm we are not processing the order through the Square gateway.
166 if ( ! $order instanceof \WC_Order || in_array( $order->get_payment_method(), $this->square_payment_gateways, true ) ) {
167 return;
168 }
169
170 $this->sync_stock_for_order( $order );
171 }
172
173 /**
174 * Checks if we should sync stock for this order.
175 * We only sync for other gateways that Square will not be aware of.
176 *
177 * This functions sets a process in motion that gathers products that will be processed on shutdown.
178 *
179 * @since 4.0.0
180 * @deprecated 5.4.2 No longer used; stock sync is handled via woocommerce_reduce_order_stock on WC 7.6+.
181 *
182 * @param \WC_Order $order Order object.
183 */
184 public function maybe_sync_stock_for_store_api_order_via_other_gateway( $order ) {
185 wc_deprecated_function( __METHOD__, '5.4.2' );
186 // Confirm we are not processing the order through the Square gateway.
187 if ( ! $order instanceof \WC_Order || in_array( $order->get_payment_method(), $this->square_payment_gateways, true ) ) {
188 return;
189 }
190
191 $this->sync_stock_for_order( $order );
192 }
193
194 /**
195 * For a given order sync stock if inventory sync is enabled.
196 *
197 * @since 2.1.1
198 *
199 * @param \WC_Order $order the order for which the stock must be synced.
200 */
201 protected function sync_stock_for_order( $order ) {
202
203 if ( ! wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
204 return;
205 }
206
207 $this->cache_previous_stock( $order );
208
209 add_action( 'woocommerce_product_set_stock', array( $this, 'maybe_stage_inventory_updates_for_product' ) );
210 add_action( 'woocommerce_variation_set_stock', array( $this, 'maybe_stage_inventory_updates_for_product' ) );
211
212 add_action( 'shutdown', array( $this, 'maybe_sync_staged_inventory_updates' ) );
213 }
214
215 /**
216 * Loop through order and cached previous stock values before they are reduced.
217 *
218 * @since 2.0.8
219 *
220 * @param WC_Order $order Order object.
221 */
222 private function cache_previous_stock( $order ) {
223
224 // Loop over all items.
225 foreach ( $order->get_items() as $item ) {
226 if ( ! $item->is_type( 'line_item' ) ) {
227 continue;
228 }
229
230 // Check to make sure it hasn't already been reduced.
231 $product = $item->get_product();
232 $item_stock_reduced = $item->get_meta( '_reduced_stock', true );
233
234 if ( $item_stock_reduced || ! $product || ! $product->managing_stock() ) {
235 continue;
236 }
237
238 $this->previous_stock[ $product->get_id() ] = $product->get_stock_quantity();
239 }
240 }
241
242 /**
243 * Stages a product inventory update for sync with Square when a product stock is updated.
244 *
245 * @internal The staged values will be stored in product_to_sync
246 *
247 * @since 2.0.8
248 *
249 * @param WC_Product $product the updated product with inventory updates.
250 */
251 public function maybe_stage_inventory_updates_for_product( $product ) {
252
253 // Do not add inventory changes if we are already doing a sync, or we are not syncing this product.
254 if ( defined( 'DOING_SQUARE_SYNC' ) || ! $product || ! Product::is_synced_with_square( $product ) ) {
255 return;
256 }
257
258 // Compare stock to get difference.
259 $product_id = $product->get_id();
260 $previous = isset( $this->previous_stock[ $product_id ] ) ? $this->previous_stock[ $product_id ] : false;
261 $current = $product->get_stock_quantity();
262 $adjustment = (int) $current - $previous;
263
264 if ( false === $previous || 0 === $adjustment ) {
265 return;
266 }
267
268 // Record what type of inventory action occurred.
269 $this->products_to_sync[ $product_id ] = $adjustment;
270 }
271
272 /**
273 * Stage inventory updates for products in the order.
274 * This function only used for WooCommerce 7.6 and above.
275 *
276 * @param \WC_Order_Item_Product $item Order item data.
277 * @param array $change Change Details.
278 * @param \WC_Order $order Order data.
279 * @return void
280 *
281 * @since 4.1.0
282 */
283 public function maybe_stage_stock_updates_for_product( $item, $change, $order ) {
284 $product = $change['product'] ?? false;
285
286 /**
287 * Bail If
288 * 1. Order is processed using Square payment gateway OR
289 * 2. Inventory sync is not enabled OR
290 * 3. Square sync is in progress OR
291 * 4. Square sync is not enabled for the product
292 */
293 if (
294 ! $order instanceof \WC_Order ||
295 in_array( $order->get_payment_method(), $this->square_payment_gateways, true ) ||
296 ! wc_square()->get_settings_handler()->is_inventory_sync_enabled() ||
297 defined( 'DOING_SQUARE_SYNC' ) ||
298 ! $product ||
299 ! Product::is_synced_with_square( $product )
300 ) {
301 return;
302 }
303
304 // Get stock adjustment for the product.
305 $product_id = $product->get_id();
306 $previous = $change['from'] ?? false;
307 $current = $change['to'] ?? false;
308 $adjustment = (int) $current - $previous;
309
310 if ( false === $previous || false === $current || 0 === $adjustment ) {
311 return;
312 }
313
314 // Stage the inventory update.
315 $this->products_to_sync[ $product_id ] = $adjustment;
316 }
317
318 /**
319 * Maybe restore stock for an order that was cancelled or moved to pending.
320 * This is only for orders that were not processed through the Square gateway.
321 *
322 * @param int $order_id Order ID.
323 *
324 * @since 4.1.0
325 */
326 public function maybe_sync_inventory_for_stock_increase( $order_id ) {
327 if ( ! wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
328 return;
329 }
330
331 // Confirm we are not processing the order through the Square gateway.
332 $order = wc_get_order( $order_id );
333 if ( ! $order instanceof \WC_Order || in_array( $order->get_payment_method(), $this->square_payment_gateways, true ) ) {
334 return;
335 }
336
337 $trigger_increase = $order->get_order_stock_reduced();
338
339 // Only continue if we're increasing stock.
340 if ( ! $trigger_increase ) {
341 return;
342 }
343
344 // Cache the product's previous stock value.
345 add_action( 'woocommerce_variation_before_set_stock', array( $this, 'cache_product_previous_stock' ) );
346 add_action( 'woocommerce_product_before_set_stock', array( $this, 'cache_product_previous_stock' ) );
347
348 // Stage the inventory update.
349 add_action( 'woocommerce_variation_set_stock', array( $this, 'maybe_stage_inventory_updates_for_product' ) );
350 add_action( 'woocommerce_product_set_stock', array( $this, 'maybe_stage_inventory_updates_for_product' ) );
351
352 // Sync the staged inventory updates.
353 add_action( 'woocommerce_restore_order_stock', array( $this, 'maybe_sync_staged_inventory_updates' ) );
354 }
355
356 /**
357 * Cache the product's previous stock value.
358 *
359 * @param WC_Product $product Product object.
360 *
361 * @since 4.1.0
362 */
363 public function cache_product_previous_stock( $product ) {
364 $this->previous_stock[ $product->get_id() ] = $product->get_stock_quantity();
365 }
366
367 /**
368 * Initializes a synchronization event for any staged inventory updates in this request.
369 *
370 * @internal
371 *
372 * @since 2.0.8
373 */
374 /**
375 * Returns the Square catalog objects for these products that Square has never counted.
376 *
377 * A relative adjustment is only meaningful once Square holds a count, so callers use this to
378 * decide when to send an absolute count instead. An unavailable answer returns an empty list, so
379 * the caller keeps the existing adjustment behaviour rather than guessing.
380 *
381 * @since 5.5.0
382 *
383 * @param int[] $product_ids WooCommerce product IDs.
384 * @return string[] catalog object IDs with no inventory history.
385 */
386 private static function get_objects_without_inventory_history( array $product_ids ) {
387
388 $square_ids = array();
389
390 foreach ( $product_ids as $product_id ) {
391 $square_id = Product::get_square_item_variation_id( $product_id, false );
392
393 if ( $square_id ) {
394 $square_ids[] = $square_id;
395 }
396 }
397
398 if ( ! $square_ids ) {
399 return array();
400 }
401
402 $with_history = Helper::get_catalog_objects_with_inventory_history( $square_ids );
403
404 if ( null === $with_history ) {
405 return array();
406 }
407
408 return array_values( array_diff( $square_ids, $with_history ) );
409 }
410
411
412 public function maybe_sync_staged_inventory_updates() {
413
414 $inventory_adjustments = array();
415
416 // An order sends a relative adjustment, which assumes Square already holds a count for the
417 // item. When it does not, and the merchant enabled inventory sync after the item was created,
418 // the delta becomes Square's whole count and the two sides drift apart immediately: a sale of
419 // one leaves WooCommerce at 19 and Square at -1. For those objects send WooCommerce's current
420 // quantity as an absolute count instead, which establishes the baseline; every later order can
421 // then adjust from it normally.
422 $never_counted = self::get_objects_without_inventory_history( array_keys( $this->products_to_sync ) );
423
424 foreach ( $this->products_to_sync as $product_id => $adjustment ) {
425
426 $product = wc_get_product( $product_id );
427 if ( ! $product instanceof \WC_Product ) {
428 continue;
429 }
430
431 $square_id = Product::get_square_item_variation_id( $product_id, false );
432
433 if ( $square_id && in_array( $square_id, $never_counted, true ) ) {
434 $inventory_adjustment = Product::get_inventory_change_physical_count_type( $product );
435 } else {
436 $inventory_adjustment = Product::get_inventory_change_adjustment_type( $product, $adjustment );
437 }
438
439 if ( empty( $inventory_adjustment ) ) {
440 continue;
441 }
442
443 $inventory_adjustments[] = $inventory_adjustment;
444 }
445
446 if ( empty( $inventory_adjustments ) ) {
447 return;
448 }
449
450 wc_square()->log( 'New order from other gateway inventory syncing..' );
451 $idempotency_key = wc_square()->get_idempotency_key( md5( serialize( $inventory_adjustments ) ) . '_change_inventory' );
452
453 // This runs inside checkout and stock-reduction hooks for orders paid through OTHER
454 // gateways, so a Square API failure (e.g. an expired connection throwing an auth
455 // exception) must never bubble up - it would abort the payment flow mid transition and
456 // let the gateway mark an already paid order as failed. Log and continue instead; stock
457 // converges on the next successful sync.
458 try {
459 wc_square()->get_api()->batch_change_inventory( $idempotency_key, $inventory_adjustments );
460 } catch ( \Exception $exception ) {
461 wc_square()->log(
462 sprintf(
463 'Square inventory sync failed for order from other gateway (order flow left untouched, %d adjustment(s) skipped): %s',
464 count( $inventory_adjustments ),
465 $exception->getMessage()
466 )
467 );
468 } finally {
469 // Always reset, or the shutdown hook re-attempts the same failing call in this request.
470 $this->products_to_sync = array();
471 }
472 }
473
474 /**
475 * Handle order refunds inventory/stock changes sync.
476 *
477 * @since 2.0.8
478 *
479 * @param in $order_id
480 * @param int $refund_id
481 */
482 public function maybe_sync_stock_for_refund_from_other_gateway( $order_id, $refund_id ) {
483
484 if ( ! wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
485 return;
486 }
487
488 // Confirm we are not processing the order through the Square gateway.
489 $order = wc_get_order( $order_id );
490 if ( ! $order instanceof \WC_Order || in_array( $order->get_payment_method(), $this->square_payment_gateways, true ) ) {
491 return;
492 }
493
494 // don't refund items if the "Restock refunded items" option is unchecked - maintains backwards compatibility if this function is called outside of the `woocommerce_order_refunded` do_action
495 if ( isset( $_POST['restock_refunded_items'] ) ) {
496 // Validate the user has permissions to process this request.
497 if ( ! check_ajax_referer( 'order-item', 'security', false ) || ! current_user_can( 'edit_shop_orders' ) ) {
498 return;
499 }
500
501 if ( 'false' === $_POST['restock_refunded_items'] ) {
502 return;
503 }
504 }
505
506 $refund = new \WC_Order_Refund( $refund_id );
507 $inventory_adjustments = array();
508 foreach ( $refund->get_items() as $item ) {
509
510 if ( 'line_item' !== $item->get_type() ) {
511 continue;
512 }
513
514 $product = $item->get_product();
515 if ( ! $product instanceof \WC_Product ) {
516 continue;
517 }
518
519 $adjustment = -1 * ( $item->get_quantity() ); // we want a positive value to increase the stock and a negative number to decrease it.
520 $inventory_adjustment = Product::get_inventory_change_adjustment_type( $product, $adjustment );
521
522 if ( empty( $inventory_adjustment ) ) {
523 continue;
524 }
525
526 $inventory_adjustments[] = $inventory_adjustment;
527 }
528
529 if ( empty( $inventory_adjustments ) ) {
530 return;
531 }
532
533 wc_square()->log( 'Order from other gateway Refund inventory updates syncing..' );
534 $idempotency_key = wc_square()->get_idempotency_key( md5( serialize( $inventory_adjustments ) ) . '_change_inventory' );
535
536 // Same protection as the checkout path: a Square API failure during a refund of an order
537 // paid through another gateway must not abort the refund flow. Log and continue.
538 try {
539 wc_square()->get_api()->batch_change_inventory( $idempotency_key, $inventory_adjustments );
540 } catch ( \Exception $exception ) {
541 wc_square()->log(
542 sprintf(
543 'Square inventory sync failed for refund #%d of order #%d from other gateway (refund flow left untouched): %s',
544 $refund_id,
545 $order_id,
546 $exception->getMessage()
547 )
548 );
549 }
550 }
551
552 /**
553 * Add gift card recipient data to order line item meta.
554 *
555 * @since 4.2.0
556 *
557 * @param \WC_Order_Item_Product $item The order item product.
558 * @param string $cart_item_key The cart item key.
559 * @param array $values Values associated with a cart item key.
560 * @param \WC_Order $order Woo Order.
561 */
562 public function add_gift_card_add_to_cart_details_to_order( $item, $cart_item_key, $values, $order ) {
563 $product = wc_get_product( $values['product_id'] );
564
565 if ( ! $product instanceof \WC_Product ) {
566 return;
567 }
568
569 // Return if the product is not a gift card product.
570 if ( ! Product::is_gift_card( $product ) ) {
571 return;
572 }
573
574 // Add meta if a new gift card is purchased.
575 if ( ! empty( $values['square-gift-card-send-to-email'] ) ) {
576 if ( ! empty( $values['square-gift-card-sender-name'] ) ) {
577 $item->add_meta_data( 'square-gift-card-sender-name', $values['square-gift-card-sender-name'] );
578 }
579
580 $item->add_meta_data( 'square-gift-card-send-to-email', $values['square-gift-card-send-to-email'] );
581 $item->add_meta_data( '_square-gift-card-purchase-type', 'new' );
582
583 if ( ! empty( $values['square-gift-card-sent-to-first-name'] ) ) {
584 $item->add_meta_data( 'square-gift-card-sent-to-first-name', $values['square-gift-card-sent-to-first-name'] );
585 }
586
587 if ( ! empty( $values['square-gift-card-sent-to-message'] ) ) {
588 $item->add_meta_data( 'square-gift-card-sent-to-message', $values['square-gift-card-sent-to-message'] );
589 }
590 }
591
592 // Add meta if an existing gift card is loaded with amount.
593 if ( ! empty( $values['square-gift-card-gan'] ) ) {
594 $item->add_meta_data( 'square-gift-card-gan', $values['square-gift-card-gan'] );
595 $item->add_meta_data( '_square-gift-card-purchase-type', 'load' );
596 }
597
598 // Fallback when gift card product is added to card directly from the /shop page which
599 // bypasses adding recipient email address, so we default to purchasing a new gift card
600 // without a recipient email address.
601 if ( empty( $values['square-gift-card-send-to-email'] ) && empty( $values['square-gift-card-gan'] ) ) {
602 $item->add_meta_data( '_square-gift-card-purchase-type', 'new' );
603 }
604 }
605
606 /**
607 * Adds gift card related order item meta to the order again cart item.
608 *
609 * @param array $cart_item_data Cart item data.
610 *
611 * @return array
612 */
613 public function reorder_gift_card( $cart_item_data ) {
614 // Disabling PHPCS check as nonce verification is done in the parent method.
615 $order_id = absint( wp_unslash( $_GET['order_again'] ?? 0 ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
616
617 if ( ! $order_id ) {
618 return $cart_item_data;
619 }
620
621 $order = wc_get_order( $order_id );
622 $order_items = $order->get_items();
623
624 if ( empty( $order_items ) ) {
625 return $cart_item_data;
626 }
627
628 $product = wc_get_product( $cart_item_data['product_id'] );
629
630 if ( ! Product::is_gift_card( $product ) ) {
631 return $cart_item_data;
632 }
633
634 /** @var \WC_Order_Item $order_item */
635 foreach ( $order_items as $order_item ) {
636 $order_item_data = $order_item->get_data();
637 $product = wc_get_product( $order_item_data['product_id'] );
638
639 if ( ! Product::is_gift_card( $product ) ) {
640 continue;
641 }
642
643 $cart_item_data['square-gift-card-send-to-email'] = $order_item->get_meta( 'square-gift-card-send-to-email' );
644 $cart_item_data['square-gift-card-sender-name'] = $order_item->get_meta( 'square-gift-card-sender-name' );
645 $cart_item_data['square-gift-card-sent-to-first-name'] = $order_item->get_meta( 'square-gift-card-sent-to-first-name' );
646 $cart_item_data['square-gift-card-sent-to-message'] = $order_item->get_meta( 'square-gift-card-sent-to-message' );
647 }
648
649 return $cart_item_data;
650 }
651
652 /**
653 * Filters the meta key into a human-readable format.
654 *
655 * @since 4.2.0
656 *
657 * @param string $display_key The value that will be displayed in the order line item.
658 * @param \WC_Meta_Data $meta The order line item meta data object.
659 * @param \WC_Order_Item_Product $order_item Instance of the order item product.
660 *
661 * @return string
662 */
663 public function modify_gift_card_line_item_key( $display_key, $meta, $order_item ) {
664 if ( 'square-gift-card-sender-name' === $meta->key ) {
665 return __( "Sender's name", 'woocommerce-square' );
666 }
667
668 if ( 'square-gift-card-send-to-email' === $meta->key ) {
669 return __( "Recipient's email", 'woocommerce-square' );
670 }
671
672 if ( 'square-gift-card-sent-to-first-name' === $meta->key ) {
673 return __( "Recipient's name", 'woocommerce-square' );
674 }
675
676 if ( 'square-gift-card-sent-to-message' === $meta->key ) {
677 return __( 'Message', 'woocommerce-square' );
678 }
679
680 if ( 'square-gift-card-gan' === $meta->key ) {
681 return __( 'Gift card number', 'woocommerce-square' );
682 }
683
684 return $display_key;
685 }
686
687 /**
688 * Triggers an email to the recipient of the gift card.
689 *
690 * @since 4.2.0
691 *
692 * @param \WC_Order $order WooCommerce order.
693 */
694 public function trigger_email_for_gift_card_sent( $order ) {
695 wc_square()->get_email_handler()->get_gift_card_sent()->trigger( $order );
696 }
697
698 /**
699 * Hides the meta that indicates the type of gift card purchase,
700 * new or reload.
701 *
702 * @since 4.2.0
703 *
704 * @param array $meta_array Array of line order item meta.
705 * @return array
706 */
707 public function hide_gift_card_line_item_meta( $meta_array ) {
708 $meta_array[] = '_square-gift-card-purchase-type';
709
710 return $meta_array;
711 }
712
713 /**
714 * Returns the type of gift card purchase type - `new` or `load`.
715 *
716 * - `new` indicates a new gift card is purchased.
717 * - `load` indicates an existing gift card is loaded with additional funds.
718 *
719 * @since 4.2.0
720 *
721 * @param \WC_Order $order WooCommerce order.
722 *
723 * @return string|boolean Types of gift card purchase, false otherwise.
724 */
725 public static function get_gift_card_purchase_type( $order ) {
726 $order_items = $order->get_items();
727
728 /** @var \WC_Order_Item_Product $order_item */
729 foreach ( $order_items as $order_item ) {
730 $purchase_type = $order_item->get_meta( '_square-gift-card-purchase-type' );
731
732 if ( ! empty( $purchase_type ) ) {
733 return $purchase_type;
734 }
735 }
736
737 return false;
738 }
739
740 /**
741 * Returns the gift card number from the order line item.
742 *
743 * @since 4.2.0
744 *
745 * @param \WC_Order $order WooCommerce order.
746 *
747 * @return string|boolean The gift card number, false otherwise.
748 */
749 public static function get_gift_card_gan( $order ) {
750 $order_items = $order->get_items();
751
752 /** @var \WC_Order_Item_Product $order_item */
753 foreach ( $order_items as $order_item ) {
754 $gan = $order_item->get_meta( 'square-gift-card-gan' );
755
756 if ( ! empty( $gan ) ) {
757 return $gan;
758 }
759 }
760
761 return false;
762 }
763
764 /**
765 * Returns if the order was placed using a Square credit card.
766 *
767 * @since 3.7.0
768 *
769 * @param \WC_Order $order WooCommerce order.
770 *
771 * @return boolean
772 */
773 public static function is_tender_type_card( $order ) {
774 return '1' === wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'is_tender_type_card' );
775 }
776
777 /**
778 * @since 3.7.0
779 *
780 * @param \WC_Order $order WooCommerce order.
781 *
782 * @return boolean
783 */
784 public static function is_tender_type_gift_card( $order ) {
785 return '1' === wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'is_tender_type_gift_card' );
786 }
787
788 /**
789 * Returns if the order was placed using a Square cash app pay.
790 *
791 * @since 4.6.0
792 *
793 * @param \WC_Order $order WooCommerce order.
794 *
795 * @return boolean
796 */
797 public static function is_tender_type_cash_app_pay( $order ) {
798 return '1' === wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'is_tender_type_cash_app_wallet' );
799 }
800
801 /**
802 * Sets the amount charged on the gift card for the given order.
803 *
804 * @since 3.7.0
805 *
806 * @param \WC_Order $order WooCommerce order.
807 * @param float $amount The total amount charged on the gift card for the order.
808 */
809 public static function set_gift_card_total_charged_amount( $order, $amount ) {
810 wc_square()->get_gateway( $order->get_payment_method() )->update_order_meta( $order, 'gift_card_charged_amount', $amount );
811 }
812
813 /**
814 * Returns the amount charged on the gift card for the given order.
815 *
816 * @since 3.7.0
817 *
818 * @param \WC_Order $order WooCommerce order.
819 *
820 * @return float
821 */
822 public static function get_gift_card_total_charged_amount( $order ) {
823 return (float) wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'gift_card_charged_amount' );
824 }
825
826 /**
827 * Returns the last 4 digits of the gift card.
828 *
829 * @since 3.7.0
830 *
831 * @param \WC_Order $order WooCommerce order.
832 *
833 * @return string
834 */
835 public static function get_gift_card_last4( $order ) {
836 return wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'gift_card_last4' );
837 }
838
839 /**
840 * Sets the last 4 digits of the gift card.
841 *
842 * @since 3.7.0
843 *
844 * @param \WC_Order $order WooCommerce order.
845 *
846 * @return string
847 */
848 public static function set_gift_card_last4( $order, $number ) {
849 return wc_square()->get_gateway( $order->get_payment_method() )->update_order_meta( $order, 'gift_card_last4', $number );
850 }
851
852 /**
853 * Returns the total amount that is refunded to the gift card.
854 *
855 * @since 3.7.0
856 *
857 * @param \WC_Order $order WooCommerce order.
858 *
859 * @return float
860 */
861 public static function get_gift_card_total_refunded_amount( $order ) {
862 $amount = (float) wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'gift_card_refunded_amount' );
863 $amount = empty( $amount ) ? 0 : $amount;
864
865 return $amount;
866 }
867
868 /**
869 * Sets the total amount that is refunded to the gift card.
870 *
871 * @since 3.7.0
872 *
873 * @param \WC_Order $order WooCommerce order.
874 * @param float $amount The total amount refunded to the gift card for the order.
875 */
876 public static function set_gift_card_total_refunded_amount( $order, $amount ) {
877 wc_square()->get_gateway( $order->get_payment_method() )->update_order_meta( $order, 'gift_card_refunded_amount', $amount );
878 }
879
880 /**
881 * Sets the total order amount before applying the gift card.
882 *
883 * @since 3.7.0
884 *
885 * @param \WC_Order $order WooCommerce order.
886 * @param float $amount
887 */
888 public static function set_order_total_before_gift_card( $order, $amount ) {
889 wc_square()->get_gateway( $order->get_payment_method() )->update_order_meta( $order, 'order_total_before_gift_card', $amount );
890 }
891
892 /**
893 * Gets the total order amount before applying the gift card.
894 *
895 * @since 3.7.0
896 *
897 * @return float
898 */
899 public static function get_order_total_before_gift_card( $order ) {
900 return (float) wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'order_total_before_gift_card' );
901 }
902
903 /**
904 * Displays the gift card details in the order item.
905 *
906 * @param int $order_id WooCommerce order ID.
907 */
908 public function add_admin_order_items( $order_id ) {
909 $order = wc_get_order( $order_id );
910
911 if ( ! self::is_tender_type_gift_card( $order ) ) {
912 return;
913 }
914
915 ?>
916 <tr class="square_gift_card item">
917 <td class="thumb">
918 <div style="width: 38px;">
919 <img src="<?php echo esc_url( WC_SQUARE_PLUGIN_URL . '/build/images/gift-card.png' ); /* phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage */ ?>" />
920 </div>
921 </td>
922 <td class="name">
923 <div class="view">
924 </div>
925 <div class="view">
926 <table cellspacing="0" class="display_meta">
927 <tbody>
928 <tr>
929 <th>
930 <?php esc_html_e( 'Square Gift Card:', 'woocommerce-square' ); ?>
931 </th>
932 <td>
933 <?php
934 printf(
935 /* Translators: %s - last 4 digits of the gift card. */
936 esc_html__( 'ending in %s', 'woocommerce-square' ),
937 esc_html( self::get_gift_card_last4( $order ) )
938 );
939 ?>
940 </td>
941 </tr>
942 </tbody>
943 </table>
944 </div>
945 </td>
946 <td class="item_cost" width="1%">&nbsp;</td>
947 <td class="quantity" width="1%">&nbsp;</td>
948 <td class="line_cost" width="1%">
949 <div class="view">-
950 <?php
951 echo wp_kses_post( wc_price( self::get_gift_card_total_charged_amount( $order ), array( 'currency' => $order->get_currency() ) ) );
952 $refunded_amount = self::get_gift_card_total_refunded_amount( $order );
953 ?>
954 </div>
955 </td>
956 <td class="wc-order-edit-line-item" width="1%">
957 </tr>
958 <?php
959 }
960
961 /**
962 * Includes info regarding gift card in the payment method title.
963 *
964 * @since 3.7.0
965 *
966 * @param string $value Payment method title.
967 * @param \WC_Order $order WooCommerce order.
968 *
969 * @return string
970 */
971 public function filter_payment_method_title( $value, $order ) {
972 $is_tender_gift_card = self::is_tender_type_gift_card( $order );
973 $is_tender_card = self::is_tender_type_card( $order );
974 $is_tender_cash_app = self::is_tender_type_cash_app_pay( $order );
975
976 if ( $is_tender_gift_card && ( $is_tender_card || $is_tender_cash_app ) ) {
977 $gateway = wc_square()->get_gateway( $order->get_payment_method() );
978 $gift_card_charged = $gateway->get_order_meta( $order, 'gift_card_partial_total' );
979 $other_gateway_charged = $gateway->get_order_meta( $order, 'other_gateway_partial_total' );
980 if ( empty( $other_gateway_charged ) ) {
981 // Backward compatibility.
982 $other_gateway_charged = $gateway->get_order_meta( $order, 'credit_card_partial_total' );
983 }
984
985 $payment_method = '';
986
987 if ( $is_tender_card ) {
988 $payment_method = esc_html__( 'Credit Card', 'woocommerce-square' );
989 } elseif ( $is_tender_cash_app ) {
990 $payment_method = esc_html__( 'Cash App Pay', 'woocommerce-square' );
991 }
992
993 return sprintf(
994 /* translators: %1$s - Amount charged on gift card, %2$s - Amount charged on credit card. */
995 __( 'Square Gift Card (%1$s) and %2$s (%3$s)', 'woocommerce-square' ),
996 get_woocommerce_currency_symbol( $order->get_currency() ) . Square_Helper::number_format( $gift_card_charged ),
997 $payment_method ?? '',
998 get_woocommerce_currency_symbol( $order->get_currency() ) . Square_Helper::number_format( $other_gateway_charged )
999 );
1000 }
1001
1002 if ( $is_tender_gift_card ) {
1003 return sprintf(
1004 /* translators: %1$s - Amount charged on gift card. */
1005 __( 'Square Gift Card (%1$s)', 'woocommerce-square' ),
1006 get_woocommerce_currency_symbol( $order->get_currency() ) . $order->get_total()
1007 );
1008 }
1009
1010 return $value;
1011 }
1012
1013 /**
1014 * Includes info regarding gift card in the payment gateway title.
1015 *
1016 * @since 3.7.0
1017 *
1018 * @param string $value Gateway title.
1019 * @param string $id Plugin id.
1020 *
1021 * @return string
1022 */
1023 public function filter_gateway_title( $value, $id ) {
1024 $plugin_gateways = wc_square()->get_gateway_ids() ?? array();
1025 if ( ! in_array( $id, $plugin_gateways, true ) || ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
1026 return $value;
1027 }
1028
1029 $screen = get_current_screen();
1030
1031 if ( ! ( $screen && 'shop_order' === $screen->id ) ) {
1032 return $value;
1033 }
1034
1035 if ( ! isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1036 return $value;
1037 }
1038
1039 $post_id = wc_clean( absint( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1040 $order = wc_get_order( $post_id );
1041
1042 if ( ! $order instanceof \WC_Order ) {
1043 return $value;
1044 }
1045
1046 return self::filter_payment_method_title( $value, $order );
1047 }
1048
1049 /**
1050 * Includes split payment details on the order details screen.
1051 *
1052 * @param array $total_rows Array of order details.
1053 * @param \WC_Order $order WooCommerce Order.
1054 *
1055 * @return array
1056 */
1057 public function filter_order_item_totals( $total_rows, $order ) {
1058 $charge_type = wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'charge_type' );
1059
1060 if ( Payment_Gateway::CHARGE_TYPE_PARTIAL !== $charge_type ) {
1061 return $total_rows;
1062 }
1063
1064 $gift_card_partial_total = wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'gift_card_partial_total' );
1065 $other_gateway_partial_total = wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'other_gateway_partial_total' );
1066 if ( empty( $other_gateway_partial_total ) ) {
1067 // Backward compatibility.
1068 $other_gateway_partial_total = wc_square()->get_gateway( $order->get_payment_method() )->get_order_meta( $order, 'credit_card_partial_total' );
1069 }
1070
1071 $total_rows['order_total']['value'] = sprintf(
1072 /* translators: %1$s - Order total, %2$s - Amount charged on gift card, %3$s - Other payment method %4$s - Amount charged on credit card/cash app pay. */
1073 esc_html__( '%1$s — Total split between gift card (%2$s) and %3$s (%4$s)', 'woocommerce-square' ),
1074 wp_kses_post( $total_rows['order_total']['value'] ),
1075 wp_kses_post( wc_price( $gift_card_partial_total, array( 'currency' => $order->get_currency() ) ) ),
1076 'square_cash_app_pay' === $order->get_payment_method() ? esc_html__( 'cash app pay', 'woocommerce-square' ) : esc_html__( 'credit card', 'woocommerce-square' ),
1077 wp_kses_post( wc_price( $other_gateway_partial_total, array( 'currency' => $order->get_currency() ) ) )
1078 );
1079
1080 return $total_rows;
1081 }
1082
1083 /**
1084 * Renders a notice if the billing country is not set in manual order.
1085 *
1086 * @since 4.2.0
1087 * @param \WC_Order $order
1088 */
1089 public function render_admin_missing_billing_details_notice( $order ) {
1090 $created_via = $order->get_created_via();
1091
1092 if ( $order->is_paid() ) {
1093 return;
1094 }
1095
1096 if ( ! ( '' === $created_via || 'admin' === $created_via ) ) {
1097 return;
1098 }
1099
1100 $billing_country = $order->get_billing_country();
1101
1102 if ( ! empty( $billing_country ) ) {
1103 return;
1104 }
1105
1106 ?>
1107 <p style="color: #b32d2e; display: none;" class="form-field form-field-wide square-billing-details-info"><?php esc_html_e( 'Billing country is a mandatory field for Square payment gateway.', 'woocommerce-square' ); ?></p>
1108 <?php
1109 }
1110
1111 /**
1112 * Renders a notice if the billing country is not set in manual order.
1113 *
1114 * @since 4.2.0
1115 *
1116 * @param \WC_Order $order
1117 */
1118 public function render_missing_billing_details_notice( $order ) {
1119 if ( is_user_logged_in() ) {
1120 return;
1121 }
1122
1123 if ( ! $order instanceof \WC_Order ) {
1124 return;
1125 }
1126
1127 if ( ! empty( $order->get_billing_country() ) ) {
1128 return;
1129 }
1130
1131 require_once WC()->plugin_path() . '/includes/admin/wc-meta-box-functions.php';
1132
1133 ?>
1134 <div style="display: none;" id="square-pay-for-order-billing-details-wrapper">
1135 <?php
1136 wc_print_notice( __( 'Billing country is not set which is required for payment using Square. Please update the billing country before continuing.', 'woocommerce-square' ), 'notice', array( 'error-scope' => 'square-billing-not-set' ) );
1137 ?>
1138
1139 <form action="" method="POST">
1140 <h3><?php esc_html_e( 'Update billing details:', 'woocommerce-square' ); ?></h2>
1141 <div id="order_data">
1142 <?php
1143 foreach ( WC()->countries->get_address_fields( '', 'billing_' ) as $key => $field ) {
1144 if ( 'billing_email' === $key ) {
1145 continue;
1146 }
1147
1148 if ( is_callable( array( $order, 'get_' . $key ) ) ) {
1149 $current_value = $order->{"get_$key"}( 'edit' );
1150 } else {
1151 $current_value = $order->get_meta( '_' . $key );
1152 }
1153
1154 woocommerce_form_field( $key, $field, $current_value );
1155 }
1156
1157 wp_nonce_field(
1158 $this->get_save_guest_details_nonce_action( $order->get_id() ),
1159 'woocommerce_square_save_guest_details'
1160 );
1161 ?>
1162
1163 <input type="submit" name="wc-square-save-guest-details" value="<?php esc_html_e( 'Save details', 'woocommerce-square' ); ?>">
1164 </div>
1165 </form>
1166 </div>
1167 <?php
1168 }
1169
1170 /**
1171 * Saves billing details of a guest user on the Pay for order page.
1172 *
1173 * Requires a valid order-pay context, matching order key, guest order, and
1174 * an order-specific nonce.
1175 *
1176 * @since 4.2.0
1177 * @since 5.4.3 Require order key + order-specific nonce; null-guard order.
1178 */
1179 public function save_guest_details() {
1180 if ( ! isset( $_POST['wc-square-save-guest-details'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1181 return;
1182 }
1183
1184 $order_id = absint( get_query_var( 'order-pay' ) );
1185 $order = $order_id ? wc_get_order( $order_id ) : false;
1186
1187 if ( ! $this->can_save_guest_billing_details( $order ) ) {
1188 return;
1189 }
1190
1191 $props = array();
1192
1193 foreach ( WC()->countries->get_address_fields( '', 'billing_' ) as $key => $field ) {
1194 if ( 'billing_email' === $key ) {
1195 continue;
1196 }
1197
1198 if ( is_callable( array( $order, 'set_' . $key ) ) ) {
1199 $props[ $key ] = isset( $_POST[ $key ] ) ? wc_clean( wp_unslash( $_POST[ $key ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1200 } else {
1201 $order->update_meta_data( $key, isset( $_POST[ $key ] ) ? wc_clean( wp_unslash( $_POST[ $key ] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1202 }
1203 }
1204
1205 $order->set_props( $props );
1206 $order->add_order_note(
1207 esc_html__( 'Customer has updated their billing details from the Pay for order page.', 'woocommerce-square' ),
1208 0,
1209 true
1210 );
1211 $order->save();
1212 }
1213
1214 /**
1215 * Disables email verification when billing details are updated by a guest on
1216 * the pay for order page — only for an authorized save request.
1217 *
1218 * @since 4.2.0
1219 * @since 5.4.3 Scope bypass to order-key + nonce verified requests.
1220 *
1221 * @param bool $email_verification_required If email verification is required.
1222 * @param \WC_Order $order Order being viewed/paid.
1223 * @param string $context Verification context.
1224 *
1225 * @return bool
1226 */
1227 public function no_verification_on_guest_save( $email_verification_required, $order = null, $context = '' ) {
1228 if ( ! isset( $_POST['wc-square-save-guest-details'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1229 return $email_verification_required;
1230 }
1231
1232 if ( 'order-pay' !== $context || ! $this->can_save_guest_billing_details( $order ) ) {
1233 return $email_verification_required;
1234 }
1235
1236 return false;
1237 }
1238
1239 /**
1240 * Nonce action for saving guest billing details for a specific order.
1241 *
1242 * @since 5.4.3
1243 *
1244 * @param int $order_id Order ID.
1245 * @return string
1246 */
1247 private function get_save_guest_details_nonce_action( $order_id ) {
1248 return 'wc_square_save_guest_details_' . absint( $order_id );
1249 }
1250
1251 /**
1252 * Whether the current request may save guest billing details for an order.
1253 *
1254 * Authorization mirrors WooCommerce pay-for-order: the order key in the URL
1255 * is the capability token for guest orders. Capability alone is insufficient
1256 * because guests receive pay_for_order for any order with no customer user.
1257 *
1258 * @since 5.4.3
1259 *
1260 * @param mixed $order Order object or false.
1261 * @return bool
1262 */
1263 private function can_save_guest_billing_details( $order ) {
1264 if ( ! $order instanceof \WC_Order ) {
1265 return false;
1266 }
1267
1268 if ( ! $order->needs_payment() ) {
1269 return false;
1270 }
1271
1272 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1273 $order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : '';
1274
1275 if ( '' === $order_key || ! $order->key_is_valid( $order_key ) ) {
1276 return false;
1277 }
1278
1279 // This form is guest-only (see render_missing_billing_details_notice).
1280 if ( $order->get_user_id() ) {
1281 return false;
1282 }
1283
1284 // Ensure the order matches the order-pay endpoint being requested.
1285 $query_order_id = absint( get_query_var( 'order-pay' ) );
1286 if ( ! $query_order_id || $query_order_id !== $order->get_id() ) {
1287 return false;
1288 }
1289
1290 $nonce = isset( $_POST['woocommerce_square_save_guest_details'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
1291 ? wc_clean( wp_unslash( $_POST['woocommerce_square_save_guest_details'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1292 : '';
1293
1294 if ( ! $nonce || ! wp_verify_nonce( $nonce, $this->get_save_guest_details_nonce_action( $order->get_id() ) ) ) {
1295 return false;
1296 }
1297
1298 return true;
1299 }
1300 }
1301