Skip to content

Commit b6d96d6

Browse files
Merge pull request #4312 from woocommerce/dev/PCP-5956-validate-shipping-to-us
Limit shipping countries to US-only (5956)
2 parents 3124a13 + 11eb443 commit b6d96d6

7 files changed

Lines changed: 389 additions & 96 deletions

File tree

modules/ppcp-store-sync/src/CartValidation/ShippingValidator.php

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use WC_Countries;
2020
use WC_Validation;
2121
use WC_Product;
22+
2223
use WooCommerce\PayPalCommerce\StoreSync\Enums\Priority;
2324
use WooCommerce\PayPalCommerce\StoreSync\Enums\ShippingIssue;
2425
use WooCommerce\PayPalCommerce\StoreSync\Helper\ProductManager;
@@ -31,6 +32,12 @@
3132

3233
class ShippingValidator implements ValidatorInterface {
3334

35+
/**
36+
* List of shipping countries that are supported by the agentic integration.
37+
* Managed on plugin-side for extra code and test stability.
38+
*/
39+
private const PAYPAL_SUPPORTED_COUNTRIES = array( 'US' );
40+
3441
private ProductManager $product_manager;
3542

3643
public function __construct( ProductManager $product_manager ) {
@@ -109,52 +116,55 @@ private function validate_address_completeness( Address $address ): array {
109116
$issues = array();
110117

111118
if ( ! $address->address_line_1() ) {
112-
$issues[] = ValidationIssue::create_invalid_address( 'Shipping address is missing street address' )
113-
->user_message( 'Please provide a complete street address.' )
114-
->for_field( 'shipping_address.address_line_1' )
115-
->add_resolution(
116-
ResolutionOption::create_provide_missing_field()
117-
->label( 'Provide street address' )
118-
->set_meta( 'field', 'address_line_1' )
119-
)
120-
->add_resolution(
121-
ResolutionOption::create_update_address()
122-
->label( 'Update shipping address' )
123-
->priority( Priority::LOW )
124-
);
119+
$issues[] =
120+
ValidationIssue::create_invalid_address( 'Shipping address is missing street address' )
121+
->user_message( 'Please provide a complete street address.' )
122+
->for_field( 'shipping_address.address_line_1' )
123+
->add_resolution(
124+
ResolutionOption::create_provide_missing_field()
125+
->label( 'Provide street address' )
126+
->set_meta( 'field', 'address_line_1' )
127+
)
128+
->add_resolution(
129+
ResolutionOption::create_update_address()
130+
->label( 'Update shipping address' )
131+
->priority( Priority::LOW )
132+
);
125133
}
126134

127135
if ( ! $address->admin_area_2() ) {
128-
$issues[] = ValidationIssue::create_invalid_address( 'Shipping address is missing city' )
129-
->user_message( 'Please provide a city.' )
130-
->for_field( 'shipping_address.admin_area_2' )
131-
->add_resolution(
132-
ResolutionOption::create_provide_missing_field()
133-
->label( 'Provide city' )
134-
->set_meta( 'field', 'admin_area_2' )
135-
)
136-
->add_resolution(
137-
ResolutionOption::create_update_address()
138-
->label( 'Update shipping address' )
139-
->priority( Priority::LOW )
140-
);
136+
$issues[] =
137+
ValidationIssue::create_invalid_address( 'Shipping address is missing city' )
138+
->user_message( 'Please provide a city.' )
139+
->for_field( 'shipping_address.admin_area_2' )
140+
->add_resolution(
141+
ResolutionOption::create_provide_missing_field()
142+
->label( 'Provide city' )
143+
->set_meta( 'field', 'admin_area_2' )
144+
)
145+
->add_resolution(
146+
ResolutionOption::create_update_address()
147+
->label( 'Update shipping address' )
148+
->priority( Priority::LOW )
149+
);
141150
}
142151

143152
$postal_code = $address->postal_code();
144153
if ( ! $postal_code ) {
145-
$issues[] = ValidationIssue::create_invalid_address( 'Shipping address is missing postal code' )
146-
->user_message( 'Please provide a postal code.' )
147-
->for_field( 'shipping_address.postal_code' )
148-
->add_resolution(
149-
ResolutionOption::create_provide_missing_field()
150-
->label( 'Provide postal code' )
151-
->set_meta( 'field', 'postal_code' )
152-
)
153-
->add_resolution(
154-
ResolutionOption::create_update_address()
155-
->label( 'Update shipping address' )
156-
->priority( Priority::LOW )
157-
);
154+
$issues[] =
155+
ValidationIssue::create_invalid_address( 'Shipping address is missing postal code' )
156+
->user_message( 'Please provide a postal code.' )
157+
->for_field( 'shipping_address.postal_code' )
158+
->add_resolution(
159+
ResolutionOption::create_provide_missing_field()
160+
->label( 'Provide postal code' )
161+
->set_meta( 'field', 'postal_code' )
162+
)
163+
->add_resolution(
164+
ResolutionOption::create_update_address()
165+
->label( 'Update shipping address' )
166+
->priority( Priority::LOW )
167+
);
158168
} else {
159169
$postal_validation =
160170
$this->validate_postal_code_format( $postal_code, $address->country_code() );
@@ -351,6 +361,10 @@ private function is_country_allowed( string $country_code ): bool {
351361
return true;
352362
}
353363

364+
if ( ! $this->is_paypal_supported_country( $country_code ) ) {
365+
return false;
366+
}
367+
354368
$allowed_countries = $wc_countries->get_shipping_countries();
355369

356370
if ( empty( $allowed_countries ) ) {
@@ -377,17 +391,18 @@ private function get_country_name( string $country_code ): string {
377391
return $countries[ $country_code ] ?? $country_code;
378392
}
379393

380-
/**
381-
* @return \WC_Countries|null
382-
*/
383-
private function get_wc_countries() {
394+
private function get_wc_countries(): ?WC_Countries {
384395
if ( ! function_exists( 'WC' ) ) {
385396
return null;
386397
}
387398

388399
// The only place in the class that has a `WC()` dependency.
389400
$wc = WC();
390401

391-
return $wc ? $wc->countries : null;
402+
return $wc->countries;
403+
}
404+
405+
private function is_paypal_supported_country( string $country_code ): bool {
406+
return in_array( $country_code, self::PAYPAL_SUPPORTED_COUNTRIES, true );
392407
}
393408
}

tests/PHPUnit/StoreSync/CartValidation/CouponValidatorTest.php

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@
1111
use WooCommerce\PayPalCommerce\StoreSync\CartValidation\CouponValidator\CouponContextBuilder;
1212
use WooCommerce\PayPalCommerce\StoreSync\CartValidation\CouponValidator\DiscountCalculator;
1313
use WooCommerce\PayPalCommerce\StoreSync\CartValidation\CouponValidator\CouponResolutionBuilder;
14-
use WooCommerce\PayPalCommerce\TestCase;
1514

1615
/**
1716
* @covers \WooCommerce\PayPalCommerce\StoreSync\CartValidation\CouponValidator\CouponValidator
1817
*/
19-
class CouponValidatorTest extends TestCase
18+
class CouponValidatorTest extends ValidationTest
2019
{
2120

2221
private CouponValidator $validator;
@@ -86,14 +85,8 @@ public function test_validate_returns_error_when_coupons_disabled(): void
8685
$this->assertIsArray($result);
8786
$this->assertCount(1, $result);
8887

89-
$issue = $result[0];
90-
$data = $issue->to_array();
91-
92-
$this->assertSame('PRICING_ERROR', $data['code']);
93-
$this->assertSame('BUSINESS_RULE', $data['type']);
94-
$this->assertSame('Coupons are not enabled', $data['message']);
95-
$this->assertStringContainsString('does not accept coupon codes', $data['user_message']);
96-
$this->assertSame('coupons', $data['field']);
88+
$data = $result[0]->to_array();
89+
$this->assertValidationIssue($data, 'PRICING_ERROR', 'BUSINESS_RULE', 'coupons', 'Coupons are not enabled');
9790
}
9891

9992
public function test_validate_filters_only_apply_actions(): void
@@ -118,9 +111,7 @@ public function test_coupon_invalid_issue_has_correct_error_code(): void
118111
->for_field( 'coupons[0]' );
119112

120113
$data = $issue->to_array();
121-
122-
$this->assertSame( 'PRICING_ERROR', $data['code'] );
123-
$this->assertSame( 'BUSINESS_RULE', $data['type'] );
114+
$this->assertValidationIssue( $data, 'PRICING_ERROR', 'BUSINESS_RULE' );
124115
}
125116

126117
public function test_coupon_invalid_truncates_long_messages(): void
@@ -132,6 +123,7 @@ public function test_coupon_invalid_truncates_long_messages(): void
132123
->user_message( $long_user_message );
133124

134125
$data = $issue->to_array();
126+
$this->assertValidationIssue( $data, 'PRICING_ERROR', 'BUSINESS_RULE' );
135127

136128
$this->assertSame( 255, strlen( $data['message'] ) );
137129
$this->assertSame( 500, strlen( $data['user_message'] ) );

tests/PHPUnit/StoreSync/CartValidation/CurrencyValidatorTest.php

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@
44
namespace WooCommerce\PayPalCommerce\StoreSync\CartValidation;
55

66
use WooCommerce\PayPalCommerce\StoreSync\Schema\PayPalCart;
7-
use WooCommerce\PayPalCommerce\StoreSync\Validation\ValidationIssue;
8-
use WooCommerce\PayPalCommerce\TestCase;
9-
107
use function Brain\Monkey\Functions\when;
118

129
/**
1310
* @covers \WooCommerce\PayPalCommerce\StoreSync\CartValidation\CurrencyValidator
1411
*/
15-
class CurrencyValidatorTest extends TestCase {
12+
class CurrencyValidatorTest extends ValidationTest {
1613

1714
private CurrencyValidator $validator;
1815

@@ -51,11 +48,9 @@ public function test_validate_detects_mixed_currencies(): void {
5148

5249
$this->assertIsArray( $result );
5350
$this->assertCount( 1, $result );
54-
$this->assertInstanceOf( ValidationIssue::class, $result[0] );
5551

5652
$issue_data = $result[0]->to_array();
57-
$this->assertStringContainsString( 'Mixed currencies detected', $issue_data['message'] );
58-
$this->assertSame( 'items[1].price.currency_code', $issue_data['field'] );
53+
$this->assertValidationIssue( $issue_data, 'PRICING_ERROR', 'BUSINESS_RULE', 'items[1].price.currency_code', 'Mixed currencies detected' );
5954
}
6055

6156
public function test_validate_detects_store_currency_mismatch(): void {
@@ -72,12 +67,9 @@ public function test_validate_detects_store_currency_mismatch(): void {
7267

7368
$this->assertIsArray( $result );
7469
$this->assertCount( 1, $result );
75-
$this->assertInstanceOf( ValidationIssue::class, $result[0] );
7670

7771
$issue_data = $result[0]->to_array();
78-
$this->assertStringContainsString( 'Cart currency EUR does not match store currency USD', $issue_data['message'] );
79-
$this->assertStringContainsString( 'This store only accepts payments in USD', $issue_data['user_message'] );
80-
$this->assertSame( 'items[0].price.currency_code', $issue_data['field'] );
72+
$this->assertValidationIssue( $issue_data, 'PRICING_ERROR', 'BUSINESS_RULE', 'items[0].price.currency_code', 'Cart currency EUR does not match store currency USD' );
8173
}
8274

8375
public function test_validate_returns_null_for_empty_cart(): void {
@@ -165,13 +157,9 @@ public function test_detects_mismatch_skipping_empty_items(): void {
165157

166158
$this->assertIsArray( $result );
167159
$this->assertCount( 1, $result );
168-
$this->assertInstanceOf( ValidationIssue::class, $result[0] );
169160

170161
$issue_data = $result[0]->to_array();
171-
$this->assertStringContainsString( 'Mixed currencies detected', $issue_data['message'] );
172-
$this->assertStringContainsString( 'EUR', $issue_data['message'] );
173-
$this->assertStringContainsString( 'USD', $issue_data['message'] );
174-
$this->assertSame( 'items[2].price.currency_code', $issue_data['field'] );
162+
$this->assertValidationIssue( $issue_data, 'PRICING_ERROR', 'BUSINESS_RULE', 'items[2].price.currency_code', 'Mixed currencies detected' );
175163
}
176164

177165
public function test_store_mismatch_points_to_correct_index(): void {
@@ -203,11 +191,9 @@ public function test_store_mismatch_points_to_correct_index(): void {
203191

204192
$this->assertIsArray( $result );
205193
$this->assertCount( 1, $result );
206-
$this->assertInstanceOf( ValidationIssue::class, $result[0] );
207194

208195
$issue_data = $result[0]->to_array();
209-
$this->assertStringContainsString( 'Cart currency EUR does not match store currency USD', $issue_data['message'] );
210-
$this->assertSame( 'items[1].price.currency_code', $issue_data['field'] );
196+
$this->assertValidationIssue( $issue_data, 'PRICING_ERROR', 'BUSINESS_RULE', 'items[1].price.currency_code', 'Cart currency EUR does not match store currency USD' );
211197
}
212198

213199
public function test_mixed_currency_prevents_store_check(): void {
@@ -224,10 +210,9 @@ public function test_mixed_currency_prevents_store_check(): void {
224210

225211
$this->assertIsArray( $result );
226212
$this->assertCount( 1, $result );
227-
$this->assertInstanceOf( ValidationIssue::class, $result[0] );
228213

229214
$issue = $result[0]->to_array();
230-
$this->assertStringContainsString( 'Mixed currencies detected', $issue['message'] );
215+
$this->assertValidationIssue( $issue, 'PRICING_ERROR', 'BUSINESS_RULE', null, 'Mixed currencies detected' );
231216
}
232217

233218
/**

0 commit comments

Comments
 (0)