diff --git a/tests/unit/Helpers/CheckoutHelperTest.php b/tests/unit/Helpers/CheckoutHelperTest.php new file mode 100644 index 000000000..e5fc7c465 --- /dev/null +++ b/tests/unit/Helpers/CheckoutHelperTest.php @@ -0,0 +1,97 @@ +countries = $wcCountriesAvailable ? $countriesMock : null; + + WP_Mock::userFunction('WC') + ->andReturn($wcMock); + + $countriesMock->allows('get_allowed_countries') + ->andReturn($allowedCountries); + + $this->assertSame($expected, CheckoutHelper::isCountryAllowedToOrder($countryCode)); + } + + /** + * @covers ::isCountryAllowedForShipping + * @dataProvider countryCodeProvider + */ + public function testCanDetermineIsCountryAllowedForShipping( + string $countryCode, + bool $wcCountriesAvailable, + array $allowedCountries, + bool $expected + ) { + $wcMock = Mockery::mock('WooCommerce'); + + $countriesMock = Mockery::mock('WC_Countries'); + $wcMock->countries = $wcCountriesAvailable ? $countriesMock : null; + + WP_Mock::userFunction('WC') + ->andReturn($wcMock); + + $countriesMock->allows('get_shipping_countries') + ->andReturn($allowedCountries); + + $this->assertSame($expected, CheckoutHelper::isCountryAllowedForShipping($countryCode)); + } + + /** + * @see testCanDetermineIsCountryAllowedToOrder + */ + public function countryCodeProvider() : Generator + { + yield 'empty country code' => [ + 'countryCode' => '', + 'wcCountriesAvailable' => false, + 'allowedCountries' => [], + 'expected' => false, + ]; + + yield 'WC countries not available' => [ + 'countryCode' => 'GB', + 'wcCountriesAvailable' => false, + 'allowedCountries' => [], + 'expected' => true, + ]; + + yield 'not on allow list' => [ + 'countryCode' => 'GB', + 'wcCountriesAvailable' => true, + 'allowedCountries' => ['US' => 'United States', 'FR' => 'France'], + 'expected' => false, + ]; + + yield 'is on allow list' => [ + 'countryCode' => 'GB', + 'wcCountriesAvailable' => true, + 'allowedCountries' => ['US' => 'United States', 'FR' => 'France', 'GB' => 'Great Britain'], + 'expected' => true, + ]; + } +} diff --git a/woocommerce/Helpers/CheckoutHelper.php b/woocommerce/Helpers/CheckoutHelper.php new file mode 100644 index 000000000..ee5cc1baf --- /dev/null +++ b/woocommerce/Helpers/CheckoutHelper.php @@ -0,0 +1,52 @@ +countries) { + $allowed_countries = WC()->countries->get_allowed_countries(); + + return array_key_exists($countryCode, $allowed_countries); + } + + return true; + } + + /** + * Determines whether the provided country code is allowed for shipping. + * + * @since 6.0.1 + * + * @param string $countryCode recommended to pass through the *shipping* address + * @return bool + */ + public static function isCountryAllowedForShipping(string $countryCode): bool + { + if (empty($countryCode)) { + return false; + } + + if (WC() && WC()->countries) { + $shipping_countries = WC()->countries->get_shipping_countries(); + + return array_key_exists($countryCode, $shipping_countries); + } + + return true; + } +} diff --git a/woocommerce/changelog.txt b/woocommerce/changelog.txt index a63c4b66f..1214c22a1 100644 --- a/woocommerce/changelog.txt +++ b/woocommerce/changelog.txt @@ -1,7 +1,8 @@ *** SkyVerge WooCommerce Plugin Framework Changelog *** -2025.nn.nn - version 6.0.1 +2026.nn.nn - version 6.0.1 * Dev - Set PHP 8.1 defaults on `html_entity_decode()` usage (ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401) + * Fix - Apple Pay: Check store country restrictions for selling/shipping 2025.nn.nn - version 6.0.0 * Dev - Completely reworked how we handle dynamic properties on WooCommerce order objects. For now this change is backwards compatible with v5, but we will eventually drop support for the legacy method, so migrating ASAP is recommended. See the upgrade guide: https://github.com/godaddy-wordpress/wc-plugin-framework/wiki/Migrating-to-v6 diff --git a/woocommerce/payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-ajax.php b/woocommerce/payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-ajax.php index 6ef03c3c1..03f098075 100644 --- a/woocommerce/payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-ajax.php +++ b/woocommerce/payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-ajax.php @@ -24,6 +24,8 @@ namespace SkyVerge\WooCommerce\PluginFramework\v6_0_1; +use SkyVerge\WooCommerce\PluginFramework\v6_0_1\Helpers\CheckoutHelper; + defined( 'ABSPATH' ) or exit; if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v6_0_1\\SV_WC_Payment_Gateway_Apple_Pay_AJAX' ) ) : @@ -180,6 +182,16 @@ public function recalculate_totals() { $city = $contact['locality']; $postcode = $contact['postalCode']; + // validate country against WooCommerce selling and shipping settings + if ( $country ) { + /* + * Validate country against WooCommerce selling and shipping settings. + * Apple Pay contact info is primarily shipping address, but since we don't have both, we'll use + * the shipping address to validate both billing and shipping settings. + */ + $this->validateAllowedCountry( $country, $country ); + } + WC()->customer->set_shipping_city( $city ); WC()->customer->set_shipping_state( $state ); WC()->customer->set_shipping_country( $country ); @@ -238,6 +250,12 @@ public function process_payment() { try { + // final validation check: ensure billing/shipping country is still allowed + $billing_country = WC()->customer->get_billing_country(); + $shipping_country = WC()->customer->get_shipping_country(); + + $this->validateAllowedCountry( $billing_country ?: '', $shipping_country ?: '' ); + $result = $this->get_handler()->process_payment(); wp_send_json_success( $result ); @@ -254,6 +272,48 @@ public function process_payment() { } + /** + * Validates that the provided countries are allowed for billing and shipping. + * + * @since 6.0.1 + * + * @param string $billingCountry billing country code (empty string if not provided) + * @param string $shippingCountry shipping country code (empty string if not provided) + * @throws \Exception if validation fails + */ + protected function validateAllowedCountry(string $billingCountry, string $shippingCountry) + { + + // validate billing country for orders (if provided) + if (! empty($billingCountry) && ! CheckoutHelper::isCountryAllowedToOrder($billingCountry)) { + + $this->get_handler()->log("Apple Pay: Billing country '{$billingCountry}' is not allowed for orders"); + + throw new \Exception( + sprintf( + /* translators: %s country code. */ + esc_html__('Sorry, we do not allow orders from the provided country (%s)', 'woocommerce'), + esc_html($billingCountry) + ) + ); + } + + // validate shipping country for shipping (if provided) + if (! empty($shippingCountry) && ! CheckoutHelper::isCountryAllowedForShipping($shippingCountry)) { + + $this->get_handler()->log("Apple Pay: Shipping country '{$shippingCountry}' is not allowed for shipping"); + + throw new \Exception( + sprintf( + /* translators: %s country code. */ + esc_html__('Sorry, we do not ship orders to the provided country (%s)', 'woocommerce'), + esc_html($shippingCountry) + ) + ); + } + } + + /** * Gets the Apple Pay handler instance. *