Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions client/express-checkout/event-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,29 @@ export const shippingAddressChangeHandler = async ( event, elements ) => {
// when no shipping options are returned, the API still returns a 200 status code.
// We need to ensure that shipping options are present - otherwise the ECE dialog won't update correctly.
if ( shippingRates.length === 0 ) {
event.reject();
// Check if this is because no shipping zones are configured.
const hasShippingZones =
getExpressCheckoutData( 'checkout' )?.has_shipping_zones ??
true;

if ( ! hasShippingZones ) {
// Show error message about missing shipping configuration
event.reject( {
code: 'shipping_zones_not_configured',
message: __(
'No shipping methods are available. Please contact the store administrator to configure shipping.',
'woocommerce-payments'
),
} );
} else {
event.reject( {
code: 'no_shipping_options',
message: __(
'No shipping options are available for this address.',
'woocommerce-payments'
),
} );
}

return;
}
Expand All @@ -69,7 +91,13 @@ export const shippingAddressChangeHandler = async ( event, elements ) => {
lineItems: transformCartDataForDisplayItems( cartData ),
} );
} catch ( error ) {
event.reject();
event.reject( {
code: 'shipping_error',
message: __(
'There was an error processing the shipping address.',
'woocommerce-payments'
),
} );
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ public function get_express_checkout_params() {
'needs_payer_phone' => 'required' === get_option( 'woocommerce_checkout_phone_field', 'required' ),
'allowed_shipping_countries' => array_keys( WC()->countries->get_shipping_countries() ?? [] ),
'display_prices_with_tax' => 'incl' === get_option( 'woocommerce_tax_display_cart' ),
'has_shipping_zones' => $this->express_checkout_helper->has_shipping_zones_configured(),
],
'button' => $this->get_button_settings(),
'login_confirmation' => $this->get_login_confirmation_settings(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,29 @@ public function should_show_express_checkout_button() {
return false;
}

// Check if shipping is required but no shipping zones are configured.
$needs_shipping = false;

if ( $this->is_product() ) {
$needs_shipping = $this->get_product()->needs_shipping();
} elseif ( $this->is_cart() || $this->is_checkout() ) {
// Check if any products in cart need shipping.
$needs_shipping = false;
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( $cart_item['data']->needs_shipping() ) {
$needs_shipping = true;
break;
}
}
}

if ( $needs_shipping ) {
if ( ! $this->has_shipping_zones_configured() ) {
Logger::log( 'Shipping required but no shipping zones configured ( Express Checkout Element button disabled )' );
return false;
}
}

return true;
}

Expand All @@ -463,6 +486,39 @@ public function product_needs_shipping( WC_Product $product ) {
return wc_shipping_enabled() && 0 !== wc_get_shipping_method_count( true ) && $product->needs_shipping();
}

/**
* Check if shipping zones are properly configured.
*
* @return bool Returns true if shipping zones are configured; otherwise, returns false.
*/
public function has_shipping_zones_configured() {
if ( ! wc_shipping_enabled() ) {
return false;
}

$shipping_zones = WC_Shipping_Zones::get_zones();
$rest_of_world_zone = WC_Shipping_Zones::get_zone_by( 'zone_id', 0 );

// Check if there are any shipping zones configured.
if ( empty( $shipping_zones ) && ( ! $rest_of_world_zone || empty( $rest_of_world_zone->get_shipping_methods() ) ) ) {
return false;
}

// Check if any zone has shipping methods.
foreach ( $shipping_zones as $zone ) {
if ( ! empty( $zone->get_shipping_methods() ) ) {
return true;
}
}

// Check rest of world zone.
if ( $rest_of_world_zone && ! empty( $rest_of_world_zone->get_shipping_methods() ) ) {
return true;
}

return false;
}

/**
* Checks to make sure product type is supported.
*
Expand Down Expand Up @@ -632,78 +688,6 @@ public function get_product_data() {
return apply_filters( 'wcpay_payment_request_product_data', $data, $product );
}

/**
* The Store API doesn't allow checkout without the billing email address present on the order data.
* https://github.com/woocommerce/woocommerce/issues/48540
*
* @return bool
*/
private function is_pay_for_order_supported() {
$order_id = absint( get_query_var( 'order-pay' ) );
if ( 0 === $order_id ) {
return false;
}

$order = wc_get_order( $order_id );
if ( ! is_a( $order, 'WC_Order' ) ) {
return false;
}

// we don't need to check its validity or value, we just need to ensure a billing email is present.
$billing_email = $order->get_billing_email();
if ( ! empty( $billing_email ) ) {
return true;
}

Logger::log( 'Billing email not present ( Express Checkout Element button disabled )' );

return false;
}

/**
* Whether product page has a supported product.
*
* @return boolean
*/
private function is_product_supported() {
$product = $this->get_product();
$is_supported = true;

/**
* Ignore undefined classes from 3rd party plugins.
*
* @psalm-suppress UndefinedClass
*/

if ( is_null( $product ) || ! is_object( $product ) ) {
$is_supported = false;
} else {
// Simple subscription that needs shipping with free trials is not supported.
$is_free_trial_simple_subs = class_exists( 'WC_Subscriptions_Product' ) && $product->get_type() === 'subscription' && $product->needs_shipping() && WC_Subscriptions_Product::get_trial_length( $product ) > 0;

if (
! in_array( $product->get_type(), $this->supported_product_types(), true )
|| $is_free_trial_simple_subs
|| ( class_exists( 'WC_Pre_Orders_Product' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) // Pre Orders charge upon release not supported.
|| ( class_exists( 'WC_Composite_Products' ) && $product->is_type( 'composite' ) ) // Composite products are not supported on the product page.
|| ( class_exists( 'WC_Mix_and_Match' ) && $product->is_type( 'mix-and-match' ) ) // Mix and match products are not supported on the product page.
) {
$is_supported = false;
} elseif ( class_exists( 'WC_Product_Addons_Helper' ) ) {
// File upload addon not supported.
$product_addons = WC_Product_Addons_Helper::get_product_addons( $product->get_id() );
foreach ( $product_addons as $addon ) {
if ( 'file_upload' === $addon['type'] ) {
$is_supported = false;
break;
}
}
}
}

return apply_filters( 'wcpay_payment_request_is_product_supported', $is_supported, $product );
}

/**
* Gets the product total price.
*
Expand Down Expand Up @@ -836,4 +820,76 @@ public function add_order_payment_method_title( $order_id ) {
$order->set_payment_method_title( $payment_method_title . $suffix );
$order->save();
}

/**
* The Store API doesn't allow checkout without the billing email address present on the order data.
* https://github.com/woocommerce/woocommerce/issues/48540
*
* @return bool
*/
private function is_pay_for_order_supported() {
$order_id = absint( get_query_var( 'order-pay' ) );
if ( 0 === $order_id ) {
return false;
}

$order = wc_get_order( $order_id );
if ( ! is_a( $order, 'WC_Order' ) ) {
return false;
}

// we don't need to check its validity or value, we just need to ensure a billing email is present.
$billing_email = $order->get_billing_email();
if ( ! empty( $billing_email ) ) {
return true;
}

Logger::log( 'Billing email not present ( Express Checkout Element button disabled )' );

return false;
}

/**
* Whether product page has a supported product.
*
* @return boolean
*/
private function is_product_supported() {
$product = $this->get_product();
$is_supported = true;

/**
* Ignore undefined classes from 3rd party plugins.
*
* @psalm-suppress UndefinedClass
*/

if ( is_null( $product ) || ! is_object( $product ) ) {
$is_supported = false;
} else {
// Simple subscription that needs shipping with free trials is not supported.
$is_free_trial_simple_subs = class_exists( 'WC_Subscriptions_Product' ) && $product->get_type() === 'subscription' && $product->needs_shipping() && WC_Subscriptions_Product::get_trial_length( $product ) > 0;

if (
! in_array( $product->get_type(), $this->supported_product_types(), true )
|| $is_free_trial_simple_subs
|| ( class_exists( 'WC_Pre_Orders_Product' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) // Pre Orders charge upon release not supported.
|| ( class_exists( 'WC_Composite_Products' ) && $product->is_type( 'composite' ) ) // Composite products are not supported on the product page.
|| ( class_exists( 'WC_Mix_and_Match' ) && $product->is_type( 'mix-and-match' ) ) // Mix and match products are not supported on the product page.
) {
$is_supported = false;
} elseif ( class_exists( 'WC_Product_Addons_Helper' ) ) {
// File upload addon not supported.
$product_addons = WC_Product_Addons_Helper::get_product_addons( $product->get_id() );
foreach ( $product_addons as $addon ) {
if ( 'file_upload' === $addon['type'] ) {
$is_supported = false;
break;
}
}
}
}

return apply_filters( 'wcpay_payment_request_is_product_supported', $is_supported, $product );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,46 @@ public function __return_base() {
return 'base';
}

/**
* Test has_shipping_zones_configured method with no shipping zones.
*/
public function test_has_shipping_zones_configured_no_zones() {
// Delete all shipping zones.
WC_Helper_Shipping::delete_simple_flat_rate();
$this->zone->delete();

// Create a new zone with no methods.
$empty_zone = new WC_Shipping_Zone();
$empty_zone->set_zone_name( 'Empty Zone' );
$empty_zone->save();

$this->assertFalse( $this->system_under_test->has_shipping_zones_configured() );

// Clean up.
$empty_zone->delete();
}

/**
* Test has_shipping_zones_configured method with configured shipping zones.
*/
public function test_has_shipping_zones_configured_with_zones() {
// The zone is already set up in set_up() with shipping methods.
$this->assertTrue( $this->system_under_test->has_shipping_zones_configured() );
}

/**
* Test has_shipping_zones_configured method when shipping is disabled.
*/
public function test_has_shipping_zones_configured_shipping_disabled() {
// Disable shipping.
update_option( 'woocommerce_ship_to_countries', 'disabled' );

$this->assertFalse( $this->system_under_test->has_shipping_zones_configured() );

// Re-enable shipping.
update_option( 'woocommerce_ship_to_countries', 'all' );
}

/**
* @return WC_Payment_Gateway_WCPay
*/
Expand Down
Loading