PluginProbe ʕ •ᴥ•ʔ
WooCommerce Square / 3.8.1
WooCommerce Square v3.8.1
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 / Gateway / Digital_Wallet.php
woocommerce-square / includes / Gateway Last commit date
API 3 years ago Templates 3 years ago API.php 3 years ago Blocks_Handler.php 3 years ago Card_Handler.php 3 years ago Customer_Helper.php 3 years ago Digital_Wallet.php 3 years ago Gift_Card.php 3 years ago Payment_Form.php 3 years ago
Digital_Wallet.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
20 namespace WooCommerce\Square\Gateway;
21
22 defined( 'ABSPATH' ) || exit;
23
24 use WooCommerce\Square\Plugin;
25
26 class Digital_Wallet {
27
28 /**
29 * @var Gateway $gateway
30 */
31 public $gateway = null;
32
33 /**
34 * @var String $page - Current page
35 */
36 public $page = null;
37
38 /**
39 * @var bool $is_available - Is Apple Pay and Google Pay available
40 */
41 public $is_available = null;
42
43 /**
44 * @var string just a simple text, 'via WooCommerce'.
45 */
46 public $total_label_suffix;
47
48 /**
49 * Setup the Digital Wallet class
50 *
51 * @since 2.3
52 */
53 public function __construct( $gateway ) {
54 $this->gateway = $gateway;
55
56 if ( 'yes' === $gateway->get_option( 'enabled', 'no' ) && $this->is_digital_wallet_enabled() ) {
57 add_action( 'wp', array( $this, 'init' ) );
58 add_action( 'admin_notices', array( $this, 'admin_notices' ) );
59 }
60
61 if ( is_admin() && ( $gateway->get_plugin()->is_gateway_settings() || $gateway->get_plugin()->is_plugin_settings() ) ) {
62 add_action( 'init', array( $this, 'apple_pay_domain_registration' ), 11 );
63 }
64
65 // WC AJAX
66 add_action( 'wc_ajax_square_digital_wallet_get_payment_request', array( $this, 'ajax_get_payment_request' ) );
67 add_action( 'wc_ajax_square_digital_wallet_add_to_cart', array( $this, 'ajax_add_to_cart' ) );
68 add_action( 'wc_ajax_square_digital_wallet_recalculate_totals', array( $this, 'ajax_recalculate_totals' ) );
69 add_action( 'wc_ajax_square_digital_wallet_process_checkout', array( $this, 'ajax_process_checkout' ) );
70
71 // Calculate the value of option `wc_square_apple_pay_enabled` which is not stored in the DB for WC Admin inbox notifications
72 add_filter( 'pre_option_wc_square_apple_pay_enabled', array( $this, 'get_option_is_apple_pay_enabled' ), 10, 1 );
73 add_filter( 'woocommerce_checkout_posted_data', array( $this, 'filter_posted_data' ) );
74 add_filter( 'woocommerce_checkout_fields', array( $this, 'filter_checkout_fields' ) );
75 }
76
77 /**
78 * Initialize the digital wallet class
79 *
80 * @since 2.3
81 */
82 public function init() {
83
84 /**
85 * Filter to update the 'via WooCommerce' text.
86 *
87 * 'woocommerce_square_payment_request_total_label_suffix' is the filter hook.
88 * 'via WooCommerce' is the value being filtered.
89 *
90 * @since 3.2
91 */
92 $total_label_suffix = apply_filters( 'woocommerce_square_payment_request_total_label_suffix', __( 'via WooCommerce', 'woocommerce-square' ) );
93 $this->total_label_suffix = $total_label_suffix ? " ($total_label_suffix)" : '';
94
95 $is_user_logged_in = is_user_logged_in();
96 $is_registration_required = WC()->checkout->is_registration_required();
97 $is_registration_enabled = WC()->checkout->is_registration_enabled();
98
99 $available_pages = $this->get_available_pages();
100
101 if ( ( $is_user_logged_in || ! $is_registration_required ) && in_array( 'product', $available_pages, true ) ) {
102 add_action( 'woocommerce_after_add_to_cart_quantity', array( $this, 'render_button' ) );
103 }
104
105 if ( ( $is_user_logged_in || ! $is_registration_required ) && in_array( 'cart', $available_pages, true ) ) {
106 add_action( 'woocommerce_proceed_to_checkout', array( $this, 'render_button' ) );
107 }
108
109 if ( ( $is_user_logged_in || ! $is_registration_required || $is_registration_enabled ) && in_array( 'checkout', $available_pages, true ) ) {
110 add_action( 'woocommerce_before_checkout_form', array( $this, 'render_button' ), 15 );
111 }
112
113 if ( ( $is_user_logged_in || ! $is_registration_required || $is_registration_enabled ) && in_array( 'checkout', $available_pages, true ) && is_wc_endpoint_url( 'order-pay' ) ) {
114 add_action( 'before_woocommerce_pay', array( $this, 'render_button' ) );
115 }
116
117 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
118 }
119
120 /**
121 * Display admin notices related to Square digital wallets
122 *
123 * @since 2.3
124 * @return void
125 */
126 public function admin_notices() {
127
128 // Apple Pay notices - Only shown when digital wallets are enabled and Apple isn't in list of hidden button options
129 if ( ! in_array( 'apple', $this->gateway->get_option( 'digital_wallets_hide_button_options', array() ), true ) ) {
130 $apple_pay_verification_file_location = $this->apple_pay_verification_file_location();
131
132 // Verification file is missing
133 if ( ! empty( $apple_pay_verification_file_location ) && ! file_exists( $apple_pay_verification_file_location ) ) {
134 wc_square()->get_admin_notice_handler()->add_admin_notice(
135 sprintf(
136 /* Translators: %1$s: expected location of apple pay verification file, %2$s: opening href tag with link to Square documentation, %3$s: closing href tag */
137 __( 'Apple Pay is not available with Square. We cannot confirm the Apple Pay domain verification file is at the expected location: %1$s. For more information, please read our documentation on %2$sSetting up Apple Pay%3$s.', 'woocommerce-square' ),
138 '<code>' . $apple_pay_verification_file_location . '</code>',
139 '<a href="https://docs.woocommerce.com/document/woocommerce-square/">',
140 '</a>'
141 ),
142 'wc-square-apple-pay-file-missing',
143 array(
144 'notice_class' => 'notice-warning',
145 )
146 );
147
148 } elseif ( 'no' === $this->gateway->get_option( 'apple_pay_domain_registered', '' ) ) {
149 // Domain failed to register
150 wc_square()->get_admin_notice_handler()->add_admin_notice(
151 sprintf(
152 /* Translators: %1$s: opening bold tags, %2$s: closing strong/bold tags, %3$s: expected location of apple pay verification file, %4$s: opening href tag with link to Square documentation, %5$s: closing href tag */
153 __( 'Apple Pay is not available with Square - there was a problem with registering your store domain with Square/Apple Pay. %1$sView the Square logs%2$s to find out what caused the registration to fail.', 'woocommerce-square' ),
154 '<a href="' . esc_url( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) . '">',
155 '</a>'
156 ),
157 'wc-square-apple-pay-domain-registered',
158 array(
159 'notice_class' => 'notice-warning',
160 )
161 );
162 }
163 }
164 }
165
166 /**
167 * Render the Digital Wallet buttons (Apple Pay) on the Product, Cart or Checkout pages
168 *
169 * @since 2.3
170 * @return void
171 */
172 public function render_button() {
173
174 $apple_pay_classes = $google_pay_classes = array( 'wc-square-wallet-buttons' );
175 $button_type = $this->gateway->get_option( 'digital_wallets_button_type', 'buy' );
176 $apple_button_style = $this->gateway->get_option( 'digital_wallets_apple_pay_button_color', 'black' );
177
178 // set button text
179 switch ( $button_type ) {
180 case 'donate':
181 case 'buy':
182 $button_text = ucfirst( $button_type ) . ' with';
183 $apple_pay_classes[] = 'wc-square-wallet-button-with-text';
184 break;
185
186 default:
187 $button_text = '';
188 }
189
190 $apple_pay_classes[] = 'wc-square-wallet-button-' . $apple_button_style;
191
192 ?>
193 <div id="wc-square-digital-wallet" style="display:none;">
194 <div id="apple-pay-button" class="apple-pay-button <?php echo esc_attr( implode( ' ', array_map( 'sanitize_html_class', $apple_pay_classes ) ) ); ?>" lang="<?php echo esc_attr( substr( get_locale(), 0, 2 ) ); ?>" style="-apple-pay-button-type: <?php echo esc_attr( $button_type ); ?>; -apple-pay-button-style: <?php echo esc_attr( $apple_button_style ); ?>">
195 <span class="text"><?php echo esc_html( $button_text ); ?></span>
196 <span class="logo"></span>
197 </div>
198
199 <div id="wc-square-google-pay" lang="<?php echo esc_attr( substr( get_locale(), 0, 2 ) ); ?>"></div>
200 <p id="wc-square-wallet-divider">&ndash; <?php esc_html_e( 'OR', 'woocommerce-square' ); ?> &ndash;</p>
201 </div>
202 <?php
203 }
204
205 /**
206 * Load Square wallet scripts and styles
207 *
208 * @since 2.3
209 * @return void
210 */
211 public function enqueue_scripts() {
212 $page = $this->get_current_page();
213
214 if ( ! $page || ! $this->is_available() ) {
215 return;
216 }
217
218 wp_enqueue_style( 'wc-square-digital-wallet', $this->gateway->get_plugin()->get_plugin_url() . '/assets/css/frontend/wc-square-digital-wallet.min.css', array(), Plugin::VERSION );
219 wp_enqueue_script( 'wc-square-digital-wallet', $this->gateway->get_plugin()->get_plugin_url() . '/assets/js/frontend/wc-square-digital-wallet.min.js', array( 'jquery' ), Plugin::VERSION, true );
220
221 try {
222 /**
223 * Hook to filter localized data for digital wallets.
224 *
225 * @param array Array of data for digital wallets.
226 * @since 2.3
227 */
228 $args = apply_filters(
229 'wc_square_digital_wallet_js_args',
230 array(
231 'application_id' => $this->gateway->get_application_id(),
232 'location_id' => wc_square()->get_settings_handler()->get_location_id(),
233 'gateway_id' => $this->gateway->get_id(),
234 'gateway_id_dasherized' => $this->gateway->get_id_dasherized(),
235 'payment_request' => $this->get_payment_request_for_context( $page ),
236 'context' => $page,
237 'general_error' => __( 'An error occurred, please try again or try an alternate form of payment.', 'woocommerce-square' ),
238 'ajax_url' => \WC_AJAX::get_endpoint( '%%endpoint%%' ),
239 'payment_request_nonce' => wp_create_nonce( 'wc-square-get-payment-request' ),
240 'add_to_cart_nonce' => wp_create_nonce( 'wc-square-add-to-cart' ),
241 'recalculate_totals_nonce' => wp_create_nonce( 'wc-square-recalculate-totals' ),
242 'process_checkout_nonce' => wp_create_nonce( 'woocommerce-process_checkout' ),
243 'logging_enabled' => $this->gateway->debug_log(),
244 'hide_button_options' => $this->get_hidden_button_options(),
245 'google_pay_color' => $this->gateway->get_option( 'digital_wallets_google_pay_button_color', 'black' ),
246 'apple_pay_color' => $this->gateway->get_option( 'digital_wallets_apple_pay_button_color', 'black' ),
247 'is_pay_for_order_page' => is_wc_endpoint_url( 'order-pay' ),
248 'order_id' => absint( get_query_var( 'order-pay' ) ),
249 )
250 );
251
252 wc_enqueue_js( sprintf( 'window.wc_square_digital_wallet_handler = new WC_Square_Digital_Wallet_Handler( %s );', wp_json_encode( $args ) ) );
253 } catch ( \Exception $e ) {
254 wp_dequeue_style( 'wc-square-digital-wallet' );
255 wp_dequeue_script( 'wc-square-digital-wallet' );
256 }
257 }
258
259 /**
260 * Build the payment request object for the given context (i.e. product, cart or checkout page)
261 *
262 * Payment request objects are used by the SqPaymentForm and need to be in a specific format.
263 * Reference: https://developer.squareup.com/docs/api/paymentform#paymentform-paymentrequestobjects
264 *
265 * @since 2.3
266 * @param string $context
267 * @return array
268 */
269 public function get_payment_request_for_context( $context ) {
270 // Ignoring nonce verification checks as it is already handled in the parent function.
271 $payment_request = array();
272 $is_pay_for_order_page = isset( $_POST['is_pay_for_order_page'] ) ? 'true' === sanitize_text_field( wp_unslash( $_POST['is_pay_for_order_page'] ) ) : is_wc_endpoint_url( 'order-pay' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
273 $order_id = isset( $_POST['order_id'] ) ? (int) sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : absint( get_query_var( 'order-pay' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
274
275 switch ( $context ) {
276 case 'product':
277 $payment_request = $this->get_product_payment_request( get_the_ID() );
278 break;
279
280 case 'cart':
281 case 'checkout':
282 if ( is_wc_endpoint_url( 'order-pay' ) || $is_pay_for_order_page ) {
283 $order = wc_get_order( $order_id );
284 $payment_request = $this->build_payment_request(
285 $order->get_total(),
286 array(
287 'order_id' => $order_id,
288 'is_pay_for_order_page' => $is_pay_for_order_page,
289 )
290 );
291 } elseif ( isset( WC()->cart ) && $this->allowed_for_cart() ) {
292 WC()->cart->calculate_totals();
293 $payment_request = $this->build_payment_request( WC()->cart->total );
294 }
295
296 break;
297 }
298
299 return $payment_request;
300 }
301
302 /**
303 * Build a payment request object to be sent to SqPaymentForm on the product page
304 *
305 * Documentation: https://developer.squareup.com/docs/api/paymentform#paymentform-paymentrequestobjects
306 *
307 * @since 2.3
308 * @param int $product_id
309 * @param bool $add_to_cart - whether or not the product needs to be added to the cart before building the payment request
310 * @return array
311 */
312 public function get_product_payment_request( $product_id = 0, $quantity = 1, $attributes = array(), $add_to_cart = false ) {
313 $data = array();
314 $items = array();
315 $product_id = ! empty( $product_id ) ? $product_id : get_the_ID();
316 $product = wc_get_product( $product_id );
317 $variation_id = 0;
318
319 if ( ! is_a( $product, 'WC_Product' ) ) {
320 /* translators: product ID */
321 throw new \Exception( sprintf( __( 'Product with the ID (%d) cannot be found.', 'woocommerce-square' ), $product_id ) );
322 }
323
324 $quantity = $product->is_sold_individually() ? 1 : $quantity;
325
326 if ( 'variable' === $product->get_type() && ! empty( $attributes ) ) {
327 $data_store = \WC_Data_Store::load( 'product' );
328 $variation_id = $data_store->find_matching_product_variation( $product, $attributes );
329
330 if ( ! empty( $variation_id ) ) {
331 $product = wc_get_product( $variation_id );
332 }
333 }
334
335 if ( ! $product->has_enough_stock( $quantity ) ) {
336 /* translators: 1: product name 2: quantity in stock */
337 throw new \Exception( sprintf( __( 'You cannot add that amount of "%1$s"; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce-square' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ) );
338 }
339
340 if ( ! $product->is_purchasable() ) {
341 /* translators: 1: product name */
342 throw new \Exception( sprintf( __( 'You cannot purchase "%1$s" because it is currently not available.', 'woocommerce-square' ), $product->get_name() ) );
343 }
344
345 if ( $add_to_cart ) {
346 WC()->cart->empty_cart();
347 WC()->cart->add_to_cart( $product->get_id(), $quantity, $variation_id, $attributes );
348
349 WC()->cart->calculate_totals();
350 return $this->build_payment_request( WC()->cart->total );
351 }
352
353 $amount = number_format( $quantity * $product->get_price(), 2, '.', '' );
354 $quantity_label = 1 < $quantity ? ' x ' . $quantity : '';
355
356 $items[] = array(
357 'label' => $product->get_name() . $quantity_label,
358 'amount' => $amount,
359 'pending' => false,
360 );
361
362 if ( wc_tax_enabled() ) {
363 $items[] = array(
364 'label' => __( 'Tax', 'woocommerce-square' ),
365 'amount' => '0.00',
366 'pending' => false,
367 );
368 }
369
370 $data['requestShippingContact'] = $product->needs_shipping();
371 $data['lineItems'] = $items;
372
373 return $this->build_payment_request( $amount, $data );
374 }
375
376 /**
377 * Build a payment request object to be sent to SqPaymentForm.
378 *
379 * Documentation: https://developer.squareup.com/docs/api/paymentform#paymentform-paymentrequestobjects
380 *
381 * @since 2.3
382 * @param string $amount - format '100.00'
383 * @param array $data
384 * @return array
385 */
386 public function build_payment_request( $amount, $data = array() ) {
387 $is_pay_for_order_page = isset( $data['is_pay_for_order_page'] ) ? $data['is_pay_for_order_page'] : false;
388 $order_id = isset( $data['order_id'] ) ? $data['order_id'] : 0;
389
390 if ( $is_pay_for_order_page ) {
391 $request_shipping_contact = false;
392 } else {
393 $request_shipping_contact = isset( WC()->cart ) && WC()->cart->needs_shipping();
394 }
395
396 $order_data = array();
397 $data = wp_parse_args(
398 $data,
399 array(
400 'requestShippingContact' => $request_shipping_contact,
401 'requestEmailAddress' => true,
402 'requestBillingContact' => true,
403 'countryCode' => substr( get_option( 'woocommerce_default_country' ), 0, 2 ),
404 'currencyCode' => get_woocommerce_currency(),
405 )
406 );
407
408 if ( $is_pay_for_order_page ) {
409 $order = wc_get_order( $order_id );
410 $order_data = array(
411 'subtotal' => $order->get_subtotal(),
412 'discount' => $order->get_discount_total(),
413 'shipping' => $order->get_shipping_total(),
414 'fees' => $order->get_total_fees(),
415 'taxes' => $order->get_total_tax(),
416 );
417
418 unset( $data['is_pay_for_order_page'], $data['order_id'] );
419 }
420
421 if ( count( WC()->shipping->get_packages() ) > 1 ) {
422 throw new \Exception( __( 'This payment method cannot be used for multiple shipments.', 'woocommerce-square' ) );
423 }
424
425 if ( ! isset( $data['lineItems'] ) ) {
426 $data['lineItems'] = $this->build_payment_request_line_items( $order_data );
427 }
428
429 if ( true === $data['requestShippingContact'] ) {
430 $data['shippingOptions'] = array(
431 array(
432 'id' => '0',
433 'label' => __( 'Pending', 'woocommerce-square' ),
434 'amount' => '0.00',
435 'pending' => false,
436 ),
437 );
438 }
439
440 $data['total'] = array(
441 'label' => get_bloginfo( 'name', 'display' ) . esc_html( $this->total_label_suffix ),
442 'amount' => number_format( $amount, 2, '.', '' ),
443 'pending' => false,
444 );
445
446 return $data;
447 }
448
449 /**
450 * Builds an array of line items/totals to be sent back to Square in the lineItems array.
451 *
452 * @since 2.3
453 * @param array $totals
454 * @return array
455 */
456 public function build_payment_request_line_items( $totals = array() ) {
457 // Ignoring nonce verification checks as it is already handled in the parent function.
458 $totals = empty( $totals ) ? $this->get_cart_totals() : $totals;
459 $line_items = array();
460 $order_id = isset( $_POST['order_id'] ) ? (int) sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : absint( get_query_var( 'order-pay' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
461
462 if ( $order_id ) {
463 $order = wc_get_order( $order_id );
464 $iterable = $order->get_items();
465 } else {
466 $iterable = WC()->cart->get_cart();
467 }
468
469 foreach ( $iterable as $item ) {
470 $amount = number_format( $order_id ? $order->get_subtotal() : $item['line_subtotal'], 2, '.', '' );
471
472 if ( $order_id ) {
473 $quantity_label = 1 < $item->get_quantity() ? ' x ' . $item->get_quantity() : '';
474 } else {
475 $quantity_label = 1 < $item['quantity'] ? ' x ' . $item['quantity'] : '';
476 }
477
478 $item = array(
479 'label' => $order_id ? $item->get_name() . $quantity_label : $item['data']->get_name() . $quantity_label,
480 'amount' => $amount,
481 'pending' => false,
482 );
483
484 $line_items[] = $item;
485 }
486
487 if ( $totals['shipping'] > 0 ) {
488 $line_items[] = array(
489 'label' => __( 'Shipping', 'woocommerce-square' ),
490 'amount' => number_format( $totals['shipping'], 2, '.', '' ),
491 'pending' => false,
492 );
493 }
494
495 if ( $totals['taxes'] > 0 ) {
496 $line_items[] = array(
497 'label' => __( 'Tax', 'woocommerce-square' ),
498 'amount' => number_format( $totals['taxes'], 2, '.', '' ),
499 'pending' => false,
500 );
501 }
502
503 if ( $totals['discount'] > 0 ) {
504 $line_items[] = array(
505 'label' => __( 'Discount', 'woocommerce-square' ),
506 'amount' => number_format( $totals['discount'], 2, '.', '' ),
507 'pending' => false,
508 );
509 }
510
511 if ( $totals['fees'] > 0 ) {
512 $line_items[] = array(
513 'label' => __( 'Fees', 'woocommerce-square' ),
514 'amount' => number_format( $totals['fees'], 2, '.', '' ),
515 'pending' => false,
516 );
517 }
518
519 return $line_items;
520 }
521
522
523 /**
524 * Get the payment request object in an ajax request
525 *
526 * @since 2.3
527 * @return void
528 */
529 public function ajax_get_payment_request() {
530 check_ajax_referer( 'wc-square-get-payment-request', 'security' );
531
532 $payment_request = array();
533 $context = ! empty( $_POST['context'] ) ? wc_clean( wp_unslash( $_POST['context'] ) ) : '';
534
535 try {
536 if ( 'product' === $context ) {
537 $product_id = ! empty( $_POST['product_id'] ) ? wc_clean( wp_unslash( $_POST['product_id'] ) ) : 0;
538 $quantity = ! empty( $_POST['quantity'] ) ? wc_clean( wp_unslash( $_POST['quantity'] ) ) : 1;
539 $attributes = ! empty( $_POST['attributes'] ) ? wc_clean( wp_unslash( $_POST['attributes'] ) ) : array();
540 $payment_request = $this->get_product_payment_request( $product_id, $quantity, $attributes );
541
542 } else {
543 $payment_request = $this->get_payment_request_for_context( $context );
544 }
545
546 if ( empty( $payment_request ) ) {
547 /* translators: Context (product, cart, checkout or page) */
548 throw new \Exception( sprintf( esc_html__( 'Empty payment request data for %s.', 'woocommerce-square' ), ! empty( $context ) ? $context : 'page' ) );
549 }
550 } catch ( \Exception $e ) {
551 wp_send_json_error( $e->getMessage() );
552 }
553
554 wp_send_json_success( wp_json_encode( $payment_request ) );
555 }
556
557 /**
558 * When the digital wallet button is pressed, add the product to cart and generate a new payment request.
559 * We need to add the product the cart to help with shipping/tax calculations.
560 *
561 * @since 2.3
562 * @return void
563 */
564 public function ajax_add_to_cart() {
565 check_ajax_referer( 'wc-square-add-to-cart', 'security' );
566
567 try {
568 $product_id = ! empty( $_POST['product_id'] ) ? wc_clean( wp_unslash( $_POST['product_id'] ) ) : 0;
569 $quantity = ! empty( $_POST['quantity'] ) ? wc_clean( wp_unslash( $_POST['quantity'] ) ) : 1;
570 $attributes = ! empty( $_POST['attributes'] ) ? wc_clean( wp_unslash( $_POST['attributes'] ) ) : array();
571
572 $response = array(
573 'payment_request' => $this->get_product_payment_request( $product_id, $quantity, $attributes, true ),
574 // We need to generate a new set of nonces now that a WC customer session exists after a product was added to the cart
575 'payment_request_nonce' => wp_create_nonce( 'wc-square-get-payment-request' ),
576 'add_to_cart_nonce' => wp_create_nonce( 'wc-square-add-to-cart' ),
577 'recalculate_totals_nonce' => wp_create_nonce( 'wc-square-recalculate-totals' ),
578 'process_checkout_nonce' => wp_create_nonce( 'woocommerce-process_checkout' ),
579 );
580
581 wp_send_json_success( wp_json_encode( $response ) );
582 } catch ( \Exception $e ) {
583 wp_send_json_error( $e->getMessage() );
584 }
585 }
586
587 /**
588 * Updates shipping method in WC session
589 *
590 * @since 2.3
591 * @param array $shipping_methods Array of selected shipping methods ids
592 * @return void
593 */
594 public function update_shipping_method( $shipping_methods ) {
595 $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
596
597 if ( is_array( $shipping_methods ) ) {
598 foreach ( $shipping_methods as $i => $value ) {
599 $chosen_shipping_methods[ $i ] = wc_clean( $value );
600 }
601 }
602
603 WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
604 }
605
606 /**
607 * Reset shipping and calculate the latest shipping options/package with the given address.
608 *
609 * If no address, use the store's base address as default.
610 *
611 * @since 2.3
612 * @param array $address
613 * @return void
614 */
615 public function calculate_shipping( $address = array() ) {
616 WC()->shipping->reset_shipping();
617
618 if ( $address['countryCode'] ) {
619 WC()->customer->set_location( strtoupper( $address['countryCode'] ), $address['state'], $address['postalCode'], $address['city'] );
620 WC()->customer->set_shipping_location( strtoupper( $address['countryCode'] ), $address['state'], $address['postalCode'], $address['city'] );
621 } else {
622 WC()->customer->set_billing_address_to_base();
623 WC()->customer->set_shipping_address_to_base();
624 }
625
626 WC()->customer->set_calculated_shipping( true );
627 WC()->customer->save();
628
629 $packages = array();
630 $packages[0]['contents'] = WC()->cart->get_cart();
631 $packages[0]['contents_cost'] = 0;
632 $packages[0]['applied_coupons'] = WC()->cart->applied_coupons;
633 $packages[0]['user']['ID'] = get_current_user_id();
634 $packages[0]['destination']['country'] = $address['countryCode'];
635 $packages[0]['destination']['state'] = $address['state'];
636 $packages[0]['destination']['postcode'] = $address['postalCode'];
637 $packages[0]['destination']['city'] = $address['city'];
638 $packages[0]['destination']['address'] = $address['address'];
639 $packages[0]['destination']['address_2'] = $address['address_2'];
640
641 foreach ( WC()->cart->get_cart() as $item ) {
642 if ( $item['data']->needs_shipping() ) {
643 if ( isset( $item['line_total'] ) ) {
644 $packages[0]['contents_cost'] += $item['line_total'];
645 }
646 }
647 }
648
649 /**
650 * Hook to filter shipping packages.
651 *
652 * @param array Array of shipping packages.
653 * @since 2.3
654 */
655 $packages = apply_filters( 'woocommerce_cart_shipping_packages', $packages );
656
657 WC()->shipping->calculate_shipping( $packages );
658 }
659
660 /**
661 * Returns location's state code by state name.
662 *
663 * @param string $country_code The country's 2 letter ISO 3166-1 alpha-2 code.
664 * @param string $state_name The full name of the state that is to be search for its code.
665 *
666 * @return string
667 */
668 public static function get_state_code_by_name( $country_code = '', $state_name = '' ) {
669 if ( empty( $country_code ) || empty( $state_name ) ) {
670 return '';
671 }
672
673 $states = WC()->countries->get_states( $country_code );
674
675 /**
676 * Check for valid country code that don't have list of states,
677 * return state code as it is.
678 */
679 $countries = WC()->countries->get_countries();
680
681 if ( false === $states && isset( $countries[ $country_code ] ) ) {
682 return $state_name;
683 }
684
685 if ( is_array( $states ) ) {
686 /** Return the state code if $state_name already contains a valid state code. */
687 if ( isset( $states[ $state_name ] ) ) {
688 return $state_name;
689 }
690
691 foreach ( $states as $code => $name ) {
692 if ( $name === $state_name ) {
693 return $code;
694 }
695 }
696 }
697
698 return '';
699 }
700
701 /**
702 * Recalculate shipping methods and cart totals and send the updated information
703 * data as a square payment request json object.
704 *
705 * @since 2.3
706 * @return void
707 */
708 public function ajax_recalculate_totals() {
709 check_ajax_referer( 'wc-square-recalculate-totals', 'security' );
710
711 $chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
712 $shipping_address = array();
713 $payment_request = array();
714
715 $is_pay_for_order_page = isset( $_POST['is_pay_for_order_page'] ) ? 'true' === sanitize_text_field( wp_unslash( $_POST['is_pay_for_order_page'] ) ) : is_wc_endpoint_url( 'order-pay' );
716 $order_id = isset( $_POST['order_id'] ) ? (int) sanitize_text_field( wp_unslash( $_POST['order_id'] ) ) : absint( get_query_var( 'order-pay' ) );
717 $order_data = array();
718
719 if ( WC()->cart->needs_shipping() || $is_pay_for_order_page ) {
720 if ( ! empty( $_POST['shipping_contact'] ) ) {
721 $shipping_address = wp_parse_args(
722 wc_clean( wp_unslash( $_POST['shipping_contact'] ) ),
723 array(
724 'countryCode' => null,
725 'state' => null,
726 'city' => null,
727 'postalCode' => null,
728 'address' => null,
729 'address_2' => null,
730 )
731 );
732
733 /**
734 * WooCommerce requires state code but for few countries, Google Pay
735 * returns the state's full name instead of the state code.
736 *
737 * The following line converts state name to code.
738 */
739 if ( isset( $shipping_address['countryCode'] ) && isset( $shipping_address['state'] ) ) {
740 $shipping_address['state'] = self::get_state_code_by_name( $shipping_address['countryCode'], $shipping_address['state'] );
741 }
742
743 $this->calculate_shipping( $shipping_address );
744
745 $packages = WC()->shipping->get_packages();
746
747 if ( ! empty( $packages ) ) {
748 foreach ( $packages[0]['rates'] as $method ) {
749 $payment_request['shippingOptions'][] = array(
750 'id' => $method->id,
751 'label' => $method->get_label(),
752 'amount' => number_format( $method->cost, 2, '.', '' ),
753 );
754 }
755 }
756
757 // sort the shippingOptions so that the default/chosen shipping method is the first option so that it's displayed first in the Apple Pay/Google Pay window
758 if ( isset( $payment_request['shippingOptions'][0] ) ) {
759 if ( isset( $chosen_methods[0] ) ) {
760 $chosen_method_id = $chosen_methods[0];
761 $compare_shipping_options = function ( $a, $b ) use ( $chosen_method_id ) {
762 if ( $a['id'] === $chosen_method_id ) {
763 return -1;
764 }
765
766 if ( $b['id'] === $chosen_method_id ) {
767 return 1;
768 }
769
770 return 0;
771 };
772
773 usort( $payment_request['shippingOptions'], $compare_shipping_options );
774 }
775
776 $first_shipping_method_id = $payment_request['shippingOptions'][0]['id'];
777 $this->update_shipping_method( array( $first_shipping_method_id ) );
778 }
779 } elseif ( ! empty( $_POST['shipping_option'] ) ) {
780 $chosen_methods = array( wc_clean( wp_unslash( $_POST['shipping_option'] ) ) );
781 $this->update_shipping_method( $chosen_methods );
782 }
783 }
784
785 if ( ! $is_pay_for_order_page ) {
786 WC()->cart->calculate_totals();
787 }
788
789 if ( $is_pay_for_order_page ) {
790 $order = wc_get_order( $order_id );
791 $order_data = array(
792 'subtotal' => $order->get_subtotal(),
793 'discount' => $order->get_discount_total(),
794 'shipping' => $order->get_shipping_total(),
795 'fees' => $order->get_total_fees(),
796 'taxes' => $order->get_total_tax(),
797 );
798 }
799
800 $payment_request['lineItems'] = $this->build_payment_request_line_items( $order_data );
801
802 if ( $is_pay_for_order_page ) {
803 $total_amount = $order->get_total();
804 } else {
805 $total_amount = WC()->cart->total;
806 }
807
808 $payment_request['total'] = array(
809 'label' => get_bloginfo( 'name', 'display' ) . esc_html( $this->total_label_suffix ),
810 'amount' => number_format( $total_amount, 2, '.', '' ),
811 'pending' => false,
812 );
813
814 wp_send_json_success( $payment_request );
815 }
816
817 /**
818 * Filters the post data just before checkout.
819 *
820 * WooCommerce requires the state code but Google Pay returns
821 * the full name of the state. We filter the post data to convert
822 * the full state name into its equivalent state code.
823 *
824 * @param array $posted_data The $_POST data submitted at checkout.
825 *
826 * @return array
827 */
828 public function filter_posted_data( $posted_data ) {
829 if ( isset( $posted_data['payment_method'] ) && 'square_credit_card' !== $posted_data['payment_method'] ) {
830 return $posted_data;
831 }
832
833 if ( isset( $posted_data['shipping_country'] ) && isset( $posted_data['shipping_state'] ) ) {
834 $posted_data['shipping_state'] = self::get_state_code_by_name( $posted_data['shipping_country'], $posted_data['shipping_state'] );
835 }
836
837 if ( isset( $posted_data['billing_country'] ) && isset( $posted_data['billing_state'] ) ) {
838 $posted_data['billing_state'] = self::get_state_code_by_name( $posted_data['billing_country'], $posted_data['billing_state'] );
839 }
840
841 return $posted_data;
842 }
843
844 /**
845 * Filters checkout fields when Digital Wallet is used.
846 *
847 * @param array $fields Array of checkout fields.
848 *
849 * @return array
850 */
851 public function filter_checkout_fields( $fields ) {
852 /** Ignoring nonce verification as that is already taken care of in WC_Checkout::process_checkout. */
853 $wallet_type = isset( $_POST['wc-square-digital-wallet-type'] ) ? wc_clean( wp_unslash( $_POST['wc-square-digital-wallet-type'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Missing
854
855 if ( ! $wallet_type ) {
856 return $fields;
857 }
858
859 if ( isset( $fields['billing'] ) && isset( $fields['billing']['billing_company'] ) ) {
860 $fields['billing']['billing_company']['required'] = false;
861 }
862
863 if ( isset( $fields['shipping'] ) && isset( $fields['shipping']['shipping_company'] ) ) {
864 $fields['shipping']['shipping_company']['required'] = false;
865 }
866
867 return $fields;
868 }
869
870 /**
871 * Process the digital wallet checkout
872 *
873 * @since 2.3
874 * @return void
875 */
876 public function ajax_process_checkout() {
877 if ( WC()->cart->is_empty() ) {
878 wp_send_json_error( __( 'Empty cart', 'woocommerce-square' ) );
879 }
880
881 if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
882 define( 'WOOCOMMERCE_CHECKOUT', true );
883 }
884
885 WC()->checkout()->process_checkout();
886
887 die( 0 );
888 }
889
890
891 /** Helper methods *******************************************************************************************/
892
893
894 /**
895 * Helper function to return the expected location of the apple-developer verification file on the server.
896 *
897 * @since 2.3
898 * @return string
899 */
900 public function apple_pay_verification_file_location() {
901 return ! empty( $_SERVER['DOCUMENT_ROOT'] ) ? untrailingslashit( wc_clean( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) ) . '/.well-known/apple-developer-merchantid-domain-association' : '';
902 }
903
904 /**
905 * Checks for the existance of Apple Pay verification domain file at:
906 * SERVER_ROOT/.well-known/apple-developer-merchantid-domain-association
907 *
908 * If the file doesn't exist or the contents has been modified, copy the file from the plugin
909 * directory to the expected verification domain file location.
910 *
911 * @since 2.3
912 * @return bool
913 */
914 public function check_apple_pay_verification_file() {
915 if ( empty( $_SERVER['DOCUMENT_ROOT'] ) ) {
916 return false;
917 }
918
919 $path = untrailingslashit( wc_clean( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) );
920 $dir = '.well-known';
921 $file = 'apple-developer-merchantid-domain-association';
922 $fullpath = $path . '/' . $dir . '/' . $file;
923 $plugin_path = $this->gateway->get_plugin()->get_plugin_path();
924 $existing_contents = @file_get_contents( $fullpath ); // @codingStandardsIgnoreLine
925 $new_contents = @file_get_contents( $plugin_path . '/' . $file ); // @codingStandardsIgnoreLine
926
927 if ( $existing_contents && $existing_contents === $new_contents ) {
928 return true;
929 }
930
931 if ( ! file_exists( $path . '/' . $dir ) ) {
932 if ( ! @mkdir( $path . '/' . $dir, 0755 ) ) { // @codingStandardsIgnoreLine
933 $this->gateway->get_plugin()->log( 'Unable to create domain association folder to domain root.' );
934 return false;
935 }
936 }
937
938 if ( ! @copy( $plugin_path . '/' . $file, $fullpath ) ) { // @codingStandardsIgnoreLine
939 $this->gateway->get_plugin()->log( 'Unable to copy domain association file to domain root.' );
940 return false;
941 }
942
943 $this->gateway->get_plugin()->log( 'Apple Pay Domain association file updated.' );
944 return true;
945 }
946
947 /**
948 * When loading the settings page this function, tries to register the current store domain with Square/Apple Pay.
949 *
950 * If digital wallets and Apple Pay is enabled, check that the domain verification file exist and check
951 * the gateway settings for `apple_pay_domain_registered` to confirm that this domain has been successfully registered with Square/Apple Pay.
952 *
953 * If the store has been registered, keep verifying the registration of the current connected account and domain every hour.
954 *
955 * @since 2.3
956 * @return string
957 */
958 public function apple_pay_domain_registration() {
959 // Only register the store url with Apple Pay if the gateway and digital wallets are enable (check POST data to account for the page load when settings are being saved).
960 if ( ( 'no' === $this->gateway->get_option( 'enabled', 'no' ) && empty( $_POST['woocommerce_square_credit_card_enabled'] ) ) || ( 'no' === $this->gateway->get_option( 'enable_digital_wallets', 'yes' ) && empty( $_POST['woocommerce_square_credit_card_enable_digital_wallets'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
961 return;
962 }
963
964 // when settings are being saved, make sure we use the latest values from POST data to check if Apple isn't one of the hidden wallet options
965 $hidden_wallet_options = ! isset( $_POST['woocommerce_square_credit_card_enable_digital_wallets'] ) ? $this->gateway->get_option( 'digital_wallets_hide_button_options', array() ) : ( ! empty( $_POST['woocommerce_square_credit_card_digital_wallets_hide_button_options'] ) ? wc_clean( wp_unslash( $_POST['woocommerce_square_credit_card_digital_wallets_hide_button_options'] ) ) : array() ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
966 if ( in_array( 'apple', $hidden_wallet_options, true ) ) {
967 return;
968 }
969
970 if ( ! $this->check_apple_pay_verification_file() ) {
971 $this->gateway->update_option( 'apple_pay_domain_registered', 'no' );
972 return;
973 }
974
975 try {
976 $recently_registered = get_transient( 'wc_square_check_apple_pay_domain_registration' );
977
978 if ( 'no' === $this->gateway->get_option( 'apple_pay_domain_registered', 'no' ) || ! $recently_registered ) {
979 $this->register_apple_pay_domain();
980
981 $this->gateway->update_option( 'apple_pay_domain_registered', 'yes' );
982 $this->gateway->get_plugin()->log( 'Your domain has been verified with Apple Pay!' );
983
984 // avoid API rate limits by limiting the request to checking every hour
985 set_transient( 'wc_square_check_apple_pay_domain_registration', true, HOUR_IN_SECONDS );
986 }
987 } catch ( \Exception $e ) {
988 $this->gateway->update_option( 'apple_pay_domain_registered', 'no' );
989 $this->gateway->get_plugin()->log( 'Error: ' . $e->getMessage() );
990 }
991 }
992
993 /**
994 * Sends an API request to endpoint v2/apple-pay/domains to register the store's domain with Square/Apple Pay.
995 *
996 * Reference: https://developer.squareup.com/docs/payment-form/cookbook/apple-pay-register-domains
997 *
998 * @since 2.3
999 * @throws \Exception on error
1000 * @return void
1001 */
1002 private function register_apple_pay_domain() {
1003 $access_token = $this->gateway->get_plugin()->get_settings_handler()->get_access_token();
1004 $is_sandbox = $this->gateway->get_plugin()->get_settings_handler()->is_sandbox();
1005 $domain_name = ! empty( $_SERVER['HTTP_HOST'] ) ? wc_clean( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '';
1006
1007 if ( empty( $domain_name ) ) {
1008 throw new \Exception( 'Unable to verify domain with Apple Pay - no domain found in $_SERVER[\'HTTP_HOST\'].' );
1009 }
1010
1011 if ( empty( $access_token ) ) {
1012 throw new \Exception( __( 'Unable to verify domain with Apple Pay - missing access token.', 'woocommerce-square' ) );
1013 }
1014
1015 $response = wp_remote_post(
1016 'https://connect.squareup' . ( $is_sandbox ? 'sandbox' : '' ) . '.com/v2/apple-pay/domains',
1017 array(
1018 'headers' => array(
1019 'Square-Version' => '2020-10-28',
1020 'Authorization' => 'Bearer ' . $access_token,
1021 'Content-Type' => 'application/json',
1022 ),
1023 'body' => wp_json_encode(
1024 array(
1025 'domain_name' => $domain_name,
1026 )
1027 ),
1028 )
1029 );
1030
1031 if ( is_wp_error( $response ) ) {
1032 /* translators: error message */
1033 throw new \Exception( sprintf( 'Unable to verify domain %s - %s', $domain_name, $response->get_error_message() ) );
1034 }
1035
1036 $parsed_response = json_decode( $response['body'], true );
1037
1038 if ( 200 !== $response['response']['code'] || empty( $parsed_response['status'] ) || 'VERIFIED' !== $parsed_response['status'] ) {
1039 /* translators: error message */
1040 throw new \Exception( sprintf( 'Unable to verify domain %s - response = %s', $domain_name, print_r( $parsed_response, true ) ) ); //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
1041 }
1042 }
1043
1044 /**
1045 * Returns an array of pages that digital wallets are loaded/available on.
1046 * Defaults to product, cart and checkout pages.
1047 *
1048 * @since 2.3
1049 * @return array
1050 */
1051 public function get_available_pages() {
1052 /**
1053 * Hook to filter pages which should load digital wallets.
1054 *
1055 * @param array Array of page names.
1056 * @since 2.3
1057 */
1058 return apply_filters(
1059 'wc_square_display_digital_wallet_on_pages',
1060 array(
1061 'product',
1062 'cart',
1063 'checkout',
1064 ),
1065 $this
1066 );
1067 }
1068
1069 /**
1070 * Returns the current page.
1071 *
1072 * Stores the result in $this->page to avoid recalculating multiple times per request
1073 *
1074 * @since 2.3
1075 * @return string
1076 */
1077 public function get_current_page() {
1078 if ( null === $this->page ) {
1079 $is_cart = is_cart() && ! WC()->cart->is_empty();
1080 $is_product = is_product() || wc_post_content_has_shortcode( 'product_page' );
1081 $this->page = $is_cart ? 'cart' : ( $is_product ? 'product' : ( is_checkout() ? 'checkout' : null ) );
1082 }
1083
1084 return $this->page;
1085 }
1086
1087 /**
1088 * Returns cart totals in an array format
1089 *
1090 * @since 2.3
1091 * @throws \Exception if no cart is found
1092 * @return array
1093 */
1094 public function get_cart_totals() {
1095 if ( ! isset( WC()->cart ) ) {
1096 throw new \Exception( 'Cart data cannot be found.' );
1097 }
1098
1099 return array(
1100 'subtotal' => WC()->cart->subtotal_ex_tax,
1101 'discount' => WC()->cart->get_cart_discount_total(),
1102 'shipping' => WC()->cart->shipping_total,
1103 'fees' => WC()->cart->fee_total,
1104 'taxes' => WC()->cart->tax_total + WC()->cart->shipping_tax_total,
1105 );
1106 }
1107
1108 /**
1109 * Returns a list of hidden digital wallet options
1110 *
1111 * If Apple Pay domain hasn't been registered, force Apple Pay to be hidden.
1112 *
1113 * @since 2.3
1114 * @return array
1115 */
1116 public function get_hidden_button_options() {
1117 $hidden_options = $this->gateway->get_option( 'digital_wallets_hide_button_options', array() );
1118
1119 if ( ( ! is_array( $hidden_options ) || ! in_array( 'apple', $hidden_options, true ) ) && 'no' === $this->gateway->get_option( 'apple_pay_domain_registered', 'no' ) ) {
1120 $hidden_options[] = 'apple';
1121 }
1122
1123 return $hidden_options;
1124 }
1125
1126 /**
1127 * Returns a list the supported product types that can be used to purchase a digital wallet
1128 *
1129 * @since 2.3
1130 * @return array
1131 */
1132 public function supported_product_types() {
1133 /**
1134 * Hook to filter array of post types that can support digital wallets.
1135 *
1136 * @param array Array of supported post types.
1137 * @since 2.3
1138 */
1139 return apply_filters(
1140 'wc_square_digital_wallets_supported_product_types',
1141 array(
1142 'simple',
1143 'variable',
1144 'variation',
1145 'booking',
1146 'bundle',
1147 'composite',
1148 'mix-and-match',
1149 )
1150 );
1151 }
1152
1153 /**
1154 * Checks if digital wallets are allowed to be used to purchase the current product.
1155 *
1156 * @since 2.3
1157 * @return bool
1158 */
1159 public function allowed_for_product_page() {
1160 global $post;
1161
1162 $product = wc_get_product( $post->ID );
1163
1164 if ( ! is_object( $product ) || ! in_array( $product->get_type(), $this->supported_product_types(), true ) ) {
1165 return false;
1166 }
1167
1168 // Trial subscriptions with shipping are not supported
1169 if ( class_exists( 'WC_Subscriptions_Order' ) && $product->needs_shipping() && \WC_Subscriptions_Product::get_trial_length( $product ) > 0 ) {
1170 return false;
1171 }
1172
1173 // Pre Orders charge upon release not supported.
1174 if ( class_exists( 'WC_Pre_Orders_Order' ) && \WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) {
1175 return false;
1176 }
1177
1178 // File upload addon not supported
1179 if ( class_exists( 'WC_Product_Addons_Helper' ) ) {
1180 $product_addons = \WC_Product_Addons_Helper::get_product_addons( $product->get_id() );
1181 foreach ( $product_addons as $addon ) {
1182 if ( 'file_upload' === $addon['type'] ) {
1183 return false;
1184 }
1185 }
1186 }
1187
1188 return true;
1189 }
1190
1191 /**
1192 * Checks the cart to see if Square Digital Wallets is allowed to purchase all cart items.
1193 *
1194 * @since 2.3
1195 * @return bool
1196 */
1197 public function allowed_for_cart() {
1198 foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
1199 /**
1200 * Hook to filter cart item product.
1201 *
1202 * @param array $cart_item['data] Product object.
1203 * @param array $cart_item Cart item.
1204 * @param string $cart_item_key Cart item key.
1205 * @since 2.3
1206 */
1207 $_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
1208
1209 if ( ! in_array( $_product->get_type(), $this->supported_product_types(), true ) ) {
1210 return false;
1211 }
1212
1213 // Trial subscriptions with shipping are not supported
1214 if ( class_exists( 'WC_Subscriptions_Order' ) && \WC_Subscriptions_Cart::cart_contains_subscription() && $_product->needs_shipping() && \WC_Subscriptions_Product::get_trial_length( $_product ) > 0 ) {
1215 return false;
1216 }
1217
1218 // Pre Orders compatbility where we don't support charge upon release.
1219 if ( class_exists( 'WC_Pre_Orders_Order' ) && \WC_Pre_Orders_Cart::cart_contains_pre_order() && \WC_Pre_Orders_Product::product_is_charged_upon_release( \WC_Pre_Orders_Cart::get_pre_order_product() ) ) {
1220 return false;
1221 }
1222 }
1223
1224 return true;
1225 }
1226
1227 /**
1228 * Returns if Google Pay and/or Apple Pay is available by checking the following:
1229 * - setting is enabled
1230 * - country and currency is supported
1231 * - square is connected and location is set
1232 * - current page/cart has items that can be purchased with a digital wallet
1233 *
1234 * Sets $this->is_available so that it's only checked once per request/page load
1235 *
1236 * @since 2.3
1237 * @return bool
1238 */
1239 public function is_available() {
1240 if ( null === $this->is_available ) {
1241 $this->is_available = $this->is_available_for_page() && $this->is_digital_wallet_enabled() && $this->gateway->is_available();
1242 }
1243
1244 return $this->is_available;
1245 }
1246
1247 /**
1248 * Returns true if digital wallets are available for the given page.
1249 * Checks the current page is no $page value is given
1250 *
1251 * @since 2.4.0
1252 * @param string $page
1253 * @return bool
1254 */
1255 public function is_available_for_page( $page = '' ) {
1256 $is_available = true;
1257 $page = ! empty( $page ) ? $page : $this->get_current_page();
1258 $available_pages = $this->get_available_pages();
1259
1260 if ( empty( $page ) || ! is_array( $available_pages ) || ! in_array( $page, $available_pages, true ) ) {
1261 $is_available = false;
1262 }
1263
1264 if ( $is_available && 'product' === $page ) {
1265 $is_available = $this->allowed_for_product_page();
1266 } elseif ( $is_available && ( 'cart' === $page || 'checkout' === $page ) ) {
1267 $is_available = $this->allowed_for_cart();
1268 }
1269
1270 return $is_available;
1271 }
1272
1273 /**
1274 * Checks if digital wallets are available and enabled.
1275 *
1276 * @since 2.4
1277 * @return bool
1278 */
1279 public function is_digital_wallet_enabled() {
1280 return $this->gateway->is_digital_wallet_available() && 'yes' === $this->gateway->get_option( 'enable_digital_wallets', 'yes' );
1281 }
1282
1283 /**
1284 * This function calculates the value returned by get_option( 'wc_square_apple_pay_enabled', $default )
1285 * and is used by WC Admin's Remote Inbox Notifications for marketing purposes.
1286 *
1287 * Returns either 1 or 2 if Apple Pay is enabled on the store, or $value (false) if not
1288 *
1289 * @since 2.3
1290 * @param $value
1291 * @return int|mixed
1292 */
1293 public function get_option_is_apple_pay_enabled( $value ) {
1294 if ( $this->is_digital_wallet_enabled() && ! in_array( 'apple', $this->gateway->get_option( 'digital_wallets_hide_button_options', array() ), true ) ) {
1295 $value = wp_rand( 1, 2 );
1296 }
1297
1298 return $value;
1299 }
1300 }
1301