@@ -285,15 +285,145 @@ private function transform_ece_address_state_data( $address ) {
285
285
return $ address ;
286
286
}
287
287
288
- // states from Apple Pay or Google Pay might be in long format, we need their short format.
288
+ // Due to a bug in Apple Pay, the "Region" part of a Hong Kong address is delivered in
289
+ // `shipping_postcode`, so we need some special case handling for that. According to
290
+ // our sources at Apple Pay people will sometimes use the district or even sub-district
291
+ // for this value. As such we check against all regions, districts, and sub-districts
292
+ // with both English and Mandarin spelling.
293
+ //
294
+ // @reykjalin: The check here is quite elaborate in an attempt to make sure this doesn't break once
295
+ // Apple Pay fixes the bug that causes address values to be in the wrong place. Because of that the
296
+ // algorithm becomes:
297
+ // 1. Use the supplied state if it's valid (in case Apple Pay bug is fixed)
298
+ // 2. Use the value supplied in the postcode if it's a valid HK region (equivalent to a WC state).
299
+ // 3. Fall back to the value supplied in the state. This will likely cause a validation error, in
300
+ // which case a merchant can reach out to us so we can either: 1) add whatever the customer used
301
+ // as a state to our list of valid states; or 2) let them know the customer must spell the state
302
+ // in some way that matches our list of valid states.
303
+ //
304
+ // @reykjalin: This HK specific sanitazation *should be removed* once Apple Pay fix
305
+ // the address bug. More info on that in pc4etw-bY-p2.
306
+ if ( Country_Code::HONG_KONG === $ country ) {
307
+ include_once WCPAY_ABSPATH . 'includes/constants/class-express-checkout-hong-kong-states.php ' ;
308
+
309
+ $ state = $ address ['state ' ] ?? '' ;
310
+ if ( ! \WCPay \Constants \Express_Checkout_Hong_Kong_States::is_valid_state ( strtolower ( $ state ) ) ) {
311
+ $ postcode = $ address ['postcode ' ] ?? '' ;
312
+ if ( strtolower ( $ postcode ) === 'hongkong ' ) {
313
+ $ postcode = 'hong kong ' ;
314
+ }
315
+ if ( \WCPay \Constants \Express_Checkout_Hong_Kong_States::is_valid_state ( strtolower ( $ postcode ) ) ) {
316
+ $ address ['state ' ] = $ postcode ;
317
+ }
318
+ }
319
+ }
320
+
321
+ // States from Apple Pay or Google Pay are in long format, we need their short format.
289
322
$ state = $ address ['state ' ] ?? '' ;
290
323
if ( ! empty ( $ state ) ) {
291
- $ address ['state ' ] = $ this ->express_checkout_button_helper -> get_normalized_state ( $ state , $ country );
324
+ $ address ['state ' ] = $ this ->get_normalized_state ( $ state , $ country );
292
325
}
293
326
294
327
return $ address ;
295
328
}
296
329
330
+ /**
331
+ * Gets the normalized state/county field because in some
332
+ * cases, the state/county field is formatted differently from
333
+ * what WC is expecting and throws an error. An example
334
+ * for Ireland, the county dropdown in Chrome shows "Co. Clare" format.
335
+ *
336
+ * @param string $state Full state name or an already normalized abbreviation.
337
+ * @param string $country Two-letter country code.
338
+ *
339
+ * @return string Normalized state abbreviation.
340
+ */
341
+ private function get_normalized_state ( $ state , $ country ) {
342
+ // If it's empty or already normalized, skip.
343
+ if ( ! $ state || $ this ->is_normalized_state ( $ state , $ country ) ) {
344
+ return $ state ;
345
+ }
346
+
347
+ // Try to match state from the Express Checkout API list of states.
348
+ $ state = $ this ->get_normalized_state_from_ece_states ( $ state , $ country );
349
+
350
+ // If it's normalized, return.
351
+ if ( $ this ->is_normalized_state ( $ state , $ country ) ) {
352
+ return $ state ;
353
+ }
354
+
355
+ // If the above doesn't work, fallback to matching against the list of translated
356
+ // states from WooCommerce.
357
+ return $ this ->get_normalized_state_from_wc_states ( $ state , $ country );
358
+ }
359
+
360
+ /**
361
+ * Checks if given state is normalized.
362
+ *
363
+ * @param string $state State.
364
+ * @param string $country Two-letter country code.
365
+ *
366
+ * @return bool Whether state is normalized or not.
367
+ */
368
+ private function is_normalized_state ( $ state , $ country ) {
369
+ $ wc_states = WC ()->countries ->get_states ( $ country );
370
+ return is_array ( $ wc_states ) && array_key_exists ( $ state , $ wc_states );
371
+ }
372
+
373
+ /**
374
+ * Get normalized state from Express Checkout API dropdown list of states.
375
+ *
376
+ * @param string $state Full state name or state code.
377
+ * @param string $country Two-letter country code.
378
+ *
379
+ * @return string Normalized state or original state input value.
380
+ */
381
+ private function get_normalized_state_from_ece_states ( $ state , $ country ) {
382
+ // Include Express Checkout Element API State list for compatibility with WC countries/states.
383
+ include_once WCPAY_ABSPATH . 'includes/constants/class-express-checkout-element-states.php ' ;
384
+ $ pr_states = \WCPay \Constants \Express_Checkout_Element_States::STATES ;
385
+
386
+ if ( ! isset ( $ pr_states [ $ country ] ) ) {
387
+ return $ state ;
388
+ }
389
+
390
+ foreach ( $ pr_states [ $ country ] as $ wc_state_abbr => $ pr_state ) {
391
+ $ sanitized_state_string = $ this ->express_checkout_button_helper ->sanitize_string ( $ state );
392
+ // Checks if input state matches with Express Checkout state code (0), name (1) or localName (2).
393
+ if (
394
+ ( ! empty ( $ pr_state [0 ] ) && $ sanitized_state_string === $ this ->express_checkout_button_helper ->sanitize_string ( $ pr_state [0 ] ) ) ||
395
+ ( ! empty ( $ pr_state [1 ] ) && $ sanitized_state_string === $ this ->express_checkout_button_helper ->sanitize_string ( $ pr_state [1 ] ) ) ||
396
+ ( ! empty ( $ pr_state [2 ] ) && $ sanitized_state_string === $ this ->express_checkout_button_helper ->sanitize_string ( $ pr_state [2 ] ) )
397
+ ) {
398
+ return $ wc_state_abbr ;
399
+ }
400
+ }
401
+
402
+ return $ state ;
403
+ }
404
+
405
+ /**
406
+ * Get normalized state from WooCommerce list of translated states.
407
+ *
408
+ * @param string $state Full state name or state code.
409
+ * @param string $country Two-letter country code.
410
+ *
411
+ * @return string Normalized state or original state input value.
412
+ */
413
+ private function get_normalized_state_from_wc_states ( $ state , $ country ) {
414
+ $ wc_states = WC ()->countries ->get_states ( $ country );
415
+
416
+ if ( is_array ( $ wc_states ) ) {
417
+ foreach ( $ wc_states as $ wc_state_abbr => $ wc_state_value ) {
418
+ if ( preg_match ( '/ ' . preg_quote ( $ wc_state_value , '/ ' ) . '/i ' , $ state ) ) {
419
+ return $ wc_state_abbr ;
420
+ }
421
+ }
422
+ }
423
+
424
+ return $ state ;
425
+ }
426
+
297
427
/**
298
428
* Transform a Google Pay/Apple Pay postcode address data fields into values that are valid for WooCommerce.
299
429
*
@@ -310,12 +440,36 @@ private function transform_ece_address_postcode_data( $address ) {
310
440
// Normalizes postal code in case of redacted data from Apple Pay or Google Pay.
311
441
$ postcode = $ address ['postcode ' ] ?? '' ;
312
442
if ( ! empty ( $ postcode ) ) {
313
- $ address ['postcode ' ] = $ this ->express_checkout_button_helper -> get_normalized_postal_code ( $ postcode , $ country );
443
+ $ address ['postcode ' ] = $ this ->get_normalized_postal_code ( $ postcode , $ country );
314
444
}
315
445
316
446
return $ address ;
317
447
}
318
448
449
+ /**
450
+ * Normalizes postal code in case of redacted data from Apple Pay.
451
+ *
452
+ * @param string $postcode Postal code.
453
+ * @param string $country Country.
454
+ */
455
+ private function get_normalized_postal_code ( $ postcode , $ country ) {
456
+ /**
457
+ * Currently, Apple Pay truncates the UK and Canadian postal codes to the first 4 and 3 characters respectively
458
+ * when passing it back from the shippingcontactselected object. This causes WC to invalidate
459
+ * the postal code and not calculate shipping zones correctly.
460
+ */
461
+ if ( Country_Code::UNITED_KINGDOM === $ country ) {
462
+ // Replaces a redacted string with something like N1C0000.
463
+ return str_pad ( preg_replace ( '/\s+/ ' , '' , $ postcode ), 7 , '0 ' );
464
+ }
465
+ if ( Country_Code::CANADA === $ country ) {
466
+ // Replaces a redacted string with something like H3B000.
467
+ return str_pad ( preg_replace ( '/\s+/ ' , '' , $ postcode ), 6 , '0 ' );
468
+ }
469
+
470
+ return $ postcode ;
471
+ }
472
+
319
473
/**
320
474
* Modify country locale settings to handle express checkout address requirements.
321
475
*
0 commit comments