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