move to HttpClient
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\HttpClient\HttpClient;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||||
|
|
||||||
class UspsV3Client
|
class UspsV3Client
|
||||||
{
|
{
|
||||||
private $token;
|
private $token;
|
||||||
@@ -10,15 +15,14 @@ class UspsV3Client
|
|||||||
{
|
{
|
||||||
$this->token = $token;
|
$this->token = $token;
|
||||||
$this->isLive = $isLive;
|
$this->isLive = $isLive;
|
||||||
// URLs from the OpenAPI spec
|
// Base URLs per OpenAPI Spec
|
||||||
$this->baseUrl = $this->isLive
|
$this->baseUrl = $this->isLive
|
||||||
? 'https://apis.usps.com/prices/v3'
|
? 'https://apis.usps.com/prices/v3'
|
||||||
: 'https://apis-tem.usps.com/prices/v3';
|
: 'https://apis-tem.usps.com/prices/v3';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call Domestic Prices v3 API
|
* Get Domestic Rate (Rates v3)
|
||||||
* Endpoint: /base-rates/search
|
|
||||||
*/
|
*/
|
||||||
public function getDomesticRate($payload)
|
public function getDomesticRate($payload)
|
||||||
{
|
{
|
||||||
@@ -26,13 +30,11 @@ class UspsV3Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call International Prices v3 API
|
* Get International Rate (Rates v3)
|
||||||
* Endpoint: /base-rates/search (Under International Base path)
|
|
||||||
* Note: The spec shows a different base URL for International
|
|
||||||
*/
|
*/
|
||||||
public function getInternationalRate($payload)
|
public function getInternationalRate($payload)
|
||||||
{
|
{
|
||||||
// International uses a different base URL structure in the spec
|
// International endpoint uses a different base structure per spec
|
||||||
$intlBaseUrl = $this->isLive
|
$intlBaseUrl = $this->isLive
|
||||||
? 'https://apis.usps.com/international-prices/v3'
|
? 'https://apis.usps.com/international-prices/v3'
|
||||||
: 'https://apis-tem.usps.com/international-prices/v3';
|
: 'https://apis-tem.usps.com/international-prices/v3';
|
||||||
@@ -40,42 +42,53 @@ class UspsV3Client
|
|||||||
return $this->post('/base-rates/search', $payload, $intlBaseUrl);
|
return $this->post('/base-rates/search', $payload, $intlBaseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal POST logic using Symfony HTTP Client
|
||||||
|
*/
|
||||||
private function post($endpoint, $payload, $overrideUrl = null)
|
private function post($endpoint, $payload, $overrideUrl = null)
|
||||||
{
|
{
|
||||||
$url = ($overrideUrl ? $overrideUrl : $this->baseUrl) . $endpoint;
|
$url = ($overrideUrl ? $overrideUrl : $this->baseUrl) . $endpoint;
|
||||||
|
|
||||||
$ch = curl_init();
|
$client = HttpClient::create([
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
'timeout' => 15,
|
||||||
curl_setopt($ch, CURLOPT_POST, 1);
|
'verify_peer' => false,
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
'verify_host' => false,
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
||||||
'Authorization: Bearer ' . $this->token,
|
|
||||||
'Content-Type: application/json',
|
|
||||||
'Accept: application/json'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
try {
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$response = $client->request('POST', $url, [
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer ' . $this->token,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Accept' => 'application/json'
|
||||||
|
],
|
||||||
|
'json' => $payload
|
||||||
|
]);
|
||||||
|
|
||||||
if (curl_errno($ch)) {
|
// toArray(false) prevents exception on 4xx/5xx responses so we can parse the error body
|
||||||
$error = curl_error($ch);
|
$data = $response->toArray(false);
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
|
||||||
return ['error' => 'CURL Error: ' . $error];
|
// Handle API Errors (400 Bad Request, 401 Unauthorized, etc)
|
||||||
}
|
if ($statusCode >= 400) {
|
||||||
|
$msg = isset($data['error']['message']) ? $data['error']['message'] : 'Unknown Error';
|
||||||
|
|
||||||
|
// Try to extract deeper error detail (e.g., from 'errors' array)
|
||||||
|
if (isset($data['error']['errors'][0]['detail'])) {
|
||||||
|
$msg .= ' - ' . $data['error']['errors'][0]['detail'];
|
||||||
|
} elseif (isset($data['error']['code'])) {
|
||||||
|
$msg .= ' (' . $data['error']['code'] . ')';
|
||||||
|
}
|
||||||
|
|
||||||
$data = json_decode($response, true);
|
return ['error' => "API HTTP $statusCode: $msg"];
|
||||||
|
|
||||||
// Check for HTTP errors (400, 401, 403, etc)
|
|
||||||
if ($httpCode >= 400) {
|
|
||||||
$msg = isset($data['error']['message']) ? $data['error']['message'] : 'Unknown Error';
|
|
||||||
if (isset($data['error']['errors'][0]['detail'])) {
|
|
||||||
$msg .= ' - ' . $data['error']['errors'][0]['detail'];
|
|
||||||
}
|
}
|
||||||
return ['error' => "HTTP $httpCode: $msg"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
|
|
||||||
|
} catch (TransportExceptionInterface $e) {
|
||||||
|
return ['error' => 'Network/Transport Error: ' . $e->getMessage()];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return ['error' => 'Client Error: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
config_uk.xml
Normal file
12
config_uk.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<module>
|
||||||
|
<name>usps_api_bridge</name>
|
||||||
|
<displayName><![CDATA[USPS API Bridge (OAuth2)]]></displayName>
|
||||||
|
<version><![CDATA[1.0.0]]></version>
|
||||||
|
<description><![CDATA[Modern OAuth2 Bridge for the legacy ZH USPS Labels module.]]></description>
|
||||||
|
<author><![CDATA[Panariga]]></author>
|
||||||
|
<tab><![CDATA[shipping_logistics]]></tab>
|
||||||
|
<confirmUninstall><![CDATA[Are you sure? This will disable the connection to the new USPS API.]]></confirmUninstall>
|
||||||
|
<is_configurable>1</is_configurable>
|
||||||
|
<need_instance>0</need_instance>
|
||||||
|
</module>
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
if (!defined('_PS_VERSION_')) {
|
if (!defined('_PS_VERSION_')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
use Symfony\Component\HttpClient\HttpClient;
|
||||||
|
|
||||||
|
|
||||||
class Usps_Api_Bridge extends Module
|
class Usps_Api_Bridge extends Module
|
||||||
{
|
{
|
||||||
@@ -354,46 +356,59 @@ class Usps_Api_Bridge extends Module
|
|||||||
$clientSecret = Configuration::get('USPS_BRIDGE_CLIENT_SECRET');
|
$clientSecret = Configuration::get('USPS_BRIDGE_CLIENT_SECRET');
|
||||||
$isLive = (bool)Configuration::get('USPS_BRIDGE_LIVE_MODE');
|
$isLive = (bool)Configuration::get('USPS_BRIDGE_LIVE_MODE');
|
||||||
|
|
||||||
// URLs based on documentation (Verification pending next step)
|
// CORRECT URLs based on the OpenAPI Spec provided:
|
||||||
|
// Prod: https://apis.usps.com/oauth2/v3
|
||||||
|
// Test: https://apis-tem.usps.com/oauth2/v3
|
||||||
$url = $isLive
|
$url = $isLive
|
||||||
? 'https://api.usps.com/oauth2/v3/token'
|
? 'https://apis.usps.com/oauth2/v3/token'
|
||||||
: 'https://api-cat.usps.com/oauth2/v3/token';
|
: 'https://apis-tem.usps.com/oauth2/v3/token';
|
||||||
|
|
||||||
$this->log("Requesting New Token from: " . $url);
|
$this->log("Requesting New Token from: " . $url);
|
||||||
|
|
||||||
$ch = curl_init();
|
// Create Symfony Client
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
$client = HttpClient::create([
|
||||||
curl_setopt($ch, CURLOPT_POST, 1);
|
'timeout' => 10,
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
|
'verify_peer' => true, // Set to true in strict production environments
|
||||||
'client_id' => $clientId,
|
'verify_host' => false,
|
||||||
'client_secret' => $clientSecret,
|
]);
|
||||||
'grant_type' => 'client_credentials'
|
|
||||||
]));
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
try {
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$response = $client->request('POST', $url, [
|
||||||
|
'headers' => [
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Accept' => 'application/json',
|
||||||
|
],
|
||||||
|
// 'json' key automatically encodes the array to JSON and sets Content-Type
|
||||||
|
'json' => [
|
||||||
|
'client_id' => $clientId,
|
||||||
|
'client_secret' => $clientSecret,
|
||||||
|
'grant_type' => 'client_credentials',
|
||||||
|
// 'scope' => 'prices international-prices' // Specifying scope helps avoid ambiguity
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
if (curl_errno($ch)) {
|
// Get status code
|
||||||
$this->log("CURL Error: " . curl_error($ch));
|
$statusCode = $response->getStatusCode();
|
||||||
|
|
||||||
return false;
|
// Convert response to array (pass false to prevent throwing exceptions on 4xx/5xx)
|
||||||
|
$data = $response->toArray(false);
|
||||||
|
|
||||||
|
if ($statusCode == 200 && isset($data['access_token'])) {
|
||||||
|
$expiresIn = isset($data['expires_in']) ? (int)$data['expires_in'] : 3599;
|
||||||
|
|
||||||
|
Configuration::updateValue('USPS_BRIDGE_ACCESS_TOKEN', $data['access_token']);
|
||||||
|
Configuration::updateValue('USPS_BRIDGE_TOKEN_EXPIRY', time() + $expiresIn);
|
||||||
|
|
||||||
|
$this->log("Token refreshed successfully.");
|
||||||
|
return $data['access_token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log detailed error from USPS
|
||||||
|
$this->log("Token Request Failed [HTTP $statusCode]: " . json_encode($data));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->log("Symfony HTTP Client Error: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$data = json_decode($response, true);
|
|
||||||
|
|
||||||
if ($httpCode == 200 && isset($data['access_token'])) {
|
|
||||||
$expiresIn = isset($data['expires_in']) ? (int)$data['expires_in'] : 3599;
|
|
||||||
|
|
||||||
Configuration::updateValue('USPS_BRIDGE_ACCESS_TOKEN', $data['access_token']);
|
|
||||||
Configuration::updateValue('USPS_BRIDGE_TOKEN_EXPIRY', time() + $expiresIn);
|
|
||||||
|
|
||||||
$this->log("Token refreshed successfully.");
|
|
||||||
return $data['access_token'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->log("Token Request Failed: " . print_r($response, true));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user