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