diff --git a/vendor/magento/module-shipping/Block/Adminhtml/Order/Packaging.php b/vendor/magento/module-shipping/Block/Adminhtml/Order/Packaging.php index f66b37a9cd34f..2523d77289654 100644 --- a/vendor/magento/module-shipping/Block/Adminhtml/Order/Packaging.php +++ b/vendor/magento/module-shipping/Block/Adminhtml/Order/Packaging.php @@ -1,7 +1,7 @@ getShipment()->getId(); $orderId = $this->getRequest()->getParam('order_id'); - $urlParams = []; - $itemsQty = []; $itemsPrice = []; $itemsName = []; $itemsWeight = []; $itemsProductId = []; $itemsOrderItemId = []; - if ($shipmentId) { - $urlParams['shipment_id'] = $shipmentId; - $createLabelUrl = $this->getUrl('adminhtml/order_shipment/createLabel', $urlParams); - $itemsGridUrl = $this->getUrl('adminhtml/order_shipment/getShippingItemsGrid', $urlParams); - foreach ($this->getShipment()->getAllItems() as $item) { - $itemsQty[$item->getId()] = $item->getQty(); - $itemsPrice[$item->getId()] = $item->getPrice(); - $itemsName[$item->getId()] = $item->getName(); - $itemsWeight[$item->getId()] = $item->getWeight(); - $itemsProductId[$item->getId()] = $item->getProductId(); - $itemsOrderItemId[$item->getId()] = $item->getOrderItemId(); - } - } else { - if ($orderId) { - $urlParams['order_id'] = $orderId; - $createLabelUrl = $this->getUrl('adminhtml/order_shipment/save', $urlParams); - $itemsGridUrl = $this->getUrl('adminhtml/order_shipment/getShippingItemsGrid', $urlParams); - foreach ($this->getShipment()->getAllItems() as $item) { - $itemsQty[$item->getOrderItemId()] = $item->getQty() * 1; - $itemsPrice[$item->getOrderItemId()] = $item->getPrice(); - $itemsName[$item->getOrderItemId()] = $item->getName(); - $itemsWeight[$item->getOrderItemId()] = $item->getWeight(); - $itemsProductId[$item->getOrderItemId()] = $item->getProductId(); - $itemsOrderItemId[$item->getOrderItemId()] = $item->getOrderItemId(); - } - } + $urlParams = $shipmentId ? ['shipment_id' => $shipmentId] : ($orderId ? ['order_id' => $orderId] : []); + $createLabelUrl = $this->getUrl( + $shipmentId ? 'adminhtml/order_shipment/createLabel' : 'adminhtml/order_shipment/save', + $urlParams + ); + $itemsGridUrl = $this->getUrl('adminhtml/order_shipment/getShippingItemsGrid', $urlParams); + + foreach ($this->getShipment()->getAllItems() as $item) { + $itemsQty[$item->getOrderItemId()] = $item->getQty() * 1; + $itemsPrice[$item->getOrderItemId()] = $item->getPrice(); + $itemsName[$item->getOrderItemId()] = $item->getName(); + $itemsWeight[$item->getOrderItemId()] = $item->getWeight(); + $itemsProductId[$item->getOrderItemId()] = $item->getProductId(); + $itemsOrderItemId[$item->getOrderItemId()] = $item->getOrderItemId(); } + $data = [ 'createLabelUrl' => $createLabelUrl, 'itemsGridUrl' => $itemsGridUrl, diff --git a/vendor/magento/module-usps/Model/Carrier.php b/vendor/magento/module-usps/Model/Carrier.php index 41174cf6e6f27..f9da5c968b639 100644 --- a/vendor/magento/module-usps/Model/Carrier.php +++ b/vendor/magento/module-usps/Model/Carrier.php @@ -1,7 +1,7 @@ _carrierHelper = $carrierHelper; $this->_productCollectionFactory = $productCollectionFactory; $this->_httpClientFactory = $httpClientFactory; + $this->uspsAuth = $uspsAuth; + $this->shipmentService = $shipmentService; + $this->shipmentService->setCarrierModel($this); + $this->trackingService = $trackingService; + $this->trackingService->setCarrierModel($this); + $this->shippingMethodManager = $shippingMethodManager; parent::__construct( $scopeConfig, $rateErrorFactory, @@ -445,17 +487,26 @@ public function getResult() */ public function isShippingLabelsAvailable() { - return false; + if ($this->isXmlRequest()) { + return false; + } else { + return true; + } } /** * Get quotes * * @return Result + * @throws LocalizedException */ protected function _getQuotes() { - return $this->_getXmlQuotes(); + if ($this->isXmlRequest()) { + return $this->_getXmlQuotes(); + } else { + return $this->shipmentService->getJsonQuotes(); + } } /** @@ -612,11 +663,14 @@ function () use ($deferredResponse, $request, $debugData) { try { $responseResult = $deferredResponse->get(); } catch (HttpException $exception) { - $this->_logger->critical($exception); + $this->_logger->critical( + 'Critical error: ' . $exception->getMessage(), + ['exception' => $exception] + ); } $responseBody = $responseResult ? $responseResult->getBody() : ''; $debugData['result'] = $responseBody; - $this->_setCachedQuotes($request, $responseBody); + $this->setCachedQuotes($request, $responseBody); $this->_debug($debugData); return $this->_parseXmlResponse($responseBody); @@ -817,6 +871,7 @@ public function getCode($type, $code = '') '2096' => __('Ground Advantage™ Cubic Hold for pickup'), '6096' => __('Ground Advantage™ Cubic Parcel locker') ], + 'rest_method' => $this->shippingMethodManager->getMethodCodesWithTitles(), 'service_to_code' => [ '0_FCLE' => 'First Class', '0_FCL' => 'First Class', @@ -928,13 +983,30 @@ public function getCode($type, $code = '') 'within_us' => [ 'method' => [ '13', '27', '16', '22', '17', '28', '2', '3', '1', '33', '34', '35', - '36', '37', '42', '43', '53', '4', '6', '15', '23', '25', '57' + '36', '37', '42', '43', '53', '4', '6', '15', '23', '25', '57', + 'PRIORITY_MAIL_EXPRESS_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_MACHINABLE_LARGE_FLAT_RATE_BOX', + 'PRIORITY_MAIL_MACHINABLE_MEDIUM_FLAT_RATE_BOX', + 'PRIORITY_MAIL_MACHINABLE_SMALL_FLAT_RATE_BOX', + 'PRIORITY_MAIL_MACHINABLE_SINGLE-PIECE', + 'PRIORITY_MAIL_EXPRESS_MACHINABLE_SINGLE-PIECE', + 'MEDIA_MAIL_MACHINABLE_5-DIGIT', + 'USPS_GROUND_ADVANTAGE_MACHINABLE_SINGLE-PIECE' ], ], 'from_us' => [ 'method' => [ 'INT_10', 'INT_8', 'INT_11', 'INT_9', 'INT_16', 'INT_20', 'INT_4', - 'INT_12', 'INT_1', 'INT_2', 'INT_13', 'INT_14', 'INT_15' + 'INT_12', 'INT_1', 'INT_2', 'INT_13', 'INT_14', 'INT_15', + 'PRIORITY_MAIL_INTERNATIONAL_ISC_SINGLE-PIECE', + 'PRIORITY_MAIL_INTERNATIONAL_ISC_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_MEDIUM_FLAT_RATE_BOX', + 'PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_LARGE_FLAT_RATE_BOX', + 'PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_SMALL_FLAT_RATE_BOX', + 'PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_SINGLE-PIECE', + 'FIRST-CLASS_PACKAGE_INTERNATIONAL_SERVICE_MACHINABLE_ISC_SINGLE-PIECE' ], ], ], @@ -943,10 +1015,13 @@ public function getCode($type, $code = '') 'containers' => ['SM FLAT RATE BOX'], 'filters' => [ 'within_us' => [ - 'method' => ['28', '57'], + 'method' => ['28', '57', 'PRIORITY_MAIL_MACHINABLE_SMALL_FLAT_RATE_BOX', + ], ], 'from_us' => [ - 'method' => ['INT_16', 'INT_24'], + 'method' => ['INT_16', 'INT_24', + 'PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_SMALL_FLAT_RATE_BOX' + ], ], ] ], @@ -954,10 +1029,13 @@ public function getCode($type, $code = '') 'containers' => ['MD FLAT RATE BOX'], 'filters' => [ 'within_us' => [ - 'method' => ['17', '57'], + 'method' => ['17', '57', 'PRIORITY_MAIL_MACHINABLE_MEDIUM_FLAT_RATE_BOX' + ], ], 'from_us' => [ - 'method' => ['INT_9', 'INT_24'], + 'method' => ['INT_9', 'INT_24', + 'PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_MEDIUM_FLAT_RATE_BOX' + ], ], ] ], @@ -965,10 +1043,12 @@ public function getCode($type, $code = '') 'containers' => ['LG FLAT RATE BOX'], 'filters' => [ 'within_us' => [ - 'method' => ['22', '57'], + 'method' => ['22', '57', 'PRIORITY_MAIL_MACHINABLE_LARGE_FLAT_RATE_BOX'], ], 'from_us' => [ - 'method' => ['INT_11', 'INT_24', 'INT_25'], + 'method' => ['INT_11', 'INT_24', 'INT_25', + 'PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_LARGE_FLAT_RATE_BOX' + ], ], ] ], @@ -1009,21 +1089,33 @@ public function getCode($type, $code = '') 'containers' => ['PADDED FLAT RATE ENVELOPE'], 'filters' => [ 'within_us' => [ - 'method' => ['62', '63', '64', '46', '29'], + 'method' => ['62', '63', '64', '46', '29', + 'PRIORITY_MAIL_EXPRESS_PADDED_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_PADDED_FLAT_RATE_ENVELOPE' + ], ], 'from_us' => [ - 'method' => ['INT_27', 'INT_23'], - ], + 'method' => ['INT_27', 'INT_23', + 'PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_PADDED_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_PADDED_FLAT_RATE_ENVELOPE' + ], + ], ] ], [ 'containers' => ['LEGAL FLAT RATE ENVELOPE'], 'filters' => [ 'within_us' => [ - 'method' => ['44', '45', '30', '31', '32'], + 'method' => ['44', '45', '30', '31', '32', + 'PRIORITY_MAIL_EXPRESS_LEGAL_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_LEGAL_FLAT_RATE_ENVELOPE' + ], ], 'from_us' => [ - 'method' => ['INT_17', 'INT_22'], + 'method' => ['INT_17', 'INT_22', + 'PRIORITY_MAIL_INTERNATIONAL_ISC_LEGAL_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_LEGAL_FLAT_RATE_ENVELOPE' + ], ], ] ], @@ -1031,11 +1123,16 @@ public function getCode($type, $code = '') 'containers' => ['FLAT RATE ENVELOPE'], 'filters' => [ 'within_us' => [ - 'method' => ['16', '13', '27', '16', '15', '37', '42', '43', '25', '62'], + 'method' => ['16', '13', '27', '16', '15', '37', '42', '43', '25', '62', + 'PRIORITY_MAIL_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_EXPRESS_FLAT_RATE_ENVELOPE' + ], ], 'from_us' => [ 'method' => [ - 'INT_10', 'INT_8', 'INT_14', 'INT_20', 'INT_17', 'INT_18', 'INT_19', 'INT_22', 'INT_27' + 'INT_10', 'INT_8', 'INT_14', 'INT_20', 'INT_17', 'INT_18', 'INT_19', 'INT_22', 'INT_27', + 'PRIORITY_MAIL_INTERNATIONAL_ISC_FLAT_RATE_ENVELOPE', + 'PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_FLAT_RATE_ENVELOPE' ], ], ] @@ -1044,10 +1141,21 @@ public function getCode($type, $code = '') 'containers' => ['RECTANGULAR'], 'filters' => [ 'within_us' => [ - 'method' => ['3', '1', '4', '6', '7', '61'], + 'method' => ['3', '1', '4', '6', '7', '61', + 'LIBRARY_MAIL_MACHINABLE_5-DIGIT', + 'MEDIA_MAIL_MACHINABLE_5-DIGIT', + 'USPS_GROUND_ADVANTAGE_MACHINABLE_SINGLE-PIECE', + 'PRIORITY_MAIL_MACHINABLE_SINGLE-PIECE', + 'PRIORITY_MAIL_EXPRESS_MACHINABLE_SINGLE-PIECE' + ], ], 'from_us' => [ - 'method' => ['INT_12', 'INT_1', 'INT_2', 'INT_15'], + 'method' => ['INT_12', 'INT_1', 'INT_2', 'INT_15', 'INT_13', 'INT_14', 'INT_16', 'INT_20', + 'INT_21', 'INT_22', 'INT_23', 'INT_24', 'INT_25', 'INT_27', + 'FIRST-CLASS_PACKAGE_INTERNATIONAL_SERVICE_MACHINABLE_ISC_SINGLE-PIECE', + 'PRIORITY_MAIL_INTERNATIONAL_ISC_SINGLE-PIECE', + 'PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_SINGLE-PIECE' + ], ], ] ], @@ -1055,17 +1163,32 @@ public function getCode($type, $code = '') 'containers' => ['NONRECTANGULAR'], 'filters' => [ 'within_us' => [ - 'method' => ['3', '1', '4', '6', '7'], + 'method' => ['3', '1', '4', '6', '7', + 'LIBRARY_MAIL_MACHINABLE_5-DIGIT', + 'MEDIA_MAIL_MACHINABLE_5-DIGIT', + 'USPS_GROUND_ADVANTAGE_MACHINABLE_SINGLE-PIECE', + 'PRIORITY_MAIL_MACHINABLE_SINGLE-PIECE', + 'PRIORITY_MAIL_EXPRESS_MACHINABLE_SINGLE-PIECE' + ], ], 'from_us' => [ - 'method' => ['INT_4', 'INT_1', 'INT_2', 'INT_15'], + 'method' => ['INT_4', 'INT_1', 'INT_2', 'INT_15', 'INT_13', 'INT_14', 'INT_16', 'INT_20', + 'INT_21', 'INT_22', 'INT_23', 'INT_24', 'INT_25', 'INT_27', 'INT_28', 'INT_29', + 'INT_30', 'INT_31', 'INT_32', 'INT_33', 'INT_34', 'INT_35', 'INT_36', 'INT_37', + 'INT_38', 'INT_39', 'INT_40', 'INT_41', 'INT_42', 'INT_43', 'INT_44', 'INT_45', + 'INT_46', 'INT_47', 'INT_48', 'INT_49', 'INT_50', 'INT_53', 'INT_58', 'INT_59', + 'INT_61', 'INT_62', 'INT_63', 'INT_64', + 'FIRST-CLASS_PACKAGE_INTERNATIONAL_SERVICE_MACHINABLE_ISC_SINGLE-PIECE', + 'PRIORITY_MAIL_INTERNATIONAL_ISC_SINGLE-PIECE', + 'PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_SINGLE-PIECE', + ], ], ] ], ], 'size' => ['REGULAR' => __('Regular'), 'LARGE' => __('Large')], 'machinable' => ['true' => __('Yes'), 'false' => __('No')], - 'delivery_confirmation_types' => ['True' => __('Not Required'), 'False' => __('Required')], + 'delivery_confirmation_types' => ['True' => __('Not Required'), 'False' => __('Required')] ]; if (!isset($codes[$type])) { @@ -1095,7 +1218,11 @@ public function getTracking($trackings) $trackings = [$trackings]; } - $this->_getXmlTracking($trackings); + if ($this->isXmlRequest()) { + $this->_getXmlTracking($trackings); + } else { + return $this->trackingService->getRestTracking($trackings); + } return $this->_result; } @@ -1166,6 +1293,7 @@ protected function _getXmlTracking($trackings) * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @throws LocalizedException */ protected function _parseXmlTrackingResponse($trackingvalue, $response) { @@ -1268,7 +1396,7 @@ public function getAllowedMethods() * @return string|false * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - protected function _getCountryName($countryId) + public function _getCountryName($countryId) { $countries = [ 'AD' => 'Andorra', @@ -1968,89 +2096,93 @@ protected function _formIntlShipmentRequest(\Magento\Framework\DataObject $reque protected function _doShipmentRequest(\Magento\Framework\DataObject $request) { $this->_prepareShipmentRequest($request); - $result = new \Magento\Framework\DataObject(); - $service = $this->getCode('service_to_code', $request->getShippingMethod()); - $recipientUSCountry = $this->_isUSCountry($request->getRecipientAddressCountryCode()); - - if ($recipientUSCountry && $service == 'Priority Express') { - $requestXml = $this->_formUsExpressShipmentRequest($request); - $api = 'ExpressMailLabel'; - } else { - if ($recipientUSCountry) { - $requestXml = $this->_formUsSignatureConfirmationShipmentRequest($request, $service); - if ($this->getConfigData('mode')) { - $api = 'SignatureConfirmationV3'; - } else { - $api = 'SignatureConfirmationCertifyV3'; - } + if ($this->isXmlRequest()) { + $result = new \Magento\Framework\DataObject(); + $service = $this->getCode('service_to_code', $request->getShippingMethod()); + $recipientUSCountry = $this->_isUSCountry($request->getRecipientAddressCountryCode()); + + if ($recipientUSCountry && $service == 'Priority Express') { + $requestXml = $this->_formUsExpressShipmentRequest($request); + $api = 'ExpressMailLabel'; } else { - if ($service == 'First Class') { - $requestXml = $this->_formIntlShipmentRequest($request); - $api = 'FirstClassMailIntl'; + if ($recipientUSCountry) { + $requestXml = $this->_formUsSignatureConfirmationShipmentRequest($request, $service); + if ($this->getConfigData('mode')) { + $api = 'SignatureConfirmationV3'; + } else { + $api = 'SignatureConfirmationCertifyV3'; + } } else { - if ($service == 'Priority') { + if ($service == 'First Class') { $requestXml = $this->_formIntlShipmentRequest($request); - $api = 'PriorityMailIntl'; + $api = 'FirstClassMailIntl'; } else { - $requestXml = $this->_formIntlShipmentRequest($request); - $api = 'ExpressMailIntl'; + if ($service == 'Priority') { + $requestXml = $this->_formIntlShipmentRequest($request); + $api = 'PriorityMailIntl'; + } else { + $requestXml = $this->_formIntlShipmentRequest($request); + $api = 'ExpressMailIntl'; + } } } } - } - $debugData = ['request' => $this->filterDebugData($requestXml)]; - $url = $this->getConfigData('gateway_secure_url'); - if (!$url) { - $url = $this->_defaultGatewayUrl; - } - /** @var LaminasClient $client */ - $client = $this->_httpClientFactory->create(); - $client->setUri($url); - $client->setOptions(['maxredirects' => 0, 'timeout' => 30]); - $client->setParameterGet( - [ - 'API' => $api, - 'XML' => $requestXml - ] - ); - $response = $client->send()->getBody(); - - $response = $this->parseXml($response); - - if ($response !== false) { - if ($response->getName() == 'Error') { - $debugData['result'] = [ - 'error' => $response->Description, - 'code' => $response->Number, - 'xml' => $response->asXML(), - ]; - $this->_debug($debugData); - $result->setErrors($debugData['result']['error']); - } else { - if ($recipientUSCountry && $service == 'Priority Express') { - // phpcs:disable Magento2.Functions.DiscouragedFunction - $labelContent = base64_decode((string)$response->EMLabel); - $trackingNumber = (string)$response->EMConfirmationNumber; - } elseif ($recipientUSCountry) { - // phpcs:disable Magento2.Functions.DiscouragedFunction - $labelContent = base64_decode((string)$response->SignatureConfirmationLabel); - $trackingNumber = (string)$response->SignatureConfirmationNumber; + $debugData = ['request' => $this->filterDebugData($requestXml)]; + $url = $this->getConfigData('gateway_secure_url'); + if (!$url) { + $url = $this->_defaultGatewayUrl; + } + /** @var LaminasClient $client */ + $client = $this->_httpClientFactory->create(); + $client->setUri($url); + $client->setOptions(['maxredirects' => 0, 'timeout' => 30]); + $client->setParameterGet( + [ + 'API' => $api, + 'XML' => $requestXml + ] + ); + $response = $client->send()->getBody(); + + $response = $this->parseXml($response); + + if ($response !== false) { + if ($response->getName() == 'Error') { + $debugData['result'] = [ + 'error' => $response->Description, + 'code' => $response->Number, + 'xml' => $response->asXML(), + ]; + $this->_debug($debugData); + $result->setErrors($debugData['result']['error']); } else { - // phpcs:disable Magento2.Functions.DiscouragedFunction - $labelContent = base64_decode((string)$response->LabelImage); - $trackingNumber = (string)$response->BarcodeNumber; + if ($recipientUSCountry && $service == 'Priority Express') { + // phpcs:disable Magento2.Functions.DiscouragedFunction + $labelContent = base64_decode((string)$response->EMLabel); + $trackingNumber = (string)$response->EMConfirmationNumber; + } elseif ($recipientUSCountry) { + // phpcs:disable Magento2.Functions.DiscouragedFunction + $labelContent = base64_decode((string)$response->SignatureConfirmationLabel); + $trackingNumber = (string)$response->SignatureConfirmationNumber; + } else { + // phpcs:disable Magento2.Functions.DiscouragedFunction + $labelContent = base64_decode((string)$response->LabelImage); + $trackingNumber = (string)$response->BarcodeNumber; + } + $result->setShippingLabelContent($labelContent); + $result->setTrackingNumber($trackingNumber); } - $result->setShippingLabelContent($labelContent); - $result->setTrackingNumber($trackingNumber); } - } - $result->setGatewayResponse($response); - $debugData['result'] = $response; - $this->_debug($debugData); + $result->setGatewayResponse($response); + $debugData['result'] = $response; + $this->_debug($debugData); - return $result; + return $result; + } else { + return $this->shipmentService->_doShipmentRequestRest($request); + } } /** @@ -2257,4 +2389,137 @@ private function createPackages(float $totalWeight, array $packages): array return $packages; } + + /** + * Sets received carrier quotes to cache + * + * @param array|string $requestParams + * @param string $response + * @return $this + */ + public function setCachedQuotes(array|string $requestParams, string $response) + { + return $this->_setCachedQuotes($requestParams, $response); + } + + /** + * Check if request is XML + * @return bool + */ + public function isXmlRequest(): bool + { + $isXmlRequest = false; + $type = $this->getConfigData('usps_type'); + if ($type == 'USPS_XML') { + $isXmlRequest = true; + } + return $isXmlRequest; + } + + /** + * To receive Oauth access token + * + * @return mixed + * @throws LocalizedException + * @throws \Throwable + */ + public function getOauthAccessRequest() + { + $userId = $this->getConfigData('client_id'); + $userIdPass = $this->getConfigData('client_secret'); + $authUrl = $this->getUrl(UspsAuth::OAUTH_REQUEST_END_POINT); + return $this->uspsAuth->getAccessToken($userId, $userIdPass, $authUrl); + } + + /** + * Get Url for REST API + * + * @param string|null $endpoint + * @return string + */ + public function getUrl(?string $endpoint = null) : string + { + $url = $this->getConfigFlag('mode') + ? $this->getConfigData('gateway_rest_prod_url') + : $this->getConfigData('gateway_rest_dev_url'); + + if (empty($url)) { + $url = $this->_defaultRestUrl; + } + + return $endpoint ? $url . $endpoint : $url; + } + + /** + * Replace sensitive fields. + * + * @param array $data + * @return array + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function filterJsonDebugData(array $data) + { + try { + foreach (array_keys($data) as $key) { + if (is_array($data[$key])) { + $data[$key] = $this->filterJsonDebugData($data[$key]); + } elseif (in_array($key, $this->_debugReplacePrivateDataKeys)) { + $data[$key] = self::DEBUG_KEYS_MASK; + } + } + } catch (\Exception $e) { + return ['*Failed to read JSON*']; + } + + return $data; + } + + /** + * Log debug data to file + * + * @param mixed $debugData + * @return void + */ + public function _debug($debugData) + { + if ($this->getDebugFlag()) { + $this->_logger->debug(var_export($debugData, true)); + } + } + + /** + * Check if the country is a U.S. Possession or Trust Territory + * + * @param string $countyId + * @return bool + */ + public function _isUSCountry($countyId) + { + $usCountries = ['AS', 'GU', 'MP', 'PW', 'PR', 'VI', 'US']; + return in_array($countyId, $usCountries, true); + } + + /** + * Checks whether some request to rates have already been done, so we have cache for it + * + * Used to reduce number of same requests done to carrier service during one session + * Returns cached response or null + * + * @param array|string $requestParams + * @return null|string + */ + public function getCachedQuotes(array|string $requestParams): ?string + { + return $this->_getCachedQuotes($requestParams); + } + + /** + * Return Raw Request Object + * + * @return DataObject|null + */ + public function getRawRequest(): ?DataObject + { + return $this->_rawRequest; + } } diff --git a/vendor/magento/module-usps/Model/Config/Source/Account.php b/vendor/magento/module-usps/Model/Config/Source/Account.php new file mode 100644 index 0000000000000..564f20b736fc3 --- /dev/null +++ b/vendor/magento/module-usps/Model/Config/Source/Account.php @@ -0,0 +1,30 @@ + __('EPS'), + 'PERMIT' => __('PERMIT') + ]; + + $arr = []; + foreach ($configData as $code => $title) { + $arr[] = ['value' => $code, 'label' => __($title)]; + } + return $arr; + } +} diff --git a/vendor/magento/module-usps/Model/Config/Source/PriceType.php b/vendor/magento/module-usps/Model/Config/Source/PriceType.php new file mode 100644 index 0000000000000..a61ef30a20568 --- /dev/null +++ b/vendor/magento/module-usps/Model/Config/Source/PriceType.php @@ -0,0 +1,25 @@ + 'COMMERCIAL', 'label' => __('Commercial')], + ['value' => 'RETAIL', 'label' => __('Retail')] + ]; + } +} diff --git a/vendor/magento/module-usps/Model/Config/Source/Type.php b/vendor/magento/module-usps/Model/Config/Source/Type.php new file mode 100644 index 0000000000000..b999874da5827 --- /dev/null +++ b/vendor/magento/module-usps/Model/Config/Source/Type.php @@ -0,0 +1,25 @@ + 'USPS_XML', 'label' => __('USPS Web Tools API')], + ['value' => 'USPS_REST', 'label' => __('USPS Rest APIs')] + ]; + } +} diff --git a/vendor/magento/module-usps/Model/ShipmentService.php b/vendor/magento/module-usps/Model/ShipmentService.php new file mode 100644 index 0000000000000..02ccf7da725da --- /dev/null +++ b/vendor/magento/module-usps/Model/ShipmentService.php @@ -0,0 +1,1008 @@ +_rateFactory = $rateFactory; + $this->_rateMethodFactory = $rateMethodFactory; + $this->_rateErrorFactory = $rateErrorFactory; + $this->_productCollectionFactory = $productCollection; + $this->_carrierHelper = $carrierHelper; + $this->_logger = $logger; + $this->uspsPaymentAuthToken = $uspsPaymentAuthToken; + $this->shippingMethodManager = $shippingMethodManager; + $this->httpClient = $httpClient ?? ObjectManager::getInstance()->get(AsyncClientInterface::class); + $this->proxyDeferredFactory = $proxyDeferredFactory + ?? ObjectManager::getInstance()->get(ProxyDeferredFactory::class); + } + + /** + * Set carrier model + * + * @param Carrier $carrierModel + * @return void + */ + public function setCarrierModel(Carrier $carrierModel) : void + { + $this->carrierModel = $carrierModel; + } + + /** + * Build RateV3 request, send it to USPS gateway and retrieve quotes in JSON format + * + * @link https://developer.usps.com/shippingoptionsv3 + * @return Result + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @throws LocalizedException + */ + public function getJsonQuotes(): Result + { + $request = $this->carrierModel->getRawRequest(); + $accessToken = $this->carrierModel->getOauthAccessRequest(); + // The origin address(shipper) must be only in USA + if (!$this->carrierModel->_isUSCountry($request->getOrigCountryId())) { + $responseBody = []; + return $this->_parseJsonResponse($responseBody); + } + $priceType = $this->carrierModel->getConfigData('price_type'); + $requestParam = [ + "originZIPCode" => $request->getOrigPostal(), + 'pricingOptions' => [ + [ + "priceType" => $priceType + ] + ], + ]; + + foreach ($request->getPackages() as $packageData) { + $requestParam['packageDescription'] = [ + "weight" => $packageData['weight_pounds'] ?? 1, + "mailClass" => 'ALL' + ]; + $requestParam['packageDescription']['length'] = $request->getLength() ? (int) $request->getLength() : 1; + $requestParam['packageDescription']['height'] = $request->getHeight() ? (int) $request->getHeight() : 1; + $requestParam['packageDescription']['width'] = $request->getWidth() ? (int) $request->getWidth() : 1; + + if ($request->getContainer() == 'NONRECTANGULAR' || $request->getContainer() == 'VARIABLE') { + $requestParam['packageDescription']['girth'] = $request->getGirth() ? (int) $request->getGirth() : 1; + } + + if ($this->carrierModel->_isUSCountry($request->getDestCountryId())) { + $requestParam['destinationZIPCode'] = is_string($request->getDestPostal()) ? + substr($request->getDestPostal(), 0, 5) : ''; + } else { + $requestParam['destinationCountryCode'] = $request->getDestCountryId(); + $requestParam['foreignPostalCode'] = $request->getDestPostal() ?? ''; + } + } + + $headers = [ + 'Content-Type' => self::CONTENT_TYPE_JSON, + 'Authorization' => self::AUTHORIZATION_BEARER . $accessToken + ]; + + $responseBody = $this->carrierModel->getCachedQuotes(json_encode($requestParam)); + + if ($responseBody === null) { + $debugData = ['request' => $this->carrierModel->filterJsonDebugData($requestParam)]; + $url = $this->carrierModel->getUrl(self::SHIPMENT_REQUEST_END_POINT); + + $deferredResponse = $this->httpClient->request(new Request( + $url, + Request::METHOD_POST, + $headers, + json_encode($requestParam) + )); + + return $this->proxyDeferredFactory->create( + [ + 'deferred' => new CallbackDeferred( + function () use ($deferredResponse, $requestParam, $debugData) { + $responseResult = null; + try { + $responseResult = $deferredResponse->get(); + } catch (HttpException $exception) { + $this->_logger->critical( + 'Critical error: ' . $exception->getMessage(), + ['exception' => $exception] + ); + } + $responseBody = $responseResult ? $responseResult->getBody() : ''; + $debugData['result'] = $responseBody; + $this->carrierModel->setCachedQuotes(json_encode($requestParam), $responseBody); + $this->carrierModel->_debug($debugData); + + return $this->_parseJsonResponse(json_decode($responseBody, true)); + } + ) + ] + ); + } + return $this->_parseJsonResponse(json_decode($responseBody, true)); + } + + /** + * Parse calculated rates for JSON response + * + * @param array $response + * @return Result + * @link http://www.usps.com/webtools/htm/Rate-Calculators-v2-3.htm + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * phpcs:disable Generic.Metrics.NestingLevel.TooHigh + */ + public function _parseJsonResponse($response): Result + { + $costArr = []; + if (!empty($response)) { + $shippingRates = $this->getShippingOptions($response); + foreach ($shippingRates as $shippingRate) { + foreach ($shippingRate as $rateElement) { + $this->processShippingRateForItem( + $rateElement, + $costArr + ); + } + } + uasort($costArr, function ($previous, $next) { + return ($previous <= $next) ? -1 : 1; + }); + } + + $result = $this->_rateFactory->create(); + if (empty($costArr)) { + $error = $this->_rateErrorFactory->create(); + $error->setCarrier('usps'); + $error->setCarrierTitle($this->carrierModel->getConfigData('title')); + $error->setErrorMessage($this->carrierModel->getConfigData('specificerrmsg')); + $result->append($error); + } else { + foreach ($costArr as $method) { + $rate = $this->_rateMethodFactory->create(); + + /** @var Method $method */ + $rate->setCarrier('usps'); + $rate->setCarrierTitle($this->carrierModel->getConfigData('title')); + + $rate->setMethod($method['code']); + $rate->setMethodTitle($method['productName']); + + $shippingCost = (float)$method['price']; + $rate->setCost($shippingCost); + $rate->setPrice($this->carrierModel->getMethodPrice($shippingCost, $method['code'])); + + /** @var Result $result */ + $result->append($rate); + } + } + + return $result; + } + + /** + * Processing rate for ship element + * + * @param array $rateElement + * @param array $costArr + */ + private function processShippingRateForItem( + array $rateElement, + array &$costArr + ): void { + if (empty($rateElement['productName']) && empty($rateElement['description'])) { + return; + } + $productName = $rateElement['description']; + $methodCode = strtoupper(substr($this->replaceSpaceWithUnderscore($productName), 0, 120)); + $methodTitle = $this->shippingMethodManager->getMethodTitle($methodCode); + $allowedMethods = $this->getRestAllowedMethods(); + if (in_array($methodCode, array_keys($allowedMethods))) { + // Use totalPrice if available, otherwise use price + $cost = isset($rateElement['totalPrice']) ? + (float)$rateElement['totalPrice'] : (float)$rateElement['price']; + $costArr[$methodCode] = [ + 'price' => $cost, + 'code' => $methodCode, + 'productName' => $methodTitle + ]; + } + } + + /** + * Replace space in string with hyphen + * + * @param string $string + * @return string + */ + public function replaceSpaceWithUnderscore(string $string): string + { + return str_replace(' ', '_', $string); + } + + /** + * Get shipping options from response + * + * @param array $response + * @return array + */ + private function getShippingOptions(array $response): array + { + $searchKey = 'rates'; + $foundValues = []; + $shippingOptions = function (array $response) use (&$foundValues, $searchKey, &$shippingOptions) { + foreach ($response as $key => $value) { + if ($key === $searchKey) { + // Include totalPrice in the rate data + foreach ($value as &$rate) { + if (isset($response['totalPrice'])) { + $rate['totalPrice'] = $response['totalPrice']; + } + } + $foundValues[] = $value; + } + if (is_array($value)) { + $shippingOptions($value); + } + } + }; + + $shippingOptions($response); + + return $foundValues; + } + + /** + * Do shipment request for domestic to carrier web service, obtain Print Shipping Labels + * + * @param DataObject $request + * @return array|DataObject + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @see no alternatives + */ + public function _prepareDomesticShipmentLabelRestRequest(DataObject $request): DataObject|array + { + $result = new DataObject(); + $shippingMethod = $request->getShippingMethod(); + try { + $packageParams = $request->getPackageParams(); + $dimensions = $this->preparePackageDimensions($request, $packageParams); + $height = $dimensions['height'] ?: $request->getPackageHeight(); + $width = $dimensions['width'] ?: $request->getPackageWidth(); + $length = $dimensions['length'] ?: $request->getPackageLength(); + $girth = $dimensions['girth'] ?: $request->getPackageGirth(); + $packagePoundsWeight = (float) $dimensions['weight'] ?: 1; + list($fromZip5) = $this->_parseZip($request->getShipperAddressPostalCode()); + list($toZip5) = $this->_parseZip($request->getRecipientAddressPostalCode(), true); + + $requestParam = [ + 'fromAddress' => [ + 'streetAddress' => $request->getShipperAddressStreet1(), + 'secondaryAddress' => $request->getShipperAddressStreet2() ?? '', + 'city' => $request->getShipperAddressCity(), + 'state' => $request->getShipperAddressStateOrProvinceCode(), + 'ZIPCode' => $fromZip5, + 'firstName' => $request->getShipperContactPersonFirstName(), + 'lastName' => $request->getShipperContactPersonLastName(), + 'firm' => $request->getShipperContactCompanyName(), + ], + 'toAddress' => [ + 'streetAddress' => $request->getRecipientAddressStreet1(), + 'secondaryAddress' => $request->getRecipientAddressStreet2() ?? '', + 'city' => $request->getRecipientAddressCity(), + 'state' => $request->getRecipientAddressStateOrProvinceCode(), + 'ZIPCode' => $toZip5, + 'firstName' => $request->getRecipientContactPersonFirstName(), + 'lastName' => $request->getRecipientContactPersonLastName(), + 'phone' => $request->getRecipientContactPhoneNumber(), + ], + 'packageDescription' => [ + 'length' => (float) $length, + 'height' => (float) $height, + 'width' => (float) $width, + 'weight' => (float) $packagePoundsWeight, + 'mailClass' => $this->shippingMethodManager->getMethodMailClass($shippingMethod), + 'mailingDate' => date('Y-m-d'), + 'processingCategory' => $this->shippingMethodManager->getMethodProcessingCategory($shippingMethod), + "destinationEntryFacilityType" => $this->shippingMethodManager + ->getMethodDestinationEntryFacilityType($shippingMethod), + 'rateIndicator' => $this->shippingMethodManager->getRateIndicator($shippingMethod), + ] + ]; + + if ($girth > 0) { + $requestParam['packageDescription']['girth'] = $girth; + } + + return $requestParam; + } catch (LocalizedException|Exception $e) { + $this->_logger->critical($e->getMessage()); + $result->setErrors([$e->getMessage()]); + } + return $result; + } + + /** + * Do shipment request for international to carrier web service, obtain Print Shipping Labels + * + * @param DataObject $request + * @return array|DataObject + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @see no alternatives + */ + public function _prepareIntlShipmentLabelRestRequest(DataObject $request): DataObject|array + { + $result = new DataObject(); + try { + $recipientCountryCode = $request->getRecipientAddressCountryCode(); + $requestParam = []; + $packageParams = $request->getPackageParams(); + $this->preparePackageDescForIntl($request, $packageParams, $requestParam); + $this->prepareCustomFormForIntl($request, $packageParams, $requestParam); + + $requestParam['fromAddress'] = [ + 'streetAddress' => $request->getShipperAddressStreet1(), + 'secondaryAddress' => $request->getShipperAddressStreet2() ?? '', + 'city' => $request->getShipperAddressCity(), + 'state' => $request->getShipperAddressStateOrProvinceCode(), + 'ZIPCode' => $request->getShipperAddressPostalCode(), + 'firstName' => $request->getShipperContactPersonFirstName(), + 'lastName' => $request->getShipperContactPersonLastName(), + 'firm' => $request->getShipperContactCompanyName(), + ]; + + $requestParam['toAddress'] = [ + 'streetAddress' => $request->getRecipientAddressStreet1(), + 'secondaryAddress' => $request->getRecipientAddressStreet2() ?? '', + 'city' => $request->getRecipientAddressCity(), + 'province' => $request->getRecipientAddressStateOrProvinceCode(), + 'postalCode' => $request->getRecipientAddressPostalCode(), + 'country' => $this->carrierModel->_getCountryName($recipientCountryCode), + 'countryISOAlpha2Code' => $recipientCountryCode, + 'firstName' => $request->getRecipientContactPersonFirstName(), + 'lastName' => $request->getRecipientContactPersonLastName(), + 'phone' => $request->getRecipientContactPhoneNumber() + ]; + + return $requestParam; + } catch (LocalizedException|Exception $e) { + $this->_logger->critical($e->getMessage()); + $result->setErrors([$e->getMessage()]); + } + + return $result; + } + + /** + * Do shipment request to carrier web service, obtain Print Shipping Labels and process errors in response + * + * @param DataObject $request + * @return DataObject + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @throws LocalizedException + * @throws \Throwable + */ + public function _doShipmentRequestRest(DataObject $request): DataObject + { + try { + $result = new DataObject(); + $recipientUSCountry = $this->carrierModel->_isUSCountry($request->getRecipientAddressCountryCode()); + $this->setPackageRequest($request); + $accessToken = $this->carrierModel->getOauthAccessRequest(); + + if (!$accessToken) { + throw new LocalizedException(__('We couldn\'t connect to USPS at the moment. + Please try again shortly.')); + } + + $paymentToken = $this->getLabelPaymentTokenRequest($accessToken); + } catch (LocalizedException|Exception $e) { + $this->_logger->critical($e->getMessage()); + $result = new DataObject(); + $result->setErrors([$e->getMessage()]); + return $result; + } + + $headers = [ + 'Content-Type' => self::CONTENT_TYPE_JSON, + 'Accept' => self::ACCEPT_HEADER, + 'Authorization' => self::AUTHORIZATION_BEARER . $accessToken, + 'X-Payment-Authorization-Token' => $paymentToken + ]; + + $url = $this->carrierModel->getUrl(); + if ($recipientUSCountry) { + $requestRest = $this->_prepareDomesticShipmentLabelRestRequest($request); + $url .= self::DOMESTIC_SHIPMENT_LABEL_REQUEST_END_POINT; + } else { + $requestRest = $this->_prepareIntlShipmentLabelRestRequest($request); + $url .= self::INTERNATIONAL_SHIPMENT_LABEL_REQUEST_END_POINT; + } + + if ($requestRest instanceof DataObject && $requestRest->getErrors()) { + $result->setErrors($requestRest->getErrors()); + return $result; + } + + $asyncResponse = $this->httpClient->request(new Request( + $url, + Request::METHOD_POST, + $headers, + json_encode($requestRest) + )); + + $responseResult = $asyncResponse->get(); + $response = json_decode($responseResult->getBody(), true); + + if ($responseResult->getStatusCode() === 200 || $responseResult->getStatusCode() === 201) { + // phpcs:disable Magento2.Functions.DiscouragedFunction + $labelContent = base64_decode((string)$response['labelImage']); + $result->setShippingLabelContent($labelContent); + if ($recipientUSCountry) { + $trackingNumber = (string)$response['trackingNumber']; + } else { + $trackingNumber = (string)$response['internationalTrackingNumber']; + } + $result->setTrackingNumber($trackingNumber); + $result->setGatewayResponse($response); + $debugData['result'] = $response; + $this->carrierModel->_debug($debugData); + } else { + $errorMsg = $this->handleErrorResponse($response); + if (empty($errorMsg)) { + $errorMsg[] = $response['error']['message'] + ?? __('An error occurred while processing your request.'); + } + $debugData['result'] = [ + 'error' => $errorMsg, + 'code' => $response['error']['code'], + 'request' => $response, + ]; + $this->carrierModel->_debug($debugData); + $result->setErrors($debugData['result']['error']); + } + return $result; + } + + /** + * Set Raw Request + * + * @param DataObject|null $request + * @return DataObject + */ + public function setPackageRequest(?DataObject $request): DataObject + { + $size = $request->getUspsSize() ?? $this->carrierModel->getConfigData('size'); + $request->setPackageSize($size); + $height = $request->getHeight() ?? $this->carrierModel->getConfigData('height'); + $length = $request->getLength() ?? $this->carrierModel->getConfigData('length'); + $width = $request->getWidth() ?? $this->carrierModel->getConfigData('width'); + + // Set default dimensions if not provided + $defaultDimension = 1; + $height = (int)($height ?: $defaultDimension); + $length = (int)($length ?: $defaultDimension); + $width = (int)($width ?: $defaultDimension); + + if ($size === 'LARGE') { + $request->setPackageHeight($height); + $request->setPackageLength($length); + $request->setPackageWidth($width); + + // Handle girth for non-rectangular containers + $container = $this->carrierModel->getConfigData('container'); + if (in_array($container, ['NONRECTANGULAR', 'VARIABLE'], true)) { + $girth = (int)($request->getGirth() + ?: $this->carrierModel->getConfigData('girth') ?: $defaultDimension); + $request->setPackageGirth($girth); + } + } else { + $request->setPackageHeight($height); + $request->setPackageWidth($width); + $request->setPackageLength($length); + } + + // Apply minimum dimensions if they exist for the shipping method + $packageDimension = $this->shippingMethodManager->getMethodMinDimensions($request->getShippingMethod()); + if ($packageDimension) { + $request->setPackageHeight(($packageDimension['height'] ?? $height)); + $request->setPackageWidth(($packageDimension['width'] ?? $width)); + $request->setPackageLength(($packageDimension['length'] ?? $length)); + } + + return $request; + } + + /** + * Handle error response and render message to user. + * + * @param array $response + * @return array + */ + public function handleErrorResponse(array $response): array + { + $errorMessage = []; + if (isset($response['error'])) { + $error = $response['error']; + if (isset($error['errors']) && is_array($error['errors'])) { + foreach ($error['errors'] as $errorDetail) { + $errorMessage[] = $errorDetail['detail']; + } + } + } + return $errorMessage; + } + + /** + * Convert decimal weight into pound-ounces format + * + * @param float $weightInPounds + * @return float[] + */ + public function _convertPoundOunces(float $weightInPounds): array + { + $weightInOunces = ceil($weightInPounds * Carrier::OUNCES_POUND); + $pounds = floor($weightInOunces / Carrier::OUNCES_POUND); + $ounces = $weightInOunces % Carrier::OUNCES_POUND; + + return [$pounds, $ounces]; + } + + /** + * Parse zip from string to zip5-zip4 + * + * @param string $zipString + * @param bool $returnFull + * @return string[] + */ + public function _parseZip(string $zipString, bool $returnFull = false): array + { + $zip4 = ''; + $zip5 = ''; + $zip = [$zipString]; + if ($zipString !== null && preg_match('/[\\d\\w]{5}\\-[\\d\\w]{4}/', $zipString) != 0) { + $zip = explode('-', $zipString); + } + $count = count($zip); + for ($i = 0; $i < $count; ++$i) { + if (strlen($zip[$i] ?? '') == 5) { + $zip5 = $zip[$i]; + } elseif (strlen($zip[$i] ?? '') == 4) { + $zip4 = $zip[$i]; + } + } + if (empty($zip5) && empty($zip4) && $returnFull) { + $zip5 = $zipString; + } + + return [$zip5, $zip4]; + } + + /** + * Get allowed rest shipping methods + * + * @return array + */ + public function getRestAllowedMethods(): array + { + $allowed = explode(',', $this->carrierModel->getConfigData('rest_allowed_methods') ?? ''); + $arr = []; + foreach ($allowed as $k) { + $arr[$k] = $this->carrierModel->getCode('rest_method', $k); + } + + return $arr; + } + + /** + * Prepare custom form request for International shipment label + * + * @param DataObject $request + * @param DataObject $packageParams + * @param array $requestParam + * @return void + * @throws LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + private function prepareCustomFormForIntl($request, $packageParams, &$requestParam): void + { + $aesitn = $this->carrierModel->getConfigData('aesitn'); + $recipientCountryCode = $request->getRecipientAddressCountryCode(); + $packageItems = $request->getPackageItems(); + // get countries of manufacture + $productCountriesManufacturesList = []; + $productIds = []; + foreach ($packageItems as $itemShipment) { + $item = new DataObject(); + $item->setData($itemShipment); + + $productIds[] = $item->getProductId(); + } + $productCollection = $this->_productCollectionFactory->create()->addStoreFilter( + $request->getStoreId() + )->addFieldToFilter( + 'entity_id', + ['in' => $productIds] + )->addAttributeToSelect( + 'country_of_manufacture' + ); + foreach ($productCollection as $product) { + $productCountriesManufacturesList[$product->getId()] = $product->getCountryOfManufacture(); + } + + if ($packageParams->getContentType() == 'OTHER' && $packageParams->getContentTypeOther() != null) { + $requestParam['customsForm']['customsContentType'] = $packageParams->getContentType(); + } else { + $requestParam['customsForm']['customsContentType'] = $packageParams->getContentType(); + } + + foreach ($packageItems as $itemShipment) { + $item = new DataObject(); + $item->setData($itemShipment); + $itemWeight = $item->getWeight() * $item->getQty(); + if ($packageParams->getWeightUnits() != Weight::POUND) { + $itemWeight = $this->_carrierHelper->convertMeasureWeight( + $itemWeight, + $packageParams->getWeightUnits(), + Weight::POUND + ); + } + $countryofOrigin = $productCountriesManufacturesList[$item->getProductId()] + ?? $request->getShipperAddressCountryCode(); + + $ceiledQty = max(1, ceil((int)$item->getQty())); + list($itemPoundsWeight) = $this->_convertPoundOunces((float)$itemWeight); + $requestParam['customsForm']['contents'][] = [ + "itemDescription" => $item->getName(), + "itemQuantity" => (int) $ceiledQty, + "itemTotalWeight" => $itemPoundsWeight, + "itemTotalValue" => (float) sprintf('%.2F', $item->getCustomsValue() * $item->getQty()), + "countryofOrigin" => $countryofOrigin + ]; + } + + if (empty($aesitn)) { + if ($recipientCountryCode === 'CA') { + $aesitn = 'NOEEI 30.36'; + } else { + throw new LocalizedException( + __( + 'Each type of goods in the shipment under certain value or less according to the + Schedule B Export Codes + and the shipment must not require an export license. If any item exceeds this value, + an export license is required. A shipment (regardless of value) is going to Canada and does not + require an export license. Users may enter %2 in the AESITN field if the shipment value + meets the exemption criteria. Please contact USPS for more information.', + 'www.census.gov/foreign-trade/schedules/b', + "'NO EEI 30.37(a)'" + ) + ); + } + } + $requestParam['customsForm']['AESITN'] = $aesitn; + } + + /** + * Prepare Package Description for International shipment label + * + * @param DataObject $request + * @param DataObject $packageParams + * @param array $requestParam + * @return void + * @throws LocalizedException + */ + private function preparePackageDescForIntl($request, $packageParams, &$requestParam): void + { + $dimensions = $this->preparePackageDimensions($request, $packageParams); + $girth = $dimensions['girth'] ?: $request->getPackageGirth(); + $packagePoundsWeight = $dimensions['weight'] ?: null; + $shippingMethod = $request->getShippingMethod(); + + $minDimension = $this->shippingMethodManager->getMethodMinDimensions($shippingMethod); + $maxDimension = $this->shippingMethodManager->getMethodMaxDimensions($shippingMethod); + + $dimensionNames = ['length', 'width', 'height']; + + foreach ($dimensionNames as $dimensionName) { + $dimensionValue = $dimensions[$dimensionName]; + if (isset($minDimension[$dimensionName]) && $dimensionValue < $minDimension[$dimensionName]) { + throw new LocalizedException(__( + 'The package dimensions are invalid. The package %1 must be greater than or equal to %2 inch.', + $dimensionName, + $minDimension[$dimensionName] + )); + } + + if (isset($maxDimension[$dimensionName]) && $dimensionValue > $maxDimension[$dimensionName]) { + throw new LocalizedException(__( + 'The package dimensions are invalid. The package %1 must be less than or equal to %2 inch.', + $dimensionName, + $maxDimension[$dimensionName] + )); + } + } + + $requestParam['packageDescription'] = [ + "destinationEntryFacilityType" => + $this->shippingMethodManager->getMethodDestinationEntryFacilityType($shippingMethod), + "height" => (float)$dimensions['height'], + "length" => (float)$dimensions['length'], + "width" => (float)$dimensions['width'], + "weight" => (float) $packagePoundsWeight, + "mailClass" => $this->shippingMethodManager->getMethodMailClass($shippingMethod), + "mailingDate" => (new \DateTime())->format('Y-m-d'), + "processingCategory" => $this->shippingMethodManager->getMethodProcessingCategory($shippingMethod), + "rateIndicator" => $this->shippingMethodManager->getRateIndicator($shippingMethod), + ]; + + if ($girth > 0) { + $requestParam['packageDescription']['girth'] = $girth; + } + } + + /** + * To receive payment access token for label generation + * + * @param string $accessToken + * @return string + * @throws LocalizedException + * @throws \Throwable + */ + public function getLabelPaymentTokenRequest(string $accessToken): string + { + $cridNumber = $this->carrierModel->getConfigData('crid'); + $midNumber = $this->carrierModel->getConfigData('mid'); + $manifestMID = $this->carrierModel->getConfigData('mmid'); + $accountNumber = $this->carrierModel->getConfigData('account_number'); + $accountType = $this->carrierModel->getConfigData('account_type'); + $accountInfo = [ + "CRID" => $cridNumber, + "MID" => $midNumber, + "manifestMID" => $manifestMID, + "accountNumber" => $accountNumber, + "accountType" => $accountType + ]; + + $authUrl = $this->carrierModel->getUrl(self::PAYMENT_AUTH_REQUEST_END_POINT); + return $this->uspsPaymentAuthToken->getPaymentAuthToken($accessToken, $authUrl, $accountInfo); + } + + /** + * Prepares package dimensions and weight for shipment. + * + * This method calculates and converts the dimensions and weight of a package + * into the required units (inches and pounds) if they are not already in those units. + * It also handles the conversion of girth dimensions if necessary. + * + * @param DataObject $request The shipment request object containing package details. + * @param DataObject $packageParams The package parameters object containing dimensions and weight units. + * @return array An associative array containing the converted dimensions and weight: + * - 'height': The height of the package in inches. + * - 'width': The width of the package in inches. + * - 'length': The length of the package in inches. + * - 'girth': The girth of the package in inches. + * - 'weight': The weight of the package in pounds. + * @throws LocalizedException If a conversion error occurs. + */ + public function preparePackageDimensions(DataObject $request, DataObject $packageParams): array + { + try { + // Retrieve initial dimensions and weight from the package parameters + // Use request values as fallback when packageParams doesn't have values + $height = $packageParams->getHeight() ?: $request->getPackageHeight(); + $width = $packageParams->getWidth() ?: $request->getPackageWidth(); + $length = $packageParams->getLength() ?: $request->getPackageLength(); + $girth = $packageParams->getGirth() ?: $request->getPackageGirth(); + $packageWeight = $request->getPackageWeight(); + + // Convert weight to pounds if it is not already in pounds + if ($packageParams->getWeightUnits() != Weight::POUND) { + $packageWeight = $this->_carrierHelper->convertMeasureWeight( + (float)$request->getPackageWeight(), + $packageParams->getWeightUnits(), + Weight::POUND + ); + } + + // Convert dimensions to inches if they are not already in inches + if ($packageParams->getDimensionUnits() != Length::INCH) { + $length = round( + (float) $this->_carrierHelper->convertMeasureDimension( + (float)$length, + $packageParams->getDimensionUnits(), + Length::INCH + ) + ); + $width = round( + (float) $this->_carrierHelper->convertMeasureDimension( + (float)$width, + $packageParams->getDimensionUnits(), + Length::INCH + ) + ); + $height = round( + (float) $this->_carrierHelper->convertMeasureDimension( + (float)$height, + $packageParams->getDimensionUnits(), + Length::INCH + ) + ); + } + + // Convert girth to inches if it is not already in inches + if ($packageParams->getGirthDimensionUnits() != Length::INCH) { + $girth = round( + (float) $this->_carrierHelper->convertMeasureDimension( + (float)$girth, + $packageParams->getGirthDimensionUnits(), + Length::INCH + ) + ); + } + + // Return the converted dimensions and weight as an associative array + return [ + 'height' => $height, + 'width' => $width, + 'length' => $length, + 'girth' => $girth, + 'weight' => $packageWeight, + ]; + } catch (MeasureException $e) { + $this->_logger->error('Error converting package dimensions or weight: ' . $e->getMessage()); + throw new LocalizedException(__('Failed to prepare package dimensions. Please check the input values.')); + } catch (Exception $e) { + $this->_logger->critical('Unexpected error: ' . $e->getMessage()); + throw new LocalizedException(__('An unexpected error occurred while preparing package dimensions.')); + } + } +} diff --git a/vendor/magento/module-usps/Model/ShippingMethodManager.php b/vendor/magento/module-usps/Model/ShippingMethodManager.php new file mode 100644 index 0000000000000..0257a5f1aaf2f --- /dev/null +++ b/vendor/magento/module-usps/Model/ShippingMethodManager.php @@ -0,0 +1,407 @@ + [ + 'description' => "Library Mail", + 'title' => "Library Mail", + 'rate_indicator' => "5D", + 'mail_class' => "LIBRARY_MAIL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "MEDIA_MAIL_MACHINABLE_5-DIGIT" => [ + 'description' => "Media Mail Machinable 5-digit", + 'title' => "Media Mail", + 'rate_indicator' => "5D", + 'mail_class' => "MEDIA_MAIL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "USPS_GROUND_ADVANTAGE_MACHINABLE_SINGLE-PIECE" => [ + 'description' => "USPS Ground Advantage Machinable Single-piece", + 'title' => "USPS Ground Advantage", + 'rate_indicator' => "SP", + 'mail_class' => "USPS_GROUND_ADVANTAGE", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "USPS_GROUND_ADVANTAGE_MACHINABLE_CUBIC_NON-SOFT_PACK_TIER_1" => [ + 'description' => "USPS Ground Advantage Machinable Cubic Non-Soft Pack Tier 1", + 'title' => "USPS Ground Advantage Cubic Non-Soft Pack", + 'rate_indicator' => "CP", + 'mail_class' => "USPS_GROUND_ADVANTAGE", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "USPS_GROUND_ADVANTAGE_MACHINABLE_CUBIC_SOFT_PACK_TIER_1" => [ + 'description' => "USPS Ground Advantage Machinable Cubic Soft Pack Tier 1", + 'title' => "USPS Ground Advantage Cubic Soft Pack", + 'rate_indicator' => "P5", + 'mail_class' => "USPS_GROUND_ADVANTAGE", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_MACHINABLE_SINGLE-PIECE" => [ + 'description' => "Priority Mail Machinable Single-piece", + 'title' => "Priority Mail", + 'rate_indicator' => "SP", + 'mail_class' => "PRIORITY_MAIL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail Flat Rate Envelope", + 'title' => "Priority Mail Flat Rate Envelope", + 'rate_indicator' => "FE", + 'mail_class' => "PRIORITY_MAIL", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_MACHINABLE_MEDIUM_FLAT_RATE_BOX" => [ + 'description' => "Priority Mail Machinable Medium Flat Rate Box", + 'title' => "Priority Mail Medium Flat Rate Box", + 'rate_indicator' => "FB", + 'mail_class' => "PRIORITY_MAIL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_MACHINABLE_LARGE_FLAT_RATE_BOX" => [ + 'description' => "Priority Mail Machinable Large Flat Rate Box", + 'title' => "Priority Mail Large Flat Rate Box", + 'rate_indicator' => "PL", + 'mail_class' => "PRIORITY_MAIL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_MACHINABLE_SMALL_FLAT_RATE_BOX" => [ + 'description' => "Priority Mail Machinable Small Flat Rate Box", + 'title' => "Priority Mail Small Flat Rate Box", + 'rate_indicator' => "FS", + 'mail_class' => "PRIORITY_MAIL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_PADDED_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail Padded Flat Rate Envelope", + 'title' => "Priority Mail Padded Flat Rate Envelope", + 'rate_indicator' => "FP", + 'mail_class' => "PRIORITY_MAIL", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_LEGAL_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail Legal Flat Rate Envelope", + 'title' => "Priority Mail Legal Flat Rate Envelope", + 'rate_indicator' => "FA", + 'mail_class' => "PRIORITY_MAIL", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_EXPRESS_MACHINABLE_SINGLE-PIECE" => [ + 'description' => "Priority Mail Express Machinable Single-piece", + 'title' => "Priority Mail Express", + 'rate_indicator' => "PA", + 'mail_class' => "PRIORITY_MAIL_EXPRESS", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_EXPRESS_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail Express Flat Rate Envelope", + 'title' => "Priority Mail Express Flat Rate Envelope", + 'rate_indicator' => "E4", + 'mail_class' => "PRIORITY_MAIL_EXPRESS", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_EXPRESS_LEGAL_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail Express Legal Flat Rate Envelope", + 'title' => "Priority Mail Express Legal Flat Rate Envelope", + 'rate_indicator' => "E6", + 'mail_class' => "PRIORITY_MAIL_EXPRESS", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "NONE" + ], + "PRIORITY_MAIL_EXPRESS_PADDED_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail Express Padded Flat Rate Envelope", + 'title' => "Priority Mail Express Padded Flat Rate Envelope", + 'rate_indicator' => "FP", + 'mail_class' => "PRIORITY_MAIL_EXPRESS", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "NONE" + ], + "FIRST-CLASS_PACKAGE_INTERNATIONAL_SERVICE_MACHINABLE_ISC_SINGLE-PIECE" => [ + 'description' => "First-Class Package International Service Machinable ISC Single-piece", + 'title' => "First-Class Package International Service", + 'rate_indicator' => "SP", + 'mail_class' => "FIRST-CLASS_PACKAGE_INTERNATIONAL_SERVICE", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 6, + 'height' => 4, + 'width' => 0.007 + ], + 'max_dimension' => [ + 'length' => 24 + ] + ], + "PRIORITY_MAIL_INTERNATIONAL_ISC_SINGLE-PIECE" => [ + 'description' => "Priority Mail International ISC Single-piece", + 'title' => "Priority Mail International", + 'rate_indicator' => "SP", + 'mail_class' => "PRIORITY_MAIL_INTERNATIONAL", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 5.5, + 'height' => 3.5, + 'width' => 0.007 + ], + 'max_dimension' => [ + 'length' => 60 + ] + ], + "PRIORITY_MAIL_INTERNATIONAL_ISC_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail International ISC Flat Rate Envelope", + 'title' => "Priority Mail International Flat Rate Envelope", + 'rate_indicator' => "FE", + 'mail_class' => "PRIORITY_MAIL_INTERNATIONAL", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 12.5, + 'height' => 9.5, + 'width' => 0.007 + ] + ], + "PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_MEDIUM_FLAT_RATE_BOX" => [ + 'description' => "Priority Mail International Machinable ISC Medium Flat Rate Box", + 'title' => "Priority Mail International Medium Flat Rate Box", + 'rate_indicator' => "FB", + 'mail_class' => "PRIORITY_MAIL_INTERNATIONAL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 11.25, + 'height' => 8.75, + 'width' => 6 + ] + ], + "PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_LARGE_FLAT_RATE_BOX" => [ + 'description' => "Priority Mail International Machinable ISC Large Flat Rate Box", + 'title' => "Priority Mail International Large Flat Rate Box", + 'rate_indicator' => "PL", + 'mail_class' => "PRIORITY_MAIL_INTERNATIONAL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 12.25, + 'height' => 12.25, + 'width' => 6 + ] + ], + "PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_SMALL_FLAT_RATE_BOX" => [ + 'description' => "Priority Mail International Machinable ISC Small Flat Rate Box", + 'title' => "Priority Mail International Small Flat Rate Box", + 'rate_indicator' => "FS", + 'mail_class' => "PRIORITY_MAIL_INTERNATIONAL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 8.6875, + 'height' => 5.4375, + 'width' => 1.75 + ] + ], + "PRIORITY_MAIL_INTERNATIONAL_ISC_LEGAL_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail International ISC Legal Flat Rate Envelope", + 'title' => "Priority Mail International Legal Flat Rate Envelope", + 'rate_indicator' => "FA", + 'mail_class' => "PRIORITY_MAIL_INTERNATIONAL", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 15, + 'height' => 9.5, + 'width' => 0.75 + ] + ], + "PRIORITY_MAIL_INTERNATIONAL_MACHINABLE_ISC_PADDED_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail International Machinable ISC Padded Flat Rate Envelope", + 'title' => "Priority Mail International Padded Flat Rate Envelope", + 'rate_indicator' => "FP", + 'mail_class' => "PRIORITY_MAIL_INTERNATIONAL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 12.5, + 'height' => 9.5, + 'width' => 1 + ] + ], + "PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail Express International ISC Flat Rate Envelope", + 'title' => "Priority Mail Express International Flat Rate Envelope", + 'rate_indicator' => "E4", + 'mail_class' => "PRIORITY_MAIL_EXPRESS_INTERNATIONAL", + 'processing_category' => "MACHINABLE", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 12.5, + 'height' => 9.5, + 'width' => 0.5 + ] + ], + "PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_LEGAL_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail Express International ISC Legal Flat Rate Envelope", + 'title' => "Priority Mail Express International Legal Flat Rate Envelope", + 'rate_indicator' => "E6", + 'mail_class' => "PRIORITY_MAIL_EXPRESS_INTERNATIONAL", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 15, + 'height' => 9.5, + 'width' => 0.75 + ] + ], + "PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_PADDED_FLAT_RATE_ENVELOPE" => [ + 'description' => "Priority Mail Express International ISC Padded Flat Rate Envelope", + 'title' => "Priority Mail Express International Padded Flat Rate Envelope", + 'rate_indicator' => "FP", + 'mail_class' => "PRIORITY_MAIL_EXPRESS_INTERNATIONAL", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 12.5, + 'height' => 9.5, + 'width' => 1 + ] + ], + "PRIORITY_MAIL_EXPRESS_INTERNATIONAL_ISC_SINGLE-PIECE" => [ + 'description' => "Priority Mail Express International ISC Single-piece", + 'title' => "Priority Mail Express International", + 'rate_indicator' => "PA", + 'mail_class' => "PRIORITY_MAIL_EXPRESS_INTERNATIONAL", + 'processing_category' => "FLATS", + 'destination_entry_facility_type' => "INTERNATIONAL_SERVICE_CENTER", + 'min_dimension' => [ + 'length' => 6, + 'height' => 0.25, + 'width' => 0.25 + ], + 'max_dimension' => [ + 'length' => 36 + ] + ] + ]; + + /** + * Get an array of all method codes with their titles + * + * @return array [method_code => title] + */ + public function getMethodCodesWithTitles(): array + { + $result = []; + foreach ($this->shippingMethods as $code => $data) { + $result[$code] = $data['title']; + } + return $result; + } + + /** + * Get the rate indicator for a specific method code + * + * @param string $methodCode The shipping method code + * @return string|null The rate indicator or null if not found + */ + public function getRateIndicator(string $methodCode): ?string + { + $methodCode = strtoupper($methodCode); + return $this->shippingMethods[$methodCode]['rate_indicator'] ?? 'SP'; + } + + /** + * Get the mail class for a specific method code + * + * @param string $methodCode + * @return string|null + */ + public function getMethodTitle(string $methodCode): ?string + { + $methodCode = strtoupper($methodCode); + return $this->shippingMethods[$methodCode]['title'] ?? null; + } + + /** + * Get the mail class for a specific method code + * + * @param string $methodCode + * @return string|null + */ + public function getMethodMailClass(string $methodCode): ?string + { + $methodCode = strtoupper($methodCode); + return $this->shippingMethods[$methodCode]['mail_class'] ?? null; + } + + /** + * Get the processing category for a specific method code + * + * @param string $methodCode + * @return string|null + */ + public function getMethodProcessingCategory(string $methodCode): ?string + { + $methodCode = strtoupper($methodCode); + return $this->shippingMethods[$methodCode]['processing_category'] ?? null; + } + + /** + * Get the destination entry facility type for a specific method code + * + * @param string $methodCode + * @return string|null + */ + public function getMethodDestinationEntryFacilityType(string $methodCode): ?string + { + $methodCode = strtoupper($methodCode); + return $this->shippingMethods[$methodCode]['destination_entry_facility_type'] ?? 'NONE'; + } + /** + * Get the minimum dimensions for a specific method code + * + * @param string $methodCode + * @return array|null + */ + public function getMethodMinDimensions(string $methodCode): ?array + { + $methodCode = strtoupper($methodCode); + return $this->shippingMethods[$methodCode]['min_dimension'] ?? null; + } + + /** + * Get the maximum dimensions for a specific method code + * + * @param string $methodCode + * @return array|null + */ + public function getMethodMaxDimensions(string $methodCode): ?array + { + $methodCode = strtoupper($methodCode); + return $this->shippingMethods[$methodCode]['max_dimension'] ?? null; + } +} diff --git a/vendor/magento/module-usps/Model/Source/Freemethod.php b/vendor/magento/module-usps/Model/Source/Freemethod.php index 7072d1e09b29b..566cdaea5ec49 100644 --- a/vendor/magento/module-usps/Model/Source/Freemethod.php +++ b/vendor/magento/module-usps/Model/Source/Freemethod.php @@ -1,7 +1,7 @@ code = $this->getUspsTypeMethodCode(); + } + + /** + * Get dynamic code based on USPS type configuration + * + * @return string + */ + private function getUspsTypeMethodCode(): string + { + $uspsType = $this->shippingUsps->getConfigData('usps_type'); + + return match ($uspsType) { + 'USPS_REST' => 'rest_method', + default => 'method', + }; + } + /** + * @inheritDoc */ public function toOptionArray() { diff --git a/vendor/magento/module-usps/Model/Source/RestMethod.php b/vendor/magento/module-usps/Model/Source/RestMethod.php new file mode 100644 index 0000000000000..855d159152660 --- /dev/null +++ b/vendor/magento/module-usps/Model/Source/RestMethod.php @@ -0,0 +1,18 @@ +trackFactory = $trackFactory; + $this->trackErrorFactory = $trackErrorFactory; + $this->trackStatusFactory = $trackStatusFactory; + $this->httpClient = $httpClient ?? ObjectManager::getInstance()->get(AsyncClientInterface::class); + } + + /** + * Set carrier model + * + * @param Carrier $carrierModel + * @return void + */ + public function setCarrierModel(Carrier $carrierModel) : void + { + $this->carrierModel = $carrierModel; + } + + /** + * Send request for tracking using REST API + * + * @param array $trackingData + * @return Result|null + * @throws LocalizedException + * @throws Throwable + */ + public function getRestTracking(array $trackingData): ?Result + { + $url = $this->carrierModel->getUrl(self::TRACK_REQUEST_END_POINT); + $accessToken = $this->carrierModel->getOauthAccessRequest(); + $queryParams = [ + "expand" => "DETAIL" + ]; + + /** @var HttpResponseDeferredInterface[] $trackingResponses */ + $trackingResponses = []; + $debugData = []; + + foreach ($trackingData as $tracking) { + if ($tracking === null) { + $tracking = ''; + } + $trackParams = (object)[]; + $trackPayload = json_encode($trackParams); + $headers = [ + 'Content-Type' => self::CONTENT_TYPE_JSON, + 'Authorization' => self::AUTHORIZATION_BEARER . $accessToken + ]; + $debugData[$tracking] = ['request' => $trackPayload]; + try { + $trackingResponses[$tracking] = $this->httpClient->request( + new Request( + $url . '/' . urlencode($tracking) . + "?" . http_build_query($queryParams), + Request::METHOD_GET, + $headers, + null + ) + ); + } catch (\Throwable $e) { + $debugData[$tracking]['error'] = $e->getMessage(); + $this->carrierModel->_debug($debugData); + continue; // Skip to the next tracking number + } + } + foreach ($trackingResponses as $tracking => $response) { + $httpResponse = $response->get(); + $jsonResponse = $httpResponse->getStatusCode() >= 400 ? '' : $httpResponse->getBody(); + $debugData[$tracking]['result'] = $jsonResponse; + $this->carrierModel->_debug($debugData); + $this->parseRestTrackingResponse((string)$tracking, $jsonResponse); + } + return $this->result; + } + + /** + * Parse REST tracking response + * + * @param string $trackingValue + * @param string $jsonResponse + * @return Result + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function parseRestTrackingResponse(string $trackingValue, string $jsonResponse): Result + { + $errorTitle = self::ERROR_TITLE_DEFAULT; + $resultArr = []; + $packageProgress = []; + if ($jsonResponse) { + $responseData = json_decode($jsonResponse, true); + if (is_array($responseData) && isset($responseData['trackingNumber'])) { + $trackingEvents = $responseData['trackingEvents'] ?? []; + if (is_array($trackingEvents)) { + foreach ($trackingEvents as $activityTag) { + $this->processActivityRestTagInfo($activityTag, $packageProgress); + } + $resultArr['track_summary'] = $responseData['statusSummary']; + $resultArr['progressdetail'] = $packageProgress; + } + } else { + $errorTitle = $responseData['error']['message']; + } + } + + return $this->setTrackingResultData($resultArr, $trackingValue, $errorTitle); + } + + /** + * Set Tracking Response Data + * + * @param array $resultArr + * @param string $trackingValue + * @param string $errorTitle + * @return Result + */ + private function setTrackingResultData(array $resultArr, string $trackingValue, string $errorTitle): Result + { + if (!$this->result) { + $this->result = $this->trackFactory->create(); + } + + if ($resultArr) { + $tracking = $this->trackStatusFactory->create(); + $tracking->setCarrier($this->carrierModel->_code); + $tracking->setCarrierTitle($this->carrierModel->getConfigData('title')); + $tracking->setTracking($trackingValue); + $tracking->addData($resultArr); + $this->result->append($tracking); + } else { + $error = $this->trackErrorFactory->create(); + $error->setCarrier($this->carrierModel->_code); + $error->setCarrierTitle($this->carrierModel->getConfigData('title')); + $error->setTracking($trackingValue); + $error->setErrorMessage($errorTitle); + $this->result->append($error); + } + + return $this->result; + } + + /** + * Process tracking info from activity tag + * + * @param array $activityTag + * @param array $packageProgress + */ + private function processActivityRestTagInfo( + array $activityTag, + array &$packageProgress + ): void { + $addressArr = array_filter([ + $activityTag['eventCity'] ?? null, + $activityTag['eventState'] ?? null, + $activityTag['eventCountry'] ?? null + ]); + + $eventTimestamp = (new \DateTime((string)$activityTag['eventTimestamp'])); + $date = $eventTimestamp->format('Y-m-d'); + $time = $eventTimestamp->format('H:i:s'); + + $packageProgress[] = [ + 'activity' => (string)$activityTag['eventType'], + 'deliverydate' => $date, + 'deliverytime' => $time, + 'deliverylocation' => implode(', ', $addressArr) + ]; + } + + /** + * Get tracking response + * + * @return string + */ + public function getResponse(): string + { + $statuses = ''; + if ($this->result instanceof \Magento\Shipping\Model\Tracking\Result) { + if ($trackingData = $this->result->getAllTrackings()) { + foreach ($trackingData as $tracking) { + if ($data = $tracking->getAllData()) { + $statuses .= !empty($data['track_summary']) ? __($data['track_summary']) : __('Empty response'); + } + } + } + } + return $statuses ?: (string)__('Empty response'); + } +} diff --git a/vendor/magento/module-usps/Model/UspsAuth.php b/vendor/magento/module-usps/Model/UspsAuth.php new file mode 100644 index 0000000000000..935421ea40841 --- /dev/null +++ b/vendor/magento/module-usps/Model/UspsAuth.php @@ -0,0 +1,118 @@ +cache = $cacheManager; + $this->_rateErrorFactory = $rateErrorFactory; + $this->httpClient = $httpClient ?? ObjectManager::getInstance()->get(AsyncClientInterface::class); + } + + /** + * Token Generation + * + * @param String $clientId + * @param String $clientSecret + * @param String $clientUrl + * @return string|false|null + * @throws LocalizedException + * @throws \Throwable + */ + public function getAccessToken(string $clientId, string $clientSecret, string $clientUrl): string|false|null + { + $cacheKey = self::CACHE_KEY_PREFIX; + $accessToken = $this->cache->load($cacheKey); + if (!$accessToken) { + $headers = [ + 'Content-Type' => self::CONTENT_TYPE_FORM_URLENCODED + ]; + + $authPayload = http_build_query([ + 'grant_type' => 'client_credentials', + 'client_id' => $clientId, + 'client_secret' => $clientSecret, + 'scope' => 'prices shipments tracking labels payments international-labels' + ]); + try { + $asyncResponse = $this->httpClient->request(new Request( + $clientUrl, + Request::METHOD_POST, + $headers, + $authPayload + )); + + $responseResult = $asyncResponse->get(); + $response = $responseResult->getBody(); + $response = json_decode($response, true); + + if (!empty($response['access_token'])) { + $accessToken = $response['access_token']; + $this->cache->save($accessToken, $cacheKey, [], $response['expires_in'] ?: 10000); + } else { + $debugData = ['request_type' => 'Access Token Request', 'result' => $response]; + $this->_debug($debugData); + return false; + } + } catch (\Exception $e) { + $this->_debug(self::ERROR_LOG_MESSAGE . $e->getMessage()); + return null; + } + } + return $accessToken; + } + + /** + * @inheritDoc + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * phpcs:disable + */ + public function collectRates(RateRequest $request) + { + return ''; // This block is empty as not required. + } +} diff --git a/vendor/magento/module-usps/Model/UspsPaymentAuthToken.php b/vendor/magento/module-usps/Model/UspsPaymentAuthToken.php new file mode 100644 index 0000000000000..c929a06e182c9 --- /dev/null +++ b/vendor/magento/module-usps/Model/UspsPaymentAuthToken.php @@ -0,0 +1,114 @@ +asyncHttpClient = $asyncHttpClient ?? ObjectManager::getInstance()->get(AsyncClientInterface::class); + } + + /** + * Payment Auth Token Generation + * + * @param String $token + * @param string $authUrl + * @param array $accountInfo + * @return string + * @throws Throwable + */ + public function getPaymentAuthToken(string $token, string $authUrl, array $accountInfo): string + { + $headers = [ + 'Content-Type' => self::CONTENT_TYPE_JSON, + 'Authorization' => self::AUTHORIZATION_BEARER . $token, + ]; + + $requestParam = [ + 'roles' => [ + [ + "roleName" => "PAYER", + "CRID" => $accountInfo['CRID'], + "accountNumber" => $accountInfo['accountNumber'], + "accountType" => $accountInfo['accountType'], + ], + [ + "roleName" => "LABEL_OWNER", + "CRID" => $accountInfo['CRID'], + "MID" => $accountInfo['MID'], + "manifestMID" => $accountInfo['manifestMID'], + ] + ], + ]; + + try { + $asyncResponse = $this->asyncHttpClient->request(new Request( + $authUrl, + Request::METHOD_POST, + $headers, + json_encode($requestParam) + )); + + $responseResult = $asyncResponse->get(); + $responseData = $responseResult->getBody(); + $responseData = json_decode($responseData); + + if (isset($responseData->paymentAuthorizationToken)) { + return $responseData->paymentAuthorizationToken; + } else { + $debugData = ['request_type' => 'Payment Access Token Request for Label generation', + 'result' => $responseData]; + $this->_debug($debugData); + $errorMessage = $responseData->error->message ?? 'Unknown error occurred'; + throw new LocalizedException(__($errorMessage)); + + } + } catch (HttpException $e) { + $this->_debug(self::ERROR_LOG_MESSAGE . $e->getMessage()); + throw new LocalizedException(__('HTTP Exception: ' . $e->getMessage())); + } catch (Throwable $e) { + $this->_debug(self::ERROR_LOG_MESSAGE . $e->getMessage()); + throw new LocalizedException(__('Error: ' . $e->getMessage())); + } + } + + /** + * @inheritDoc + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * phpcs:disable + */ + public function collectRates(RateRequest $request) : string + { + return ''; // This block is empty as not required. + } +} diff --git a/vendor/magento/module-usps/etc/adminhtml/system.xml b/vendor/magento/module-usps/etc/adminhtml/system.xml index 00c9632b99367..8db91306e4b4a 100644 --- a/vendor/magento/module-usps/etc/adminhtml/system.xml +++ b/vendor/magento/module-usps/etc/adminhtml/system.xml @@ -1,8 +1,8 @@ @@ -14,13 +14,39 @@ Magento\Config\Model\Config\Source\Yesno + + + Magento\Usps\Model\Config\Source\Type + Magento\Usps\Model\Config\Backend\UspsUrl + + USPS_XML + + + + + Magento\Usps\Model\Config\Backend\UspsUrl + + USPS_REST + 1 + + + + + Magento\Usps\Model\Config\Backend\UspsUrl + + USPS_REST + 0 + Magento\Usps\Model\Config\Backend\UspsUrl + + USPS_XML + @@ -28,22 +54,95 @@ Magento\Config\Model\Config\Backend\Encrypted + + USPS_XML + Magento\Config\Model\Config\Backend\Encrypted + + USPS_XML + + + + + Magento\Config\Model\Config\Backend\Encrypted + + USPS_REST + + + + + Magento\Config\Model\Config\Backend\Encrypted + + USPS_REST + + + + + Magento\Usps\Model\Config\Source\Account + + USPS_REST + - + + + Magento\Usps\Model\Config\Source\PriceType + + USPS_REST + + + + + required-entry + + USPS_REST + + + + + required-entry + + USPS_REST + + + + + required-entry + + USPS_REST + + + + + required-entry + + USPS_REST + + + + + Automated Export System/International Transaction Number. For international shipment outside US. + Each item in the shipment, classified under Schedule B Export Codes (available at www.census.gov/foreign-trade/schedules/b), is valued at $2,500 or less, and no export license is required. If the shipment, regardless of value, is destined for Canada without requiring an export license, no further action is needed. Otherwise, the AESITN field must be filled with the appropriate AES/ITN. + + USPS_REST + + + Magento\Shipping\Model\Config\Source\Online\Mode - + Magento\Shipping\Model\Config\Source\Online\Requesttype - + Magento\Usps\Model\Source\Container + + USPS_XML + @@ -51,18 +150,21 @@ + required-entry validate-number validate-greater-than-zero LARGE + required-entry validate-number validate-greater-than-zero LARGE + required-entry validate-number validate-greater-than-zero LARGE @@ -76,6 +178,9 @@ Magento\Usps\Model\Source\Machinable + + USPS_XML + @@ -97,6 +202,17 @@ Magento\Usps\Model\Source\Method 1 + + USPS_XML + + + + + Magento\Usps\Model\Source\RestMethod + 1 + + USPS_REST + diff --git a/vendor/magento/module-usps/etc/config.xml b/vendor/magento/module-usps/etc/config.xml index bd6ad73f03130..0e4c7f6c7c3e9 100644 --- a/vendor/magento/module-usps/etc/config.xml +++ b/vendor/magento/module-usps/etc/config.xml @@ -1,8 +1,8 @@ @@ -10,12 +10,16 @@ 0 + USPS_XML 0 0_FCLE,0_FCL,0_FCP,1,2,3,4,6,7,13,16,17,22,23,25,27,28,33,34,35,36,37,42,43,53,57,61,INT_1,INT_2,INT_4,INT_6,INT_7,INT_8,INT_9,INT_10,INT_11,INT_12,INT_13,INT_14,INT_15,INT_16,INT_20,1058,4058,6058,2058,4096,1096,2096,6096 + LIBRARY_MAIL_MACHINABLE_5-DIGIT,MEDIA_MAIL_MACHINABLE_5-DIGIT,USPS_GROUND_ADVANTAGE_MACHINABLE_SINGLE-PIECE,PRIORITY_MAIL_MACHINABLE_SINGLE-PIECE,PRIORITY_MAIL_FLAT_RATE_ENVELOPE,PRIORITY_MAIL_MACHINABLE_MEDIUM_FLAT_RATE_BOX,PRIORITY_MAIL_MACHINABLE_LARGE_FLAT_RATE_BOX,PRIORITY_MAIL_MACHINABLE_SMALL_FLAT_RATE_BOX,PRIORITY_MAIL_PADDED_FLAT_RATE_ENVELOPE,PRIORITY_MAIL_LEGAL_FLAT_RATE_ENVELOPE,PRIORITY_MAIL_EXPRESS_MACHINABLE_SINGLE-PIECE,PRIORITY_MAIL_EXPRESS_FLAT_RATE_ENVELOPE,PRIORITY_MAIL_EXPRESS_LEGAL_FLAT_RATE_ENVELOPE,FIRST-CLASS_PACKAGE_INTERNATIONAL_SERVICE_MACHINABLE_ISC_SINGLE-PIECE,PRIORITY_MAIL_INTERNATIONAL_ISC_SINGLE-PIECE,PRIORITY_MAIL_INTERNATIONAL_ISC_FLAT_RATE_ENVELOPE VARIABLE https://production.shippingapis.com/ShippingAPI.dll + https://api.usps.com/ + https://apis-tem.usps.com/ https://secure.shippingapis.com/ShippingAPI.dll 0 @@ -23,9 +27,13 @@ Magento\Usps\Model\Carrier REGULAR + EPS + COMMERCIAL United States Postal Service + + 0 This shipping method is currently unavailable. If you would like to ship using this shipping method, please contact us. 70