Skip to content
Open
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
97 changes: 97 additions & 0 deletions tests/unit/Helpers/CheckoutHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace SkyVerge\WooCommerce\PluginFramework\v6_0_1\Tests\unit\Helpers;

use Generator;
use Mockery;
use SkyVerge\WooCommerce\PluginFramework\v6_0_1\Helpers\CheckoutHelper;
use SkyVerge\WooCommerce\PluginFramework\v6_0_1\Tests\TestCase;
use WP_Mock;

/**
* @coversDefaultClass \SkyVerge\WooCommerce\PluginFramework\v6_0_1\Helpers\CheckoutHelper
*/
final class CheckoutHelperTest extends TestCase
{
/**
* @covers ::isCountryAllowedToOrder
* @dataProvider countryCodeProvider
*/
public function testCanDetermineIsCountryAllowedToOrder(
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_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,
];
}
}
52 changes: 52 additions & 0 deletions woocommerce/Helpers/CheckoutHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace SkyVerge\WooCommerce\PluginFramework\v6_0_1\Helpers;

class CheckoutHelper
{
/**
* Determines whether the provided country code is allowed to place an order.
*
* @since 6.0.1
*
* @param string $countryCode recommended to pass through the *billing* address
* @return bool
*/
public static function isCountryAllowedToOrder(string $countryCode): bool
{
if (empty($countryCode)) {
return false;
}

if (WC() && WC()->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;
}
}
3 changes: 2 additions & 1 deletion woocommerce/changelog.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' ) ) :
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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 );
Expand All @@ -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.
*
Expand Down
Loading