$destination A package's `destination`. */ public static function fromPackage( array $destination ): ?self { $country = strtoupper( trim( is_string( $destination['country'] ?? null ) ? $destination['country'] : '' ) ); $postcode = strtoupper( (string) preg_replace( '/\s+/', '', is_string( $destination['postcode'] ?? null ) ? $destination['postcode'] : '' ) ); if ( 1 !== preg_match( '/^[A-Z]{2}$/', $country ) ) { return null; } if ( 'CA' === $country ) { $postcode = self::canadianPostcode( $postcode ); return null === $postcode ? null : new self( $country, $postcode, Services::DOMESTIC ); } if ( 'US' === $country ) { if ( 1 === preg_match( '/^(\d{5})-?(\d{4})$/', $postcode, $zip ) ) { $postcode = $zip[1] . '-' . $zip[2]; } return 1 === preg_match( '/^\d{5}(-\d{4})?$/', $postcode ) ? new self( $country, $postcode, Services::UNITED_STATES ) : null; } // Canada Post accepts up to 10 characters; anything longer is not a postal code. return new self( $country, strlen( $postcode ) <= 10 ? $postcode : '', Services::INTERNATIONAL ); } /** "V2S6X1" from "v2s 6x1", or null if it is not a Canadian postal code. */ public static function canadianPostcode( string $value ): ?string { $value = strtoupper( (string) preg_replace( '/\s+/', '', $value ) ); return 1 === preg_match( '/^[A-Z]\d[A-Z]\d[A-Z]\d$/', $value ) ? $value : null; } /** @return array> The mailingScenario `destination`. */ public function toApi(): array { return match ( $this->group ) { Services::DOMESTIC => array( 'domestic' => array( 'postalCode' => $this->postcode ) ), Services::UNITED_STATES => array( 'unitedStates' => array( 'zipCode' => $this->postcode ) ), default => array( 'international' => array_filter( array( 'countryCode' => $this->country, 'postalCode' => $this->postcode, ), static fn ( string $value ): bool => '' !== $value ), ), }; } }